Merge pull request #10 from ForestOfLight/dev

v1.0.6
This commit is contained in:
Forest
2025-12-14 15:44:47 -08:00
committed by GitHub
Unverified
27 changed files with 337 additions and 154 deletions
+2 -1
View File
@@ -6,7 +6,7 @@
[![GitHub Downloads](https://img.shields.io/github/downloads/ForestOfLight/Construct/total?label=Github%20downloads&logo=github)](https://github.com/ForestOfLight/Construct/releases) [![GitHub Downloads](https://img.shields.io/github/downloads/ForestOfLight/Construct/total?label=Github%20downloads&logo=github)](https://github.com/ForestOfLight/Construct/releases)
[![Curseforge Downloads](https://cf.way2muchnoise.eu/full_1283139_downloads.svg)](https://www.curseforge.com/minecraft-bedrock/addons/construct) [![Curseforge Downloads](https://cf.way2muchnoise.eu/full_1283139_downloads.svg)](https://www.curseforge.com/minecraft-bedrock/addons/construct)
[![Minecraft - Version](https://img.shields.io/badge/Minecraft-v1.21.120_(Bedrock)-brightgreen)](https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs) [![Minecraft - Version](https://img.shields.io/badge/Minecraft-v1.21.130_(Bedrock)-brightgreen)](https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs)
[![Discord](https://badgen.net/discord/members/9KGche8fxm?icon=discord&label=Discord&list=what)](https://discord.gg/9KGche8fxm) [![Discord](https://badgen.net/discord/members/9KGche8fxm?icon=discord&label=Discord&list=what)](https://discord.gg/9KGche8fxm)
</div> </div>
@@ -71,6 +71,7 @@ Need help, want to discuss technical Minecraft, or follow future updates? [**Joi
- [x] Automatic material gathering from inventories - [x] Automatic material gathering from inventories
- [x] Material list - [x] Material list
- [x] Flexible structure movement - [x] Flexible structure movement
- [ ] Translation support
- [ ] Block texture display - [ ] Block texture display
## Issues & Suggestions ## Issues & Suggestions
+7 -3
View File
@@ -1,21 +1,25 @@
{ {
"format_version": "1.20.30", "format_version": "1.20.50",
"minecraft:item": { "minecraft:item": {
"description": { "description": {
"identifier": "construct:easy_place", "identifier": "construct:easy_place",
"category": "Items" "category": "Items"
}, },
"components": { "components": {
"minecraft:max_stack_size": 1,
"minecraft:icon": { "minecraft:icon": {
"texture": "construct:easy_place" "texture": "construct:easy_place"
}, },
"minecraft:display_name": { "minecraft:display_name": {
"value": "Easy Place" "value": "construct.easyplace.name"
}, },
"minecraft:max_stack_size": 1,
"minecraft:wearable": { "minecraft:wearable": {
"dispensable": true, "dispensable": true,
"slot": "slot.weapon.offhand" "slot": "slot.weapon.offhand"
},
"minecraft:use_modifiers": {
"use_duration": 3600,
"movement_modifier": 1
} }
} }
} }
+1 -1
View File
@@ -11,7 +11,7 @@
"texture": "construct:material_grabber" "texture": "construct:material_grabber"
}, },
"minecraft:display_name": { "minecraft:display_name": {
"value": "Material Grabber" "value": "construct.materialgrabber.name"
}, },
"minecraft:wearable": { "minecraft:wearable": {
"dispensable": true, "dispensable": true,
+1 -1
View File
@@ -11,7 +11,7 @@
"texture": "construct:menu" "texture": "construct:menu"
}, },
"minecraft:display_name": { "minecraft:display_name": {
"value": "Construct" "value": "construct.menu.name"
}, },
"minecraft:wearable": { "minecraft:wearable": {
"dispensable": true, "dispensable": true,
+5 -5
View File
@@ -1,11 +1,11 @@
{ {
"format_version": 2, "format_version": 2,
"header": { "header": {
"name": "Construct [BP] v1.0.5", "name": "Construct [BP] v1.0.6",
"description": "Survival building addon by §aForestOfLight§r.", "description": "Survival building addon by §aForestOfLight§r.",
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
"min_engine_version": [1, 21, 120], "min_engine_version": [1, 21, 130],
"version": [1, 0, 5] "version": [1, 0, 6]
}, },
"modules": [ "modules": [
{ {
@@ -26,7 +26,7 @@
"dependencies": [ "dependencies": [
{ {
"module_name": "@minecraft/server", "module_name": "@minecraft/server",
"version": "2.4.0-beta" "version": "2.5.0-beta"
}, },
{ {
"module_name": "@minecraft/server-ui", "module_name": "@minecraft/server-ui",
@@ -34,7 +34,7 @@
}, },
{ {
"uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", // Construct RP "uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", // Construct RP
"version": [1, 0, 5] "version": [1, 0, 6]
} }
], ],
"metadata": { "metadata": {
+22 -10
View File
@@ -16,29 +16,41 @@ class BlockInfo {
static showStructureBlockInfo(player) { static showStructureBlockInfo(player) {
const block = Raycaster.getTargetedStructureBlock(player, { isFirst: true, collideWithWorldBlocks: true, useActiveLayer: true }); const block = Raycaster.getTargetedStructureBlock(player, { isFirst: true, collideWithWorldBlocks: true, useActiveLayer: true });
if (!block && this.shownToLastTick.has(player.id)) { if (!block && this.shownToLastTick.has(player.id)) {
player.onScreenDisplay.setActionBar({ text: 'Structure:\n§7None' }); player.onScreenDisplay.setActionBar({ rawtext: [
{ translate: 'construct.blockinfo.header' },
{ text: '\n' },
{ translate: 'construct.blockinfo.none' }
]});
this.shownToLastTick.delete(player.id); this.shownToLastTick.delete(player.id);
} }
if (!block) if (!block)
return; return;
player.onScreenDisplay.setActionBar({ text: this.getFormattedBlockInfo(player, block.permutation) }); player.onScreenDisplay.setActionBar(this.getFormattedBlockInfo(player, block.permutation));
this.shownToLastTick.add(player.id); this.shownToLastTick.add(player.id);
} }
static getFormattedBlockInfo(player, block) { static getFormattedBlockInfo(player, block) {
return 'Structure:' + this.getSupplyMessage(player, block) + '\n' + this.getBlockMessage(block); return { rawtext: [
{ translate: 'construct.blockinfo.header' },
this.getSupplyMessage(player, block),
{ text: '\n' },
this.getBlockMessage(block)
] };
} }
static getBlockMessage(block) { static getBlockMessage(block) {
if (!block) if (!block)
return '§7Unknown'; return { translate: 'construct.blockinfo.unknown' };
let output = `§a${block.type.id}`; const message = { rawtext: [{ text: '§a' }, { translate: block.type.id }]};
const states = block.getAllStates(); const states = block.getAllStates();
if (Object.keys(states).length > 0) if (Object.keys(states).length > 0)
output += `\n§7${this.getFormattedStates(states)}`; message.rawtext.push({ text: `\n§7${this.getFormattedStates(states)}` });
if (block.isWaterlogged) if (block.isWaterlogged)
output += `\n§7isWaterlogged: §3true`; message.rawtext.push({ rawtext: [
return output; { text: '\n§7' },
{ translate: 'construct.blockinfo.waterlogged' }
]});
return message;
} }
static getFormattedStates(states) { static getFormattedStates(states) {
@@ -49,8 +61,8 @@ class BlockInfo {
const itemStack = fetchMatchingItemSlot(player, block.getItemStack()?.typeId); const itemStack = fetchMatchingItemSlot(player, block.getItemStack()?.typeId);
const isInSurvival = player.getGameMode() === GameMode.Survival; const isInSurvival = player.getGameMode() === GameMode.Survival;
if (!itemStack && isInSurvival) if (!itemStack && isInSurvival)
return ' §c[No Supply]'; return { translate: 'construct.blockinfo.nosupply' };
return ''; return { text: '' };
} }
} }
@@ -20,10 +20,21 @@ export class BuilderForm {
for (let i = 0; i < optionIds.length; i++) { for (let i = 0; i < optionIds.length; i++) {
const option = BuilderOptions.get(optionIds[i]); const option = BuilderOptions.get(optionIds[i]);
const changedToValue = option.setValue(this.player.id, formValues[i]); const changedToValue = option.setValue(this.player.id, formValues[i]);
if (changedToValue === true) if (changedToValue === true) {
this.player.sendMessage(`§a${option.displayName} is now enabled!§7 ${option.howToUse}`); this.player.sendMessage({ rawtext: [
else if (changedToValue === false) { text: `§a` },
this.player.sendMessage(`§c${option.displayName} is now disabled.`) option.displayName,
{ translate: 'construct.option.enabled' },
{ text: `§7 ` },
option.howToUse
]});
} else if (changedToValue === false) {
this.player.sendMessage({ rawtext: [
{ text: `§c` },
option.displayName,
{ translate: 'construct.option.disabled' }
]});
}
} }
} }
@@ -8,9 +8,9 @@ export class BuilderFormBuilder {
.title(MenuFormBuilder.menuTitle); .title(MenuFormBuilder.menuTitle);
for (const optionId of BuilderOptions.getOptionIds()) { for (const optionId of BuilderOptions.getOptionIds()) {
const option = BuilderOptions.get(optionId); const option = BuilderOptions.get(optionId);
form.toggle(`${option.displayName}`, { defaultValue: option.isEnabled(player.id), tooltip: option.description }); form.toggle(option.displayName, { defaultValue: option.isEnabled(player.id), tooltip: option.description });
} }
form.submitButton('§2Apply'); form.submitButton({ translate: "construct.menu.submit" });
return form; return form;
} }
} }
@@ -1,16 +1,16 @@
export const InstanceButtons = Object.freeze({ export const InstanceButtons = Object.freeze({
Unknown: 'Unknown', Unknown: 'construct.menu.instance.button.unknown',
MainMenu: '<<', MainMenu: '<<',
Place: '§aPlace Instance', Place: 'construct.menu.instance.button.place',
Enable: '§aEnable Instance', Enable: 'construct.menu.instance.button.enable',
Disable: '§cDisable Instance', Disable: 'construct.menu.instance.button.disable',
Rename: 'Rename Instance', Rename: 'construct.menu.instance.button.rename',
Delete: '§cDelete Instance', Delete: 'construct.menu.instance.button.delete',
NextLayer: 'Increase Layer', NextLayer: 'construct.menu.instance.button.nextLayer',
PreviousLayer: 'Decrease Layer', PreviousLayer: 'construct.menu.instance.button.previousLayer',
Move: 'Move Here', Move: 'construct.menu.instance.button.move',
FlexibleMove: 'Flexible Move', FlexibleMove: 'construct.menu.instance.button.flexibleMove',
Statistics: 'Statistics', Statistics: 'construct.menu.instance.button.statistics',
Settings: 'Settings', Settings: 'construct.menu.instance.button.settings',
Materials: 'Material List' Materials: 'construct.menu.instance.button.materials'
}); });
@@ -23,7 +23,7 @@ export class FlexibleInstanceMove {
tryStart() { tryStart() {
if (this.instance.isFlexibleMoving()) { if (this.instance.isFlexibleMoving()) {
this.sendFeedback('§cThis instance is already being moved.'); this.sendFeedback({ translate: 'construct.instance.flexibleMove.alreadyMoving' });
return; return;
} }
this.start(); this.start();
@@ -48,7 +48,7 @@ export class FlexibleInstanceMove {
this.allowPlayerMovement(false); this.allowPlayerMovement(false);
world.beforeEvents.itemUse.subscribe(this.onPlayerUseItemBound); world.beforeEvents.itemUse.subscribe(this.onPlayerUseItemBound);
world.beforeEvents.playerLeave.unsubscribe(this.onPlayerLeaveBound); world.beforeEvents.playerLeave.unsubscribe(this.onPlayerLeaveBound);
this.sendFeedback(`§aNow moving "${this.instance.getName()}". Use the Construct item when finished.`); this.sendFeedback({ translate: 'construct.instance.flexibleMove.start', with: [this.instance.getName()] });
} }
onFlexibleMovementTick() { onFlexibleMovementTick() {
@@ -109,7 +109,10 @@ export class FlexibleInstanceMove {
builder.flexibleInstanceMovement = void 0; builder.flexibleInstanceMovement = void 0;
world.beforeEvents.itemUse.unsubscribe(this.onPlayerUseItemBound); world.beforeEvents.itemUse.unsubscribe(this.onPlayerUseItemBound);
world.beforeEvents.playerLeave.unsubscribe(this.onPlayerLeaveBound); world.beforeEvents.playerLeave.unsubscribe(this.onPlayerLeaveBound);
this.sendFeedback(`§aMoved "${this.instance.getName()}" to ${this.currentInstanceLocation.floor()}.`); this.sendFeedback({ translate: 'construct.instance.flexibleMove.finish', with: [
this.instance.getName(),
String(this.currentInstanceLocation.floor())
] });
} }
allowPlayerMovement(enable) { allowPlayerMovement(enable) {
@@ -113,7 +113,7 @@ export class InstanceForm {
new MenuForm(this.player, { jumpToInstance: false }); new MenuForm(this.player, { jumpToInstance: false });
break; break;
default: default:
this.player.sendMessage(`§cUnknown option: ${option}`); this.player.sendMessage({ translate: 'construct.menu.instance.unknownOption', with: [option] });
break; break;
} }
} }
@@ -124,15 +124,14 @@ export class InstanceForm {
return; return;
const newName = response.formValues[0]; const newName = response.formValues[0];
if (newName === '') { if (newName === '') {
this.player.sendMessage('§cInstance name cannot be empty.'); this.player.sendMessage({ translate: 'construct.menu.instance.nameEmpty' });
return; return;
} }
try { try {
structureCollection.rename(this.instanceName, newName); structureCollection.rename(this.instanceName, newName);
this.instanceName = newName; this.instanceName = newName;
} catch (e) { } catch (e) {
this.player.sendMessage(`§cError renaming instance: ${e.message}`); this.player.sendMessage({ translate: 'construct.instance.rename.error', with: [e.message] });
return;
} }
}); });
} }
@@ -156,13 +155,13 @@ export class InstanceForm {
statsForm = await InstanceFormBuilder.buildStatistics(this.instance); statsForm = await InstanceFormBuilder.buildStatistics(this.instance);
} catch (e) { } catch (e) {
if (e.message === 'StructureVerifier is already running.') { if (e.message === 'StructureVerifier is already running.') {
this.player.sendMessage('§cA verification is already in progress. Please wait until it finishes.'); this.player.sendMessage({ translate: 'construct.instance.validation.alreadyRunning' });
return; return;
} }
throw e; throw e;
} }
if (!statsForm) { if (!statsForm) {
this.player.sendMessage('§cFailed to build statistics form.'); this.player.sendMessage({ translation: 'construct.instance.validation.formfail' });
return; return;
} }
statsForm.form.show(this.player).then((response) => { statsForm.form.show(this.player).then((response) => {
@@ -11,12 +11,17 @@ export class InstanceFormBuilder {
const location = instance.getLocation(); const location = instance.getLocation();
const form = new ActionFormData() const form = new ActionFormData()
.title(MenuFormBuilder.menuTitle) .title(MenuFormBuilder.menuTitle)
let body = `Instance: §a${instance.getName()}\n§fStructure: §2${instance.getStructureId()}\n`; const body = { rawtext: [
{ translate: 'construct.instance.menu.body', with: [instance.getName(), instance.getStructureId()] }
]};
if (instance.hasLocation()) if (instance.hasLocation())
body += `§7(${location.location.x} ${location.location.y} ${location.location.z} in ${location.dimensionId})\n`; body.rawtext.push(...[
{ text: '\n' },
{ translate: 'construct.instance.menu.location', with: [String(location.location.x), String(location.location.y), String(location.location.z), location.dimensionId] }
]);
form.body(body); form.body(body);
options.forEach(option => { options.forEach(option => {
form.button(`${option}`); form.button({ translate: option });
}); });
return form; return form;
} }
@@ -24,8 +29,8 @@ export class InstanceFormBuilder {
static buildRenameInstance(currentName) { static buildRenameInstance(currentName) {
return new ModalFormData() return new ModalFormData()
.title(MenuFormBuilder.menuTitle) .title(MenuFormBuilder.menuTitle)
.textField('Enter a new name for the instance:', currentName) .textField({ translate: 'construct.isntance.menu.rename' }, currentName)
.submitButton('Rename'); .submitButton({ translate: 'construct.menu.instance.button.rename' });
} }
static async buildStatistics(instance) { static async buildStatistics(instance) {
@@ -45,9 +50,9 @@ export class InstanceFormBuilder {
static buildSettings(instance) { static buildSettings(instance) {
return new ModalFormData() return new ModalFormData()
.title(MenuFormBuilder.menuTitle) .title(MenuFormBuilder.menuTitle)
.toggle('Block Validation', { defaultValue: instance.options.verifier.isEnabled, tooltip: 'Shows missing and incorrect block overlay.' }) .toggle({ translate: 'construct.instance.option.validation' }, { defaultValue: instance.options.verifier.isEnabled, tooltip: { translate: 'construct.instance.option.validation.description' }})
.slider("Layer", 0, instance.getMaxLayer(), { defaultValue: instance.getLayer(), valueStep: 1, tooltip: 'Changes the active layer. Use 0 for all layers.' }) .slider({ translate: 'construct.instance.option.layer'}, 0, instance.getMaxLayer(), { defaultValue: instance.getLayer(), valueStep: 1, tooltip: { translate: 'construct.instance.option.layer.description' }})
.submitButton('§2Apply'); .submitButton({ translate: 'construct.menu.submit' });
} }
static buildMaterialList(instance, onlyMissing = false, player = false) { static buildMaterialList(instance, onlyMissing = false, player = false) {
@@ -59,25 +64,25 @@ export class InstanceFormBuilder {
if (onlyMissing) { if (onlyMissing) {
const inventoryContainer = player?.getComponent(EntityComponentTypes.Inventory)?.container; const inventoryContainer = player?.getComponent(EntityComponentTypes.Inventory)?.container;
if (!inventoryContainer) { if (!inventoryContainer) {
form.body('§cNo player inventory found.'); form.body({ translate: 'construct.instance.materials.noinventory' });
return form; return form;
} }
bodyText.rawtext.push({ text: `§cMaterials Missing From Inventory:` }); bodyText.rawtext.push({ translate: 'construct.instance.materials.missing.header' });
if (instance.hasLayerSelected()) if (instance.hasLayerSelected())
bodyText.rawtext.push({ text: ` §7(layer ${instance.getLayer()})` }); bodyText.rawtext.push({ translate: 'construct.instance.materials.layer', with: [String(instance.getLayer())] });
bodyText.rawtext.push({ text: `§f\n\n` }); bodyText.rawtext.push({ text: `§f\n\n` });
bodyText.rawtext.push(materials.formatString(materials.getMaterialsDifference(inventoryContainer))); bodyText.rawtext.push(materials.formatString(materials.getMaterialsDifference(inventoryContainer)));
buttonText = "Show All Materials"; buttonText = "construct.instance.materials.all.button";
} else { } else {
bodyText.rawtext.push({ text: `§aAll Materials:` }); bodyText.rawtext.push({ translate: `construct.instance.materials.all.header` });
if (instance.hasLayerSelected()) if (instance.hasLayerSelected())
bodyText.rawtext.push({ text: ` §7(layer ${instance.getLayer()})` }); bodyText.rawtext.push({ translate: 'construct.instance.materials.layer', with: [String(instance.getLayer())] });
bodyText.rawtext.push({ text: `§f\n\n` }); bodyText.rawtext.push({ text: `§f\n\n` });
bodyText.rawtext.push(materials.formatString()); bodyText.rawtext.push(materials.formatString());
buttonText = "Show Missing Materials"; buttonText = "construct.instance.materials.missing.button";
} }
form.body(bodyText); form.body(bodyText);
form.button(buttonText); form.button({ translate: buttonText });
return form; return form;
} }
} }
@@ -17,13 +17,13 @@ export class MaterialGrabberForm {
const selectedInstanceName = structureCollection.getInstanceNames()[response.selection]; const selectedInstanceName = structureCollection.getInstanceNames()[response.selection];
if (selectedInstanceName) { if (selectedInstanceName) {
this.setActiveInstance(selectedInstanceName); this.setActiveInstance(selectedInstanceName);
this.player.sendMessage(`§7Selected instance for material grabber: §2${selectedInstanceName}`); this.player.sendMessage({ translate: 'construct.materials.grabber.menu.success', with: [selectedInstanceName] });
return; return;
} }
}); });
} catch (e) { } catch (e) {
if (e.message === 'Menu timed out.') { if (e.message === 'Menu timed out.') {
this.player.sendMessage('§8Menu timed out.'); this.player.sendMessage({ translate: 'construct.menu.open.timeout' });
return; return;
} }
throw e; throw e;
@@ -4,18 +4,19 @@ import { structureCollection } from "../Structure/StructureCollection";
import { Builders } from "../Builder/Builders"; import { Builders } from "../Builder/Builders";
export class MaterialGrabberFormBuilder { export class MaterialGrabberFormBuilder {
static menuTitle = MenuFormBuilder.menuTitle + ' Material Grabber'; static menuTitle = { rawtext: [MenuFormBuilder.menuTitle, { translate: 'construct.materials.grabber.menu.title' }] };
static buildInstanceSelector(player) { static buildInstanceSelector(player) {
const allInstanceNameForm = new ActionFormData() const allInstanceNameForm = new ActionFormData()
.title(this.menuTitle); .title(this.menuTitle);
const currInstanceName = Builders.get(player.id).materialInstanceName; const currInstanceName = Builders.get(player.id).materialInstanceName;
let body = '§7Current instance: '; const body = { rawtext: [{ translate: 'construct.materials.grabber.menu.header' }] };
if (currInstanceName) if (currInstanceName)
body += `§2${currInstanceName}`; body.rawtext.push({ text: `§2${currInstanceName}` });
else else
body += '§7None'; body.rawtext.push({ translate: 'construct.materials.grabber.menu.noinstance' });
body += '\n§7Select an instance:'; body.rawtext.push({ text: '\n' });
body.rawtext.push({ translate: 'construct.materials.grabber.menu.selectinstance' });
allInstanceNameForm.body(body); allInstanceNameForm.body(body);
structureCollection.getInstanceNames().forEach(instanceName => { structureCollection.getInstanceNames().forEach(instanceName => {
allInstanceNameForm.button(`§2${instanceName}`); allInstanceNameForm.button(`§2${instanceName}`);
+3 -3
View File
@@ -42,7 +42,7 @@ export class MenuForm {
}); });
} catch (e) { } catch (e) {
if (e.message === 'Menu timed out.') { if (e.message === 'Menu timed out.') {
this.player.sendMessage('§8Menu timed out.'); this.player.sendMessage({ translate: 'construct.menu.open.timeout' });
return; return;
} }
throw e; throw e;
@@ -63,11 +63,11 @@ export class MenuForm {
structureCollection.add(instanceName, structureId); structureCollection.add(instanceName, structureId);
} catch (e) { } catch (e) {
if (e.name === 'InvalidInstanceError') { if (e.name === 'InvalidInstanceError') {
this.player.sendMessage(`§cInstance '${instanceName}' already exists. Try again with a new name.`); this.player.sendMessage({ translate: 'construct.mainmenu.instance.exists', with: [instanceName] });
return void 0; return void 0;
} }
if (e.name === 'InvalidStructureError') { if (e.name === 'InvalidStructureError') {
this.player.sendMessage(`§cStructure ID '${structureId}' not found. If you're looking for a structure that you put in the structures folder, please restart your world and try again.`); this.player.sendMessage({ translate: 'construct.mainmenu.instance.notfound', with: [structureId] });
return void 0; return void 0;
} }
throw e; throw e;
+20 -18
View File
@@ -2,56 +2,58 @@ import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
import { structureCollection } from './Structure/StructureCollection'; import { structureCollection } from './Structure/StructureCollection';
export class MenuFormBuilder { export class MenuFormBuilder {
static menuTitle = '§l§2Construct'; static menuTitle = { translate: 'construct.mainmenu.title' };
static buildAllInstanceName() { static buildAllInstanceName() {
const allInstanceNameForm = new ActionFormData() const allInstanceNameForm = new ActionFormData()
.title(this.menuTitle) .title(this.menuTitle)
.body('Select an instance:'); .body({ translate: 'construct.mainmenu.selectinstance' });
allInstanceNameForm.button('Builder Settings'); allInstanceNameForm.button({ translate: 'construct.mainmenu.settings' });
structureCollection.getInstanceNames().forEach(instanceName => { structureCollection.getInstanceNames().forEach(instanceName => {
allInstanceNameForm.button(`${structureCollection.get(instanceName).isEnabled() ? '§2' : '§c'}${instanceName}`); allInstanceNameForm.button(`${structureCollection.get(instanceName).isEnabled() ? '§2' : '§c'}${instanceName}`);
}); });
allInstanceNameForm.button('Create New Instance'); allInstanceNameForm.button({ translate: 'construct.mainmenu.newinstance' });
return allInstanceNameForm; return allInstanceNameForm;
} }
static buildNewInstance() { static buildNewInstance() {
return new ModalFormData() return new ModalFormData()
.title(this.menuTitle) .title(this.menuTitle)
.textField('Enter a name for the new instance:', 'example_instance') .textField({ translate: 'construct.mainmenu.newinstance.prompt' }, { translate: 'construct.mainmenu.newinstance.placeholder' })
.submitButton('Submit'); .submitButton({ translate: 'construct.menu.submit' });
} }
static buildAllStructures() { static buildAllStructures() {
const allStructuresForm = new ActionFormData() const allStructuresForm = new ActionFormData()
.title(this.menuTitle) .title(this.menuTitle)
.body('Select a structure:'); .body({ translate: 'construct.mainmenu.selectstructure.header' });
structureCollection.getWorldStructureIds().forEach(structureId => { structureCollection.getWorldStructureIds().forEach(structureId => {
const structureName = structureId.replace('mystructure:', ''); const structureName = structureId.replace('mystructure:', '');
allStructuresForm.button(`§2${structureName}`); allStructuresForm.button(`§2${structureName}`);
}); });
allStructuresForm.button('Other'); allStructuresForm.button({ translate: 'construct.mainmenu.selectstructure.other' });
allStructuresForm.button('How to Add/Remove Structures'); allStructuresForm.button({ translate: 'construct.mainmenu.selectstructure.howto' });
return allStructuresForm; return allStructuresForm;
} }
static buildOtherStructure() { static buildOtherStructure() {
return new ModalFormData() return new ModalFormData()
.title(this.menuTitle) .title(this.menuTitle)
.textField('Enter the Structure ID:', 'example_structure') .textField({ translate: 'construct.mainmenu.selectstructure.other.prompt' }, { translate: 'construct.mainmenu.selectstructure.other.placeholder' })
.submitButton('Submit'); .submitButton({ translate: 'construct.menu.submit' });
} }
static buildHowTo() { static buildHowTo() {
let body = "§aHow to Add Structures:\n" const message = { rawtext: [
body += "§7- Save a structure using a §fstructure block§7 or the §f/structure§7 command.\n" { translate: 'construct.mainmenu.selectstructure.howto.add.header' }, { text: '\n' },
body += "§7§lOR§r\n" { translate: 'construct.mainmenu.selectstructure.howto.add.structureblock' }, { text: '\n' },
body += "§7- Add a §f.mcstructure§7 file to this pack's §fstructures folder§7. When selecting your structure, select the §fOther§7 option and then use the filename (without '.mcstructure') as the §fStructure ID§7. After its first use, it will be added to the list of structures."; { translate: 'construct.mainmenu.selectstructure.howto.add.or' }, { text: '\n' },
body += "\n\n§cHow to Remove Structures:\n" { translate: 'construct.mainmenu.selectstructure.howto.add.mcstructure' }, { text: '\n' },
body += "§7- Use the §f/structure delete§7 command to remove a structure from the world.\n" { text: '\n' }, { translate: 'construct.mainmenu.selectstructure.howto.remove.header' }, { text: '\n' },
{ translate: 'construct.mainmenu.selectstructure.howto.remove.body' }, { text: '\n' },
] };
return new ActionFormData() return new ActionFormData()
.title(this.menuTitle) .title(this.menuTitle)
.body(body); .body(message);
} }
} }
@@ -43,18 +43,22 @@ export class StructureStatistics {
} }
getMessage() { getMessage() {
let message = ''; const message = { rawtext: [] };
message += `§fStatistics for §a${this.instance.getName()}§f:`; message.rawtext.push({ translate: 'construct.structure.statistics.header', with: [this.instance.getName()] });
if (this.instance.hasLayerSelected()) if (this.instance.hasLayerSelected())
message += ` §7(layer ${this.instance.getLayer()})`; message.rawtext.push({ translate: 'construct.instance.materials.layer', with: [String(this.instance.getLayer())] });
message += `\n§7Blocks: §2${this.getNonAirBlocks()}\n`; message.rawtext.push({ rawtext: [
{ text: '\n' },
{ translate: 'construct.structure.statistics.blocks', with: [String(this.getNonAirBlocks())] },
{ text: '\n' }
]});
const skipped = this.getSkipped(); const skipped = this.getSkipped();
if (skipped > 0) if (skipped > 0)
message += `§c[!] This analysis skipped ${skipped} blocks.\n`; message.rawtext.push({ rawtext: [{ translate: 'construct.structure.statistics.skipped', with: [String(skipped)] }, { text: '\n' }] });
message += `§7Correct: §a${this.formatStat(this.getStat(BlockVerificationLevel.Match))}\n`; message.rawtext.push({ rawtext: [{ translate: 'construct.structure.statistics.correct', with: [this.formatStat(this.getStat(BlockVerificationLevel.Match))] }, { text: '\n' }] });
message += `§7Block State Incorrect: §e${this.formatStat(this.getStat(BlockVerificationLevel.TypeMatch))}\n`; message.rawtext.push({ rawtext: [{ translate: 'construct.structure.statistics.stateincorrect', with: [this.formatStat(this.getStat(BlockVerificationLevel.TypeMatch))] }, { text: '\n' }] });
message += `§7Incorrect: §c${this.formatStat(this.getStat(BlockVerificationLevel.NoMatch))}\n`; message.rawtext.push({ rawtext: [{ translate: 'construct.structure.statistics.incorrect', with: [this.formatStat(this.getStat(BlockVerificationLevel.NoMatch))] }, { text: '\n' }] });
message += `§7Missing: §3${this.formatStat(this.getStat(BlockVerificationLevel.Missing))}\n`; message.rawtext.push({ rawtext: [{ translate: 'construct.structure.statistics.missing', with: [this.formatStat(this.getStat(BlockVerificationLevel.Missing))] }, { text: '\n' }] });
return message; return message;
} }
+5 -5
View File
@@ -9,7 +9,7 @@ export const MENU_ITEM = 'construct:menu';
const menuCmd = new Command({ const menuCmd = new Command({
name: 'construct', name: 'construct',
description: { text: 'Opens the Construct menu.' }, description: { translate: 'construct.commands.construct' },
usage: 'construct', usage: 'construct',
callback: (sender) => openMenu(sender) callback: (sender) => openMenu(sender)
}); });
@@ -18,7 +18,7 @@ extension.addCommand(menuCmd);
system.beforeEvents.startup.subscribe((event) => { system.beforeEvents.startup.subscribe((event) => {
const command = { const command = {
name: 'construct:item', name: 'construct:item',
description: 'Gives you the Construct item. Use it to open the Construct menu.', description: 'construct.commands.item',
permissionLevel: CommandPermissionLevel.Any, permissionLevel: CommandPermissionLevel.Any,
cheatsRequired: false cheatsRequired: false
}; };
@@ -28,13 +28,13 @@ system.beforeEvents.startup.subscribe((event) => {
function givePlayerConstructItem(origin) { function givePlayerConstructItem(origin) {
const player = origin.sourceEntity; const player = origin.sourceEntity;
if (player instanceof Player === false) if (player instanceof Player === false)
return { status: CustomCommandStatus.Failure, message: 'This command can only be used by players.' }; return { status: CustomCommandStatus.Failure, message: 'construct.commands.item.denyorigin' };
system.run(() => { system.run(() => {
const givenItemStack = player.getComponent(EntityComponentTypes.Inventory)?.container?.addItem(new ItemStack(MENU_ITEM)); const givenItemStack = player.getComponent(EntityComponentTypes.Inventory)?.container?.addItem(new ItemStack(MENU_ITEM));
if (givenItemStack) if (givenItemStack)
player.sendMessage('§cFailed to give you the Construct item.'); player.sendMessage({ translate: 'construct.commands.item.fail' });
else else
player.sendMessage('§aYou recieved the Construct item! Use it to open the Construct menu.'); player.sendMessage({ translate: 'construct.commands.item.success' });
}); });
return { status: CustomCommandStatus.Success }; return { status: CustomCommandStatus.Success };
} }
+1 -1
View File
@@ -4,5 +4,5 @@ export const extension = new CanopyExtension({
author: 'ForestOfLight', author: 'ForestOfLight',
name: 'Construct', name: 'Construct',
description: 'Survival building addon by §aForestOfLight§r.', description: 'Survival building addon by §aForestOfLight§r.',
version: '1.0.5' version: '1.0.6'
}); });
+4 -2
View File
@@ -26,7 +26,9 @@ export const bannedToValidBlockMap = {
'lit_blast_furnace': 'blast_furnace', 'lit_blast_furnace': 'blast_furnace',
'lit_redstone_ore': 'redstone_ore', 'lit_redstone_ore': 'redstone_ore',
'lit_redstone_lamp': 'redstone_lamp', 'lit_redstone_lamp': 'redstone_lamp',
'unlit_redstone_torch': 'redstone_torch' 'unlit_redstone_torch': 'redstone_torch',
'powered_comparator': 'unpowered_comparator',
'powered_repeater': 'unpowered_repeater'
}; };
export const resetToBlockStates = { export const resetToBlockStates = {
@@ -54,4 +56,4 @@ export const blockIdToItemStackMap = {
export const specialItemPlacementConversions = { export const specialItemPlacementConversions = {
'water_bucket': 'bucket', 'water_bucket': 'bucket',
'lava_bucket': 'bucket' 'lava_bucket': 'bucket'
} };
+7 -6
View File
@@ -1,16 +1,15 @@
import { BuilderOption } from '../classes/Builder/BuilderOption'; import { BuilderOption } from '../classes/Builder/BuilderOption';
import { BlockPermutation, EntityComponentTypes, EquipmentSlot, GameMode, ItemStack, system, world } from '@minecraft/server'; import { BlockPermutation, EntityComponentTypes, EquipmentSlot, GameMode, ItemStack, system, world } from '@minecraft/server';
import { structureCollection } from '../classes/Structure/StructureCollection'; import { structureCollection } from '../classes/Structure/StructureCollection';
import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, blockIdToItemStackMap } from '../data';
blockIdToItemStackMap } from '../data';
import { fetchMatchingItemSlot, placeBlock } from '../utils'; import { fetchMatchingItemSlot, placeBlock } from '../utils';
import { Builders } from '../classes/Builder/Builders'; import { Builders } from '../classes/Builder/Builders';
const builderOption = new BuilderOption({ const builderOption = new BuilderOption({
identifier: 'easyPlace', identifier: 'easyPlace',
displayName: 'Easy Place', displayName: { translate: 'construct.option.easyplace.name' },
description: 'Always place the correct structure block.', description: { translate: 'construct.option.easyplace.description' },
howToUse: "Hold the Easy Place item in your offhand and place blocks in a structure to have them be corrected automatically.", howToUse: { translate: 'construct.option.easyplace.howto' },
onEnableCallback: (playerId) => giveActionItem(playerId), onEnableCallback: (playerId) => giveActionItem(playerId),
onDisableCallback: (playerId) => removeActionItem(playerId) onDisableCallback: (playerId) => removeActionItem(playerId)
}); });
@@ -83,7 +82,7 @@ function shouldPreventAction(player, structureBlock) {
function preventAction(event, player) { function preventAction(event, player) {
event.cancel = true; event.cancel = true;
system.run(() => { system.run(() => {
player.onScreenDisplay.setActionBar('§cAction prevented by Easy Place.'); player.onScreenDisplay.setActionBar({ translate: 'construct.option.easyplace.actionprevented' });
}); });
} }
@@ -107,6 +106,8 @@ function tryConvertBannedToValidBlock(structureBlock) {
const blockId = structureBlock.type.id.replace('minecraft:', ''); const blockId = structureBlock.type.id.replace('minecraft:', '');
if (Object.keys(bannedToValidBlockMap).includes(blockId)) if (Object.keys(bannedToValidBlockMap).includes(blockId))
return BlockPermutation.resolve(bannedToValidBlockMap[blockId], structureBlock.getAllStates()); return BlockPermutation.resolve(bannedToValidBlockMap[blockId], structureBlock.getAllStates());
if (blockId === "bubble_column" && structureBlock.isWaterlogged)
return BlockPermutation.resolve('minecraft:water');
return structureBlock; return structureBlock;
} }
+37 -20
View File
@@ -8,13 +8,14 @@ import { Builders } from '../classes/Builder/Builders';
import { Vector } from '../lib/Vector'; import { Vector } from '../lib/Vector';
const locationsPlacedLastTick = new Set(); const locationsPlacedLastTick = new Set();
const PLAYER_COLLISION_BOX = { width: 0.6, height: 1.8 }; const ACTION_ITEM = 'construct:easy_place';
const runnerByPlayer = {};
const builderOption = new BuilderOption({ const builderOption = new BuilderOption({
identifier: 'fastEasyPlace', identifier: 'fastEasyPlace',
displayName: 'Fast Easy Place', displayName: { translate: 'construct.option.fasteasyplace.name' },
description: 'Place correct structure blocks just by looking at them.', description: { translate: 'construct.option.fasteasyplace.description' },
howToUse: "Hold the Easy Place item in your main hand and look at blocks in a structure to place them.", howToUse: { translate: 'construct.option.fasteasyplace.howto' },
onEnableCallback: (playerId) => giveActionItem(playerId), onEnableCallback: (playerId) => giveActionItem(playerId),
onDisableCallback: (playerId) => removeActionItem(playerId) onDisableCallback: (playerId) => removeActionItem(playerId)
}); });
@@ -22,9 +23,9 @@ const builderOption = new BuilderOption({
function giveActionItem(playerId) { function giveActionItem(playerId) {
const player = world.getEntity(playerId); const player = world.getEntity(playerId);
const container = player.getComponent(EntityComponentTypes.Inventory)?.container; const container = player.getComponent(EntityComponentTypes.Inventory)?.container;
const itemStack = new ItemStack('construct:easy_place'); const itemStack = new ItemStack(ACTION_ITEM);
const offhandItemStack = player.getComponent(EntityComponentTypes.Equippable).getEquipment(EquipmentSlot.Offhand); const offhandItemStack = player.getComponent(EntityComponentTypes.Equippable).getEquipment(EquipmentSlot.Offhand);
if (!container.contains(itemStack) && offhandItemStack?.typeId !== 'construct:easy_place') { if (!container.contains(itemStack) && offhandItemStack?.typeId !== ACTION_ITEM) {
const remainingItemStack = container.addItem(itemStack); const remainingItemStack = container.addItem(itemStack);
if (remainingItemStack) if (remainingItemStack)
player.dimension.spawnItem(remainingItemStack, player.location); player.dimension.spawnItem(remainingItemStack, player.location);
@@ -39,24 +40,38 @@ function removeActionItem(playerId) {
const container = player.getComponent(EntityComponentTypes.Inventory)?.container; const container = player.getComponent(EntityComponentTypes.Inventory)?.container;
for (let i = 0; i < container.size; i++) { for (let i = 0; i < container.size; i++) {
const itemStack = container.getItem(i); const itemStack = container.getItem(i);
if (itemStack?.typeId === 'construct:easy_place') if (itemStack?.typeId === ACTION_ITEM)
container.setItem(i, void 0); container.setItem(i, void 0);
} }
const equipment = player.getComponent(EntityComponentTypes.Equippable); const equipment = player.getComponent(EntityComponentTypes.Equippable);
const offhandItemStack = equipment?.getEquipment(EquipmentSlot.Offhand); const offhandItemStack = equipment?.getEquipment(EquipmentSlot.Offhand);
if (offhandItemStack?.typeId === 'construct:easy_place') { if (offhandItemStack?.typeId === ACTION_ITEM) {
equipment.setEquipment(EquipmentSlot.Offhand, void 0); equipment.setEquipment(EquipmentSlot.Offhand, void 0);
} }
} }
system.runInterval(onTick); system.runInterval(onPlacingTick);
world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteractWithBlock); world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteractWithBlock);
world.afterEvents.itemStartUse.subscribe(onItemStartUse);
world.afterEvents.itemStopUse.subscribe(onItemStopUse);
function onTick() { function onItemStartUse(event) {
for (const player of world.getAllPlayers()) { if (event.itemStack?.typeId !== ACTION_ITEM)
return;
runnerByPlayer[event.source.id] = system.runInterval((() => onPlacingTick(event.source)));
}
function onItemStopUse(event) {
if (event.itemStack?.typeId !== ACTION_ITEM)
return;
const playerRunnerId = runnerByPlayer[event.source.id];
if (playerRunnerId)
system.clearRun(playerRunnerId);
}
function onPlacingTick(player) {
if (player && builderOption.isEnabled(player.id)) if (player && builderOption.isEnabled(player.id))
processEasyPlace(player); processEasyPlace(player);
}
} }
function onPlayerInteractWithBlock(event) { function onPlayerInteractWithBlock(event) {
@@ -83,7 +98,7 @@ function processEasyPlace(player) {
function preventAction(event, player) { function preventAction(event, player) {
event.cancel = true; event.cancel = true;
system.run(() => { system.run(() => {
player.onScreenDisplay.setActionBar('§cAction prevented by Easy Place.'); player.onScreenDisplay.setActionBar({ translate: 'construct.option.easyplace.actionprevented' });
}); });
} }
@@ -91,7 +106,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 === 'construct:easy_place'; return mainhandItemStack.typeId === ACTION_ITEM;
} }
function tryPlaceBlock(player, worldBlock, structureBlock) { function tryPlaceBlock(player, worldBlock, structureBlock) {
@@ -131,6 +146,8 @@ function tryConvertBannedToValidBlock(structureBlock) {
const blockId = structureBlock.type.id.replace('minecraft:', ''); const blockId = structureBlock.type.id.replace('minecraft:', '');
if (Object.keys(bannedToValidBlockMap).includes(blockId)) if (Object.keys(bannedToValidBlockMap).includes(blockId))
return BlockPermutation.resolve(bannedToValidBlockMap[blockId], structureBlock.getAllStates()); return BlockPermutation.resolve(bannedToValidBlockMap[blockId], structureBlock.getAllStates());
if (blockId === "bubble_column" && structureBlock.isWaterlogged)
return BlockPermutation.resolve('minecraft:water');
return structureBlock; return structureBlock;
} }
@@ -159,11 +176,11 @@ function getPlaceableItemStack(structureBlock) {
} }
function isBlockInsidePlayer(player, worldBlock) { function isBlockInsidePlayer(player, worldBlock) {
const playerLocation = Vector.from(player.location); const playerAABB = player.getAABB();
const blockLocation = Vector.from(worldBlock.location); const playerCenter = Vector.from(playerAABB.center);
const playerMin = playerLocation.subtract({ x: PLAYER_COLLISION_BOX.width / 2, y: -0.1, z: PLAYER_COLLISION_BOX.width / 2 }); const playerMin = playerCenter.subtract({ x: playerAABB.extent.x, y: playerAABB.extent.y - 0.001, z: playerAABB.extent.z });
const playerMax = playerLocation.add({ x: PLAYER_COLLISION_BOX.width / 2, y: PLAYER_COLLISION_BOX.height, z: PLAYER_COLLISION_BOX.width / 2 }); const playerMax = playerCenter.add(playerAABB.extent);
const blockMin = blockLocation; const blockMin = Vector.from(worldBlock.location);
const blockMax = blockLocation.add({ x: 1, y: 1, z: 1 }); const blockMax = blockMin.add({ x: 1, y: 1, z: 1 });
return Vector.intersect(playerMax, playerMin, blockMax, blockMin); return Vector.intersect(playerMax, playerMin, blockMax, blockMin);
} }
+20 -11
View File
@@ -6,9 +6,9 @@ import { structureCollection } from '../classes/Structure/StructureCollection';
const builderOption = new BuilderOption({ const builderOption = new BuilderOption({
identifier: 'materialGrabber', identifier: 'materialGrabber',
displayName: 'Material Grabber', displayName: { translate: 'construct.option.materialgrabber.name' },
description: 'Pulls structure items from inventories.', description: { translate: 'construct.option.materialgrabber.description' },
howToUse: "Interact with inventories using the Material Grabber item to pull structure items from them.", howToUse: { translate: 'construct.option.materialgrabber.howto' },
onEnableCallback: (playerId) => giveActionItem(playerId), onEnableCallback: (playerId) => giveActionItem(playerId),
onDisableCallback: (playerId) => removeActionItem(playerId) onDisableCallback: (playerId) => removeActionItem(playerId)
}); });
@@ -43,6 +43,8 @@ world.beforeEvents.itemUse.subscribe(onItemUse);
world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract); world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract);
world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract); world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract);
const BANNED_ITEMTYPES = [/shulker_box/g];
function onItemUse(event) { function onItemUse(event) {
if (!isActionItem(event.itemStack) || !builderOption.isEnabled(event.source?.id)) if (!isActionItem(event.itemStack) || !builderOption.isEnabled(event.source?.id))
return; return;
@@ -113,17 +115,16 @@ function ignoreAlreadyGathered(materials, playerContainer) {
} }
function sendTransferMessage(player, transferCount) { function sendTransferMessage(player, transferCount) {
if (transferCount === 0) { if (transferCount === 0)
player.onScreenDisplay.setActionBar('§7Grabbed 0 items.'); player.onScreenDisplay.setActionBar({ translate: 'construct.option.materialgrabber.grabbed.zero' });
} else if (transferCount === 1) { else if (transferCount === 1)
player.onScreenDisplay.setActionBar('§aGrabbed 1 item.'); player.onScreenDisplay.setActionBar({ translate: 'construct.option.materialgrabber.grabbed.one' });
} else { else
player.onScreenDisplay.setActionBar(`§aGrabbed ${transferCount} item(s).`); player.onScreenDisplay.setActionBar({ translate: 'construct.option.materialgrabber.grabbed.many', with: [String(transferCount)] });
}
} }
function tryTransferToPlayer(slot, playerContainer, materials) { function tryTransferToPlayer(slot, playerContainer, materials) {
if (slot.hasItem() && materials.has(slot.typeId)) { if (slot.hasItem() && materials.has(slot.typeId) && !isBannedItemType(slot.typeId)) {
const grabAmount = Math.min(slot.amount, materials.get(slot.typeId).count); const grabAmount = Math.min(slot.amount, materials.get(slot.typeId).count);
if (grabAmount > 0) if (grabAmount > 0)
return tryTransferAmountToPlayer(slot, playerContainer, materials, grabAmount); return tryTransferAmountToPlayer(slot, playerContainer, materials, grabAmount);
@@ -207,3 +208,11 @@ function emptySlotPass(inventory, itemStack) {
function isSlotAvailableForStacking(slot, itemStack) { function isSlotAvailableForStacking(slot, itemStack) {
return slot.hasItem() && slot.isStackableWith(itemStack) && slot.amount !== slot.maxAmount; return slot.hasItem() && slot.isStackableWith(itemStack) && slot.amount !== slot.maxAmount;
} }
function isBannedItemType(itemTypeId) {
for (const regex of BANNED_ITEMTYPES) {
if (itemTypeId.match(regex))
return true;
}
return false;
}
+2 -2
View File
@@ -1,4 +1,4 @@
import { system, EntityComponentTypes, LiquidType } from '@minecraft/server'; import { system, EntityComponentTypes, LiquidType, ItemStack } from '@minecraft/server';
import { FormCancelationReason } from '@minecraft/server-ui'; import { FormCancelationReason } from '@minecraft/server-ui';
import { specialItemPlacementConversions } from './data'; import { specialItemPlacementConversions } from './data';
import { blocks, block_sounds } from './blocks'; import { blocks, block_sounds } from './blocks';
@@ -8,7 +8,7 @@ export async function forceShow(player, form, timeout = Infinity) {
while ((system.currentTick - startTick) < timeout) { while ((system.currentTick - startTick) < timeout) {
const response = await form.show(player); const response = await form.show(player);
if (startTick + 1 === system.currentTick && response.cancelationReason === FormCancelationReason.UserBusy) if (startTick + 1 === system.currentTick && response.cancelationReason === FormCancelationReason.UserBusy)
player.sendMessage("§8Close your chat window to access the menu."); player.sendMessage({ translate: 'construct.menu.open.closechat' });
if (response.cancelationReason !== FormCancelationReason.UserBusy) if (response.cancelationReason !== FormCancelationReason.UserBusy)
return response; return response;
} }
+3 -3
View File
@@ -1,10 +1,10 @@
{ {
"format_version": 2, "format_version": 2,
"header": { "header": {
"name": "Construct [RP] v1.0.5", "name": "Construct [RP] v1.0.6",
"description": "Survival building addon by §aForestOfLight§r.", "description": "Survival building addon by §aForestOfLight§r.",
"uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", "uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4",
"version": [1, 0, 5], "version": [1, 0, 6],
"min_engine_version": [1,17,1] "min_engine_version": [1,17,1]
}, },
"modules": [ "modules": [
@@ -17,7 +17,7 @@
"dependencies": [ "dependencies": [
{ {
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", // Construct BP "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", // Construct BP
"version": [1, 0, 5] "version": [1, 0, 6]
} }
], ],
"metadata": { "metadata": {
+109
View File
@@ -0,0 +1,109 @@
## Items
construct.menu.name=Construct
construct.easyplace.name=Easy Place
construct.materialgrabber.name=Material Grabber
## Menus
construct.menu.open.closechat=§8Close your chat window to access the menu.
construct.menu.open.timeout=§8Menu timed out.
construct.menu.submit=§2Apply
construct.menu.instance.button.unknown=Unknown
construct.menu.instance.button.place=§aPlace Instance
construct.menu.instance.button.enable=§aEnable Instance
construct.menu.instance.button.disable=§cDisable Instance
construct.menu.instance.button.rename=Rename Instance
construct.menu.instance.button.delete=§cDelete Instance
construct.menu.instance.button.nextLayer=Increase Layer
construct.menu.instance.button.previousLayer=Decrease Layer
construct.menu.instance.button.move=Move Here
construct.menu.instance.button.flexibleMove=Flexible Move
construct.menu.instance.button.statistics=Statistics
construct.menu.instance.button.settings=Settings
construct.menu.instance.button.materials=Material List
construct.menu.instance.unknownOption=§cUnknown option: %s
construct.menu.instance.nameEmpty=§cInstance name cannot be empty.
construct.instance.flexibleMove.alreadyMoving=§cThis instance is already being moved.
construct.instance.flexibleMove.start=§aNow moving "%s". Use the Construct item when finished. ## Insert string: instance name
construct.instance.flexibleMove.finish=§aMoved "%1" to %2. ## Insert strings: instance name, coordinates
construct.instance.rename.error=§cError renaming instance: %s
construct.instance.validation.alreadyRunning=§cA verification is already in progress. Please wait until it finishes.
construct.instance.validation.formfail=§cFailed to build statistics form.
construct.instance.menu.body=Instance: §a%1\n§fStructure: §2%2 ## Insert strings: instance name, structure name
construct.instance.menu.location=§7(%1 %2 %3 in %4) ## Is added to the body when the instance has is placed. Insert strings: x, y, z coordinates, dimension
construct.isntance.menu.rename=Enter a new name for the instance:
construct.instance.option.validation=Block Validation
construct.instance.option.validation.description=Shows missing and incorrect block overlay.
construct.instance.option.layer=Layer
construct.instance.option.layer.description=Changes the active layer. Use 0 for all layers.
construct.instance.materials.noinventory=§cNo player inventory found.
construct.instance.materials.missing.header=§cMaterials Missing From Inventory:
construct.instance.materials.missing.button=Show Missing Materials
construct.instance.materials.layer= §7(layer %s) ## Is added to the header when the instance is in layer mode. Insert string: layer number
construct.instance.materials.all.header=§aAll Materials:
construct.instance.materials.all.button=Show All Materials
construct.materials.grabber.menu.title= Material Grabber
construct.materials.grabber.menu.success=§7Selected instance for material grabber: §2%s ## Insert string: instance name
construct.materials.grabber.menu.header=§7Current instance:
construct.materials.grabber.menu.noinstance=§7None
construct.materials.grabber.menu.selectinstance=§7Select an instance:
construct.structure.statistics.header=§fStatistics for §a%s§f: ## Insert string: instance name
construct.structure.statistics.blocks=§7Blocks: §2%s ## Insert string: number of non-air blocks in the structure
construct.structure.statistics.skipped=§c[!] This analysis skipped %s blocks. ## Insert string: number of blocks the analysis skipped
construct.structure.statistics.correct=§7Correct: §a%s ## Insert string: number of correctly placed blocks
construct.structure.statistics.stateincorrect=§7Block State Incorrect: §e%s ## Insert string: number of incorrectly stated blocks
construct.structure.statistics.incorrect=§7Incorrect: §c%s ## Insert string: number of incorrectly placed blocks
construct.structure.statistics.missing=§7Missing: §3%s ## Insert string: number of missing blocks
## Structure Block Info Display
construct.blockinfo.header=Structure:
construct.blockinfo.none=§7None
construct.blockinfo.nosupply= §c[No Supply] ## When the targeted block is not in the player's inventory
construct.blockinfo.unknown=§7Unknown
construct.blockinfo.waterlogged=§7isWaterlogged: §3true
construct.mainmenu.instance.exists=§cInstance '%s' already exists. Try again with a new name. ## Insert string: instance name
construct.mainmenu.instance.notfound=§cStructure ID '%s' not found. If you're looking for a structure that you put in the structures folder, please restart your world and try again. ## Insert string: structure name
construct.mainmenu.title=§l§2Construct ## This is the name of the pack.
construct.mainmenu.selectinstance=Select an instance:
construct.mainmenu.settings=Builder Settings
construct.mainmenu.newinstance=Create New Instance
construct.mainmenu.newinstance.prompt=Enter a name for the new instance:
construct.mainmenu.newinstance.placeholder=example_instance ## The placeholder text in the textbox where the user enters the instance name.
construct.mainmenu.selectstructure.header=Select a structure:
construct.mainmenu.selectstructure.other=Other
construct.mainmenu.selectstructure.howto=How to Add/Remove Structures
construct.mainmenu.selectstructure.other.prompt=Enter the Structure ID:
construct.mainmenu.selectstructure.other.placeholder=example_structure ## The placeholder text in the textbox where the user enters the structure name.
construct.mainmenu.selectstructure.howto.add.header=§aHow to Add Structures:
construct.mainmenu.selectstructure.howto.add.structureblock=§7- Save a structure using a §fstructure block§7 or the §f/structure§7 command.
construct.mainmenu.selectstructure.howto.add.or=§7§lOR§r
construct.mainmenu.selectstructure.howto.add.mcstructure=§7- Add a §f.mcstructure§7 file to this pack's §fstructures folder§7. When selecting your structure, select the §fOther§7 option and then use the filename (without '.mcstructure') as the §fStructure ID§7. After its first use, it will be added to the list of structures.
construct.mainmenu.selectstructure.howto.remove.header=§cHow to Remove Structures:
construct.mainmenu.selectstructure.howto.remove.body=§7- Use the §f/structure delete§7 command to remove a structure from the world.
## Commands
construct.commands.construct=Opens the Construct menu.
construct.commands.item=Gives you the Construct item. Use it to open the Construct menu.
construct.commands.item.denyorigin=This command can only be used by players.
construct.commands.item.fail=§cFailed to give you the Construct item.
construct.commands.item.success=§aYou recieved the Construct item! Use it to open the Construct menu.
## Options
construct.option.enabled= is now enabled!
construct.option.disabled= is now disabled.
construct.option.easyplace.name=Easy Place
construct.option.easyplace.description=Always place the correct structure block.
construct.option.easyplace.howto=Hold the Easy Place item in your offhand and place blocks in an instance to have them be corrected automatically.
construct.option.easyplace.actionprevented=§cAction prevented by Easy Place.
construct.option.fasteasyplace.name=Fast Easy Place
construct.option.fasteasyplace.description=Place correct structure blocks just by clicking on them.
construct.option.fasteasyplace.howto=Hold the Easy Place item in your main hand and use it on missing blocks in a structure to place them.
construct.option.materialgrabber.name=Material Grabber
construct.option.materialgrabber.description=Pulls structure items from inventories.
construct.option.materialgrabber.howto=Interact with inventories using the Material Grabber item to pull structure items from them.
construct.option.materialgrabber.grabbed.zero=§7Grabbed 0 items.
construct.option.materialgrabber.grabbed.one=§aGrabbed 1 item.
construct.option.materialgrabber.grabbed.many=§aGrabbed %s items. ## Insert string: number of items transferred to the player
+3
View File
@@ -0,0 +1,3 @@
[
"en_US"
]