Merge branch 'main' into easyPlace
This commit is contained in:
@@ -30,14 +30,14 @@ Removes a structure.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [ ] place structure **(in progress)**
|
||||
- [x] Form to manage structure
|
||||
- [x] Structure naming & movement
|
||||
- [x] easyPlace rule
|
||||
- [x] Make a viewable material list
|
||||
- [ ] Make automatic material gathering from inventories
|
||||
- [ ] structure movement & rotation
|
||||
- [ ] structuraMode & holoprintMode rules for ease of use
|
||||
- [ ] send structure as a scriptevent so that other addons can use it
|
||||
- [ ] more litematica features!
|
||||
- [ ] Automatic Armor stand posing
|
||||
- [ ] Automatic material gathering from inventories
|
||||
- [ ] Correct block placement checking
|
||||
- [ ] Structure Mirroring & Rotation
|
||||
- [ ] Structure Merging into SuperStructures
|
||||
|
||||
## Issues & Suggestions
|
||||
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@
|
||||
"name": "StrucTool",
|
||||
"description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.",
|
||||
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
|
||||
"min_engine_version": [1, 21, 60],
|
||||
"min_engine_version": [1, 21, 70],
|
||||
"version": [1, 0, 0]
|
||||
},
|
||||
"modules": [
|
||||
@@ -26,19 +26,19 @@
|
||||
"dependencies": [
|
||||
{
|
||||
"module_name": "@minecraft/server",
|
||||
"version": "1.18.0-beta"
|
||||
"version": "2.0.0-beta"
|
||||
},
|
||||
{
|
||||
"module_name": "@minecraft/server-ui",
|
||||
"version": "1.4.0-beta"
|
||||
"version": "2.0.0-beta"
|
||||
},
|
||||
{
|
||||
"uuid": "bcf34368-ed0c-4cf7-938e-582cccf9950d", // Canopy RP
|
||||
"version": [1, 0, 2]
|
||||
"version": [1, 0, 3]
|
||||
},
|
||||
{
|
||||
"uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0", // Canopy BP
|
||||
"version": [1, 3, 8]
|
||||
"version": [1, 3, 9]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -1,24 +1,42 @@
|
||||
import { system, world } from '@minecraft/server';
|
||||
import { Raycaster } from '../classes/Raycaster';
|
||||
|
||||
system.runInterval(() => {
|
||||
for (const player of world.getAllPlayers()) {
|
||||
if (!player)
|
||||
continue;
|
||||
showStructureBlockInfo(player);
|
||||
}
|
||||
});
|
||||
class BlockInfo {
|
||||
static shownToLastTick = new Set();
|
||||
|
||||
function showStructureBlockInfo(player) {
|
||||
const block = Raycaster.getTargetedStructureBlock(player, { isFirst: true, collideWithWorldBlocks: true });
|
||||
if (!block)
|
||||
return;
|
||||
player.onScreenDisplay.setActionBar({ text: getFormattedBlockInfo(block.permutation) });
|
||||
static onTick() {
|
||||
for (const player of world.getAllPlayers()) {
|
||||
if (!player)
|
||||
continue;
|
||||
this.showStructureBlockInfo(player);
|
||||
}
|
||||
}
|
||||
|
||||
static showStructureBlockInfo(player) {
|
||||
const block = Raycaster.getTargetedStructureBlock(player, { isFirst: true, collideWithWorldBlocks: true, useLayers: false });
|
||||
if (!block && this.shownToLastTick.has(player.id)) {
|
||||
player.onScreenDisplay.setActionBar({ text: 'Structure:\n§7None' });
|
||||
this.shownToLastTick.delete(player.id);
|
||||
}
|
||||
if (!block)
|
||||
return;
|
||||
player.onScreenDisplay.setActionBar({ text: this.getFormattedBlockInfo(block.permutation) });
|
||||
this.shownToLastTick.add(player.id);
|
||||
}
|
||||
|
||||
static getFormattedBlockInfo(block) {
|
||||
const header = 'Structure:\n'
|
||||
if (!block)
|
||||
return header + '§7Unknown';
|
||||
const states = block.getAllStates();
|
||||
if (Object.keys(states).length === 0)
|
||||
return header + `§a${block.type.id}`;
|
||||
return header + `§a${block.type.id}\n§7${this.getFormattedStates(states)}`;
|
||||
}
|
||||
|
||||
static getFormattedStates(states) {
|
||||
return Object.entries(states).map(([key, value]) => `§7${key}: §3${value}`).join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
function getFormattedBlockInfo(block) {
|
||||
const states = block.getAllStates();
|
||||
if (Object.keys(states).length === 0)
|
||||
return `Structure:\n§a${block.type.id}`;
|
||||
return `Structure:\n§a${block.type.id}\n§7${JSON.stringify(block.getAllStates())}`;
|
||||
}
|
||||
system.runInterval(() => BlockInfo.onTick());
|
||||
@@ -0,0 +1,130 @@
|
||||
import { structureCollection } from './StructureCollection';
|
||||
import { MenuForm } from '../classes/MenuForm';
|
||||
import { InstanceEditOptions } from './InstanceEditOptions';
|
||||
import { InstanceEditFormBuilder } from './InstanceEditFormBuilder';
|
||||
|
||||
export class InstanceEditForm {
|
||||
instanceName;
|
||||
#buttons = {
|
||||
isEnabled: [
|
||||
InstanceEditOptions.NextLayer,
|
||||
InstanceEditOptions.PreviousLayer,
|
||||
InstanceEditOptions.SetLayer,
|
||||
InstanceEditOptions.Move,
|
||||
InstanceEditOptions.RenameInstance,
|
||||
InstanceEditOptions.DisableInstance,
|
||||
],
|
||||
isNotEnabledAndIsNotPlaced: [
|
||||
InstanceEditOptions.PlaceInstance,
|
||||
InstanceEditOptions.RenameInstance
|
||||
],
|
||||
isNotEnabledButIsPlaced: [
|
||||
InstanceEditOptions.EnableInstance,
|
||||
InstanceEditOptions.RenameInstance
|
||||
],
|
||||
common: [
|
||||
InstanceEditOptions.DeleteInstance,
|
||||
InstanceEditOptions.MainMenu
|
||||
]
|
||||
}
|
||||
|
||||
constructor(player, instanceName) {
|
||||
this.player = player;
|
||||
this.instanceName = instanceName;
|
||||
this.instance = structureCollection.get(this.instanceName);
|
||||
this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
const currentOptions = this.getActiveOptions();
|
||||
InstanceEditFormBuilder.buildInstance(this.instance, currentOptions).show(this.player).then((response) => {
|
||||
if (response.canceled) return;
|
||||
this.handleOption(currentOptions[response.selection]);
|
||||
});
|
||||
}
|
||||
|
||||
getActiveOptions() {
|
||||
let currentOptions = [];
|
||||
if (this.instance.isEnabled())
|
||||
currentOptions = this.#buttons.isEnabled;
|
||||
else if (this.instance.hasLocation())
|
||||
currentOptions = this.#buttons.isNotEnabledButIsPlaced;
|
||||
else
|
||||
currentOptions = this.#buttons.isNotEnabledAndIsNotPlaced;
|
||||
currentOptions = currentOptions.concat(this.#buttons.common);
|
||||
|
||||
if (!this.instance.hasLayers())
|
||||
currentOptions = currentOptions.filter(option =>
|
||||
option !== InstanceEditOptions.SetLayer
|
||||
&& option !== InstanceEditOptions.NextLayer
|
||||
&& option !== InstanceEditOptions.PreviousLayer
|
||||
);
|
||||
return currentOptions;
|
||||
}
|
||||
|
||||
handleOption(option) {
|
||||
switch (option) {
|
||||
case InstanceEditOptions.EnableInstance:
|
||||
this.instance.enable();
|
||||
break;
|
||||
case InstanceEditOptions.DisableInstance:
|
||||
this.instance.disable();
|
||||
break;
|
||||
case InstanceEditOptions.PlaceInstance:
|
||||
this.instance.place(this.player.dimension.id, this.player.location);
|
||||
break;
|
||||
case InstanceEditOptions.RenameInstance:
|
||||
this.renameInstanceForm();
|
||||
break;
|
||||
case InstanceEditOptions.DeleteInstance:
|
||||
structureCollection.delete(this.instanceName);
|
||||
break;
|
||||
case InstanceEditOptions.NextLayer:
|
||||
this.instance.increaseLayer();
|
||||
new InstanceEditForm(this.player, this.instanceName);
|
||||
break;
|
||||
case InstanceEditOptions.PreviousLayer:
|
||||
this.instance.decreaseLayer();
|
||||
new InstanceEditForm(this.player, this.instanceName);
|
||||
break;
|
||||
case InstanceEditOptions.SetLayer:
|
||||
this.setLayerForm();
|
||||
break;
|
||||
case InstanceEditOptions.Move:
|
||||
this.instance.move(this.player.dimension.id, this.player.location);
|
||||
break;
|
||||
case InstanceEditOptions.MainMenu:
|
||||
new MenuForm(this.player, { jumpToInstance: false });
|
||||
break;
|
||||
default:
|
||||
this.player.sendMessage(`§cUnknown option: ${option}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
renameInstanceForm() {
|
||||
InstanceEditFormBuilder.buildRenameInstance(this.instanceName).show(this.player).then((response) => {
|
||||
if (response.canceled) return;
|
||||
const newName = response.formValues[0];
|
||||
if (newName === '') {
|
||||
this.player.sendMessage('§cInstance name cannot be empty.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
structureCollection.rename(this.instanceName, newName);
|
||||
this.instanceName = newName;
|
||||
} catch (e) {
|
||||
this.player.sendMessage(`§cError renaming instance: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setLayerForm() {
|
||||
InstanceEditFormBuilder.buildSetLayer(this.instance.getBounds().max.y, this.instance.getLayer()).show(this.player).then((response) => {
|
||||
if (response.canceled) return;
|
||||
const selectedLayer = response.formValues[0];
|
||||
this.instance.setLayer(parseInt(selectedLayer));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
|
||||
import { MenuFormBuilder } from './MenuFormBuilder';
|
||||
|
||||
export class InstanceEditFormBuilder {
|
||||
static buildInstance(instance, options) {
|
||||
const location = instance.getLocation();
|
||||
const form = new ActionFormData()
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
let body = `Instance: §a${instance.name}\n§fStructure: §2${instance.getStructureId()}\n`;
|
||||
if (instance.hasLocation())
|
||||
body += `§7(${location.location.x} ${location.location.y} ${location.location.z} in ${location.dimensionId})\n`;
|
||||
form.body(body);
|
||||
options.forEach(option => {
|
||||
form.button(`${option}`);
|
||||
});
|
||||
return form;
|
||||
}
|
||||
|
||||
static buildRenameInstance(currentName) {
|
||||
return new ModalFormData()
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
.textField('Enter a new name for the instance:', currentName)
|
||||
.submitButton('Rename');
|
||||
}
|
||||
|
||||
static buildSetLayer(maxLayer, currentLayer) {
|
||||
return new ModalFormData()
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
.label('Use the slider to select the layer. Use 0 for all layers.')
|
||||
.slider("Layer", 0, maxLayer, 1, currentLayer)
|
||||
.submitButton('Set Layer');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export const InstanceEditOptions = Object.freeze({
|
||||
Unknown: 'Unknown',
|
||||
MainMenu: '<<',
|
||||
PlaceInstance: '§aPlace Instance',
|
||||
EnableInstance: '§aEnable Instance',
|
||||
DisableInstance: '§cDisable Instance',
|
||||
RenameInstance: 'Rename Instance',
|
||||
DeleteInstance: '§cDelete Instance',
|
||||
NextLayer: 'Increase Layer',
|
||||
PreviousLayer: 'Decrease Layer',
|
||||
SetLayer: 'Set Layer',
|
||||
Move: 'Move Here'
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { forceShow } from '../utils';
|
||||
import { structureCollection } from './StructureCollection';
|
||||
import { MenuFormBuilder } from './MenuFormBuilder';
|
||||
import { InstanceEditForm } from './InstanceEditForm';
|
||||
|
||||
export class MenuForm {
|
||||
constructor(player, { jumpToInstance = true } = {}) {
|
||||
this.player = player;
|
||||
this.show(jumpToInstance);
|
||||
}
|
||||
|
||||
async show(jumpToInstance = true) {
|
||||
let instanceName;
|
||||
if (jumpToInstance) {
|
||||
instanceName = structureCollection.getStructure(this.player.dimension.id, this.player.location, { useLayers: false })?.name;
|
||||
if (instanceName) {
|
||||
new InstanceEditForm(this.player, instanceName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
instanceName = await this.getInstanceNameFromForm();
|
||||
if (!instanceName)
|
||||
return;
|
||||
new InstanceEditForm(this.player, instanceName);
|
||||
}
|
||||
|
||||
async getInstanceNameFromForm() {
|
||||
try {
|
||||
return forceShow(this.player, MenuFormBuilder.buildAllInstanceName()).then((response) => {
|
||||
if (response.canceled) return;
|
||||
if (response.selection === structureCollection.getInstanceNames().length + 1) {
|
||||
MenuFormBuilder.buildHowToAddNewStructures().show(this.player);
|
||||
return;
|
||||
}
|
||||
const selectedInstanceName = structureCollection.getInstanceNames()[response.selection];
|
||||
return selectedInstanceName || this.createNewInstance();
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.message === 'Menu timed out.') {
|
||||
this.player.sendMessage('§8Menu timed out.');
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async createNewInstance() {
|
||||
return MenuFormBuilder.buildNewInstance().show(this.player).then(async (response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
const instanceName = response.formValues[0];
|
||||
if (instanceName === '')
|
||||
return void 0;
|
||||
const structureId = await this.getStructureId();
|
||||
if (!structureId)
|
||||
return;
|
||||
structureCollection.add(instanceName, structureId);
|
||||
return instanceName;
|
||||
});
|
||||
}
|
||||
|
||||
async getStructureId() {
|
||||
return MenuFormBuilder.buildAllStructures().show(this.player).then((response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
const selectedStructureId = structureCollection.getWorldStructureIds()[response.selection];
|
||||
return selectedStructureId || this.getOtherStructureId();
|
||||
});
|
||||
}
|
||||
|
||||
getOtherStructureId() {
|
||||
return MenuFormBuilder.buildOtherStructure().show(this.player).then((response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
const structureId = response.formValues[0];
|
||||
if (structureId === '')
|
||||
return void 0;
|
||||
return structureId;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
|
||||
import { structureCollection } from './StructureCollection';
|
||||
|
||||
export class MenuFormBuilder {
|
||||
static menuTitle = '§l§2StrucTool §8Menu';
|
||||
|
||||
static buildAllInstanceName() {
|
||||
const allInstanceNameForm = new ActionFormData()
|
||||
.title(this.menuTitle)
|
||||
.body('Select an instance:');
|
||||
structureCollection.getInstanceNames().forEach(instanceName => {
|
||||
allInstanceNameForm.button(`§2${instanceName}`);
|
||||
});
|
||||
allInstanceNameForm.button('Create New Instance');
|
||||
allInstanceNameForm.button('How to Add New Structures');
|
||||
return allInstanceNameForm;
|
||||
}
|
||||
|
||||
static buildNewInstance() {
|
||||
return new ModalFormData()
|
||||
.title(this.menuTitle)
|
||||
.textField('Enter a name for the new instance:', 'example_instance')
|
||||
.submitButton('Submit');
|
||||
}
|
||||
|
||||
static buildAllStructures() {
|
||||
const allStructuresForm = new ActionFormData()
|
||||
.title(this.menuTitle)
|
||||
.body('Select a structure:');
|
||||
structureCollection.getWorldStructureIds().forEach(structureId => {
|
||||
const structureName = structureId.replace('mystructure:', '');
|
||||
allStructuresForm.button(`§2${structureName}`);
|
||||
});
|
||||
allStructuresForm.button('Other');
|
||||
return allStructuresForm;
|
||||
}
|
||||
|
||||
static buildOtherStructure() {
|
||||
return new ModalFormData()
|
||||
.title(this.menuTitle)
|
||||
.textField('Enter the Structure ID:', 'example_structure')
|
||||
.submitButton('Submit');
|
||||
}
|
||||
|
||||
static buildHowToAddNewStructures() {
|
||||
let body = "How to Add Structures:\n"
|
||||
body += "§7- Save a structure using a structure block or the /structure command.\n"
|
||||
body += "§7OR\n"
|
||||
body += "§7- Add a .mcstructure file to this pack's structures folder. When selecting your structure, select the 'Other' option and then use the filename (without '.mcstructure') as the Structure ID. After its first use, it will be added to the list of structures.";
|
||||
return new ActionFormData()
|
||||
.title(this.menuTitle)
|
||||
.body(body);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,18 @@
|
||||
import { world } from "@minecraft/server";
|
||||
import { structureCollection } from "./StructureCollection";
|
||||
import { world } from "@minecraft/server";
|
||||
|
||||
export class Raycaster {
|
||||
static STEP_SIZE = 0.2;
|
||||
|
||||
static getStructureBlocks(dimension, startLocation, direction, { maxDistance, getFirst = true, collideWithWorldBlocks = true }) {
|
||||
static getStructureBlocks(dimension, startLocation, direction, { maxDistance = 7, getFirst = true, collideWithWorldBlocks = true, useLayers = true }) {
|
||||
// Can probably be optimized by the fact that we only need full blocks and aren't checking for partial blocks
|
||||
const blocks = [];
|
||||
let location = startLocation;
|
||||
let distance = 0;
|
||||
while (distance < maxDistance) {
|
||||
const locatedStructures = structureCollection.getStructuresAtLocation(location);
|
||||
if (locatedStructures.length !== 0) {
|
||||
const structure = locatedStructures[0];
|
||||
const structure = structureCollection.getStructure(dimension.id, location, { useLayers });
|
||||
if (structure) {
|
||||
const block = structure.getBlock(structure.toStructureCoords(location));
|
||||
if (collideWithWorldBlocks && !dimension.getBlock(location)?.isAir)
|
||||
break;
|
||||
if (block?.type.id !== 'minecraft:air') {
|
||||
blocks.push({
|
||||
permutation: block,
|
||||
@@ -24,6 +21,14 @@ export class Raycaster {
|
||||
if (getFirst)
|
||||
break;
|
||||
}
|
||||
try {
|
||||
if (collideWithWorldBlocks && !dimension.getBlock(location)?.isAir)
|
||||
break;
|
||||
} catch (e) {
|
||||
if (e.name === 'LocationOutOfWorldBoundariesError')
|
||||
break;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
location = {
|
||||
x: location.x + (direction.x*this.STEP_SIZE),
|
||||
@@ -36,11 +41,11 @@ export class Raycaster {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
static getTargetedStructureBlock(player, { isFirst = true, collideWithWorldBlocks = true }) {
|
||||
static getTargetedStructureBlock(player, { isFirst = true, collideWithWorldBlocks = true, useLayers = true } = {}) {
|
||||
const startLocation = player.getHeadLocation();
|
||||
const direction = player.getViewDirection();
|
||||
const maxDistance = 7;
|
||||
const blocks = this.getStructureBlocks(player.dimension, startLocation, direction, { maxDistance, getFirst: isFirst, collideWithWorldBlocks });
|
||||
const blocks = this.getStructureBlocks(player.dimension, startLocation, direction, { maxDistance, getFirst: isFirst, collideWithWorldBlocks, useLayers });
|
||||
if (blocks.length === 0)
|
||||
return void 0;
|
||||
return isFirst ? blocks[0] : blocks[blocks.length - 1];
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
import { MinecraftDimensionTypes, world } from "@minecraft/server";
|
||||
import { Outliner } from "./Outliner";
|
||||
|
||||
export class Structure {
|
||||
#structure;
|
||||
#options = {
|
||||
isPlaced: false,
|
||||
dimensionId: MinecraftDimensionTypes.overworld,
|
||||
worldLocation: { x: 0, y: 0, z: 0 },
|
||||
rotation: 0,
|
||||
mirror: false,
|
||||
currentLayer: 0
|
||||
};
|
||||
|
||||
constructor(structureName) {
|
||||
this.name = structureName;
|
||||
this.#structure = world.structureManager.get(structureName);
|
||||
if (!this.#structure) {
|
||||
throw new Error(`[StrucTool] Structure '${structureName}' not found.`);
|
||||
}
|
||||
this.#options = this.loadOptions();
|
||||
}
|
||||
|
||||
loadOptions() {
|
||||
try {
|
||||
return JSON.parse(world.getDynamicProperty(`structOptions:${this.name}`));
|
||||
} catch (e) {
|
||||
world.setDynamicProperty(`structOptions:${this.name}`, JSON.stringify(this.#options));
|
||||
}
|
||||
return this.#options;
|
||||
}
|
||||
|
||||
updateOptions() {
|
||||
world.setDynamicProperty(`structOptions:${this.name}`, JSON.stringify(this.#options));
|
||||
}
|
||||
|
||||
getStructure() {
|
||||
return this.#structure;
|
||||
}
|
||||
|
||||
getLocation() {
|
||||
return this.#options.worldLocation;
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return this.#structure.size.y;
|
||||
}
|
||||
|
||||
getLayer() {
|
||||
return this.#options.currentLayer;
|
||||
}
|
||||
|
||||
*getBlocks() {
|
||||
const max = this.#structure.size;
|
||||
for (let x = 0; x < max.x; x++) {
|
||||
for (let y = 0; y < max.y; y++) {
|
||||
for (let z = 0; z < max.z; z++) {
|
||||
yield this.#structure.getBlockPermutation({ x, y, z });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*getLayerBlocks(y) {
|
||||
const max = this.#structure.size;
|
||||
for (let x = 0; x < max.x; x++) {
|
||||
for (let z = 0; z < max.z; z++) {
|
||||
yield this.#structure.getBlockPermutation({ x, y, z });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getBlock(structureLocation) {
|
||||
return this.#structure.getBlockPermutation(structureLocation);
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
return {
|
||||
min: { x: 0, y: 0, z: 0 },
|
||||
max: this.#structure.size
|
||||
};
|
||||
}
|
||||
|
||||
getLayeredBounds() {
|
||||
if (!this.#options.isPlaced)
|
||||
throw new Error(`[StrucTool] Structure '${this.name}' is not placed.`);
|
||||
return {
|
||||
min: { x: 0, y: this.#options.currentLayer - 1, z: 0 },
|
||||
max: { x: this.#structure.size.x, y: this.#options.currentLayer, z: this.#structure.size.z }
|
||||
};
|
||||
}
|
||||
|
||||
place(dimensionId, worldLocation) {
|
||||
this.#options = {
|
||||
isPlaced: true,
|
||||
dimensionId,
|
||||
worldLocation: { x: Math.floor(worldLocation.x), y: Math.floor(worldLocation.y), z: Math.floor(worldLocation.z) },
|
||||
};
|
||||
this.updateOptions();
|
||||
this.outliner = new Outliner(dimensionId, this.toGlobalCoords(this.getBounds().min), this.toGlobalCoords(this.getBounds().max));
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (!this.#options.isPlaced)
|
||||
throw new Error(`[StrucTool] Structure '${this.name}' is not placed.`);
|
||||
this.#options.isPlaced = false;
|
||||
this.updateOptions();
|
||||
this.outliner.stopDraw();
|
||||
}
|
||||
|
||||
setLayer(layer) {
|
||||
if (layer < 1 || layer > this.#structure.size.y)
|
||||
throw new Error(`[StrucTool] Structure '${this.name}' does not have layer ${layer}.`);
|
||||
this.#options.currentLayer = layer;
|
||||
this.updateOptions();
|
||||
this.outliner.stopDraw();
|
||||
const { min, max } = this.getLayeredBounds();
|
||||
this.outliner = new Outliner(this.#options.dimensionId, this.toGlobalCoords(min), this.toGlobalCoords(max));
|
||||
}
|
||||
|
||||
isLocationInStructure(structureLocation) {
|
||||
const { min, max } = this.getBounds();
|
||||
return structureLocation.x >= min.x && structureLocation.x < max.x
|
||||
&& structureLocation.y >= min.y && structureLocation.y < max.y
|
||||
&& structureLocation.z >= min.z && structureLocation.z < max.z;
|
||||
}
|
||||
|
||||
isLocationInLayer(structureLocation) {
|
||||
const { min, max } = this.getLayeredBounds(true);
|
||||
return structureLocation.x >= min.x && structureLocation.x < max.x
|
||||
&& structureLocation.y >= min.y && structureLocation.y < max.y
|
||||
&& structureLocation.z >= min.z && structureLocation.z < max.z;
|
||||
}
|
||||
|
||||
isLocationActive(structureLocation) {
|
||||
if (!this.#options.isPlaced)
|
||||
return false
|
||||
if (this.#options.currentLayer > 0)
|
||||
return this.isLocationInLayer(structureLocation);
|
||||
return this.isLocationInStructure(structureLocation);
|
||||
}
|
||||
|
||||
toGlobalCoords(structureLocation) {
|
||||
return {
|
||||
x: this.#options.worldLocation.x + structureLocation.x,
|
||||
y: this.#options.worldLocation.y + structureLocation.y,
|
||||
z: this.#options.worldLocation.z + structureLocation.z
|
||||
};
|
||||
}
|
||||
|
||||
toStructureCoords(worldLocation) {
|
||||
return {
|
||||
x: worldLocation.x - this.#options.worldLocation.x,
|
||||
y: worldLocation.y - this.#options.worldLocation.y,
|
||||
z: worldLocation.z - this.#options.worldLocation.z
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,88 @@
|
||||
import { Structure } from './Structure';
|
||||
import { StructureInstance } from './StructureInstance';
|
||||
import { world } from '@minecraft/server';
|
||||
|
||||
class StructureCollection {
|
||||
#structures;
|
||||
structures;
|
||||
|
||||
constructor() {
|
||||
this.#structures = {};
|
||||
this.structures = {};
|
||||
}
|
||||
|
||||
add(name) {
|
||||
if (this.#structures[name]) {
|
||||
throw new Error(`Structure ${name} already exists.`);
|
||||
}
|
||||
const structure = new Structure(name);
|
||||
this.#structures[name] = structure;
|
||||
loadExistingStructures() {
|
||||
world.getDynamicPropertyIds().filter(id => id.startsWith('structOptions:')).forEach(id => {
|
||||
const instanceName = id.replace('structOptions:', '');
|
||||
let structureId;
|
||||
try {
|
||||
structureId = StructureInstance.parseOptions(instanceName).structureId;
|
||||
this.structures[instanceName] = new StructureInstance(instanceName, structureId);
|
||||
} catch (e) {
|
||||
world.sendMessage(`§c[StrucTool] Error loading structure instance '${instanceName}'. It will be removed.`);
|
||||
world.setDynamicProperty(id, void 0);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
add(instanceName, structureId) {
|
||||
if (this.structures[instanceName])
|
||||
throw new Error(`Instance ${instanceName} already exists.`);
|
||||
const structure = new StructureInstance(instanceName, structureId);
|
||||
this.structures[instanceName] = structure;
|
||||
return structure;
|
||||
}
|
||||
|
||||
get(name) {
|
||||
const structure = this.#structures[name];
|
||||
get(instanceName) {
|
||||
const structure = this.structures[instanceName];
|
||||
if (!structure) {
|
||||
throw new Error(`Structure ${name} not found.`);
|
||||
throw new Error(`Instance ${instanceName} not found.`);
|
||||
}
|
||||
return structure;
|
||||
}
|
||||
|
||||
remove(name) {
|
||||
const struct = this.get(name);
|
||||
struct.remove();
|
||||
delete this.#structures[name];
|
||||
delete(instanceName) {
|
||||
const struct = this.get(instanceName);
|
||||
struct.delete();
|
||||
delete this.structures[instanceName];
|
||||
}
|
||||
|
||||
getStructuresAtLocation(location) {
|
||||
return Object.values(this.#structures).filter(structure => structure.isLocationActive(structure.toStructureCoords(location)));
|
||||
getInstanceNames() {
|
||||
return Object.keys(this.structures);
|
||||
}
|
||||
|
||||
fetchStructureBlock(location) {
|
||||
const locatedStructures = this.getStructuresAtLocation(location);
|
||||
if (locatedStructures.length === 0)
|
||||
getStructures(dimensionId, location, options = {}) {
|
||||
return Object.values(this.structures).filter(structure => structure.isLocationActive(dimensionId, structure.toStructureCoords(location), options));
|
||||
}
|
||||
|
||||
getStructure(dimensionId, location, options = {}) {
|
||||
return this.getStructures(dimensionId, location, options)[0];
|
||||
}
|
||||
|
||||
fetchStructureBlock(dimensionId, location) {
|
||||
const structure = this.getStructure(dimensionId, location);
|
||||
if (!structure)
|
||||
return void 0;
|
||||
const structure = locatedStructures[0];
|
||||
return structure.getBlock(structure.toStructureCoords(location));
|
||||
}
|
||||
|
||||
getWorldStructureIds() {
|
||||
return world.structureManager.getWorldStructureIds()
|
||||
.filter(id => id.startsWith('mystructure:'))
|
||||
.map(id => id.replace('mystructure:', ''));
|
||||
}
|
||||
|
||||
rename(instanceName, newName) {
|
||||
const structure = this.get(instanceName);
|
||||
if (this.structures[newName])
|
||||
throw new Error(`Instance '${newName}' already exists.`);
|
||||
structure.rename(newName);
|
||||
this.structures[newName] = structure;
|
||||
delete this.structures[instanceName];
|
||||
structure.name = newName;
|
||||
}
|
||||
}
|
||||
|
||||
export const structureCollection = new StructureCollection();
|
||||
export const structureCollection = new StructureCollection();
|
||||
|
||||
world.afterEvents.worldLoad.subscribe(() => {
|
||||
structureCollection.loadExistingStructures();
|
||||
});
|
||||
@@ -0,0 +1,244 @@
|
||||
import { world } from "@minecraft/server";
|
||||
import { Outliner } from "./Outliner";
|
||||
|
||||
export class StructureInstance {
|
||||
name;
|
||||
#structure;
|
||||
#options = {
|
||||
structureId: void 0,
|
||||
isEnabled: false,
|
||||
dimensionId: void 0,
|
||||
worldLocation: { x: 0, y: 0, z: 0 },
|
||||
rotation: 0,
|
||||
mirror: false,
|
||||
currentLayer: 0
|
||||
};
|
||||
|
||||
constructor(instanceName, structureId) {
|
||||
this.name = instanceName;
|
||||
this.#structure = world.structureManager.get(structureId);
|
||||
if (!this.#structure)
|
||||
throw new Error(`[StrucTool] Structure '${structureId}' not found.`);
|
||||
this.#structure.saveToWorld();
|
||||
this.#options = this.loadOptions();
|
||||
this.#options.structureId = structureId;
|
||||
if (this.#options.isEnabled)
|
||||
this.refreshOutliner();
|
||||
this.updateOptions();
|
||||
}
|
||||
|
||||
loadOptions() {
|
||||
try {
|
||||
return JSON.parse(world.getDynamicProperty(`structOptions:${this.name}`));
|
||||
} catch (e) {
|
||||
world.setDynamicProperty(`structOptions:${this.name}`, JSON.stringify(this.#options));
|
||||
}
|
||||
return this.#options;
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.disable();
|
||||
world.setDynamicProperty(`structOptions:${this.name}`, void 0);
|
||||
this.#structure = void 0;
|
||||
this.#options = void 0;
|
||||
delete this.outliner;
|
||||
}
|
||||
|
||||
static parseOptions(instanceName) {
|
||||
const options = JSON.parse(world.getDynamicProperty(`structOptions:${instanceName}`));
|
||||
if (!options)
|
||||
throw new Error(`[StrucTool] Instance '${instanceName}' not found.`);
|
||||
return options;
|
||||
}
|
||||
|
||||
updateOptions() {
|
||||
world.setDynamicProperty(`structOptions:${this.name}`, JSON.stringify(this.#options));
|
||||
}
|
||||
|
||||
getStructure() {
|
||||
return this.#structure;
|
||||
}
|
||||
|
||||
getStructureId() {
|
||||
return this.#options.structureId;
|
||||
}
|
||||
|
||||
getLocation() {
|
||||
return { dimensionId: this.#options.dimensionId, location: this.#options.worldLocation };
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return this.#structure.size.y;
|
||||
}
|
||||
|
||||
getLayer() {
|
||||
return this.#options.currentLayer || 0;
|
||||
}
|
||||
|
||||
*getBlocks() {
|
||||
const max = this.#structure.size;
|
||||
for (let x = 0; x < max.x; x++) {
|
||||
for (let y = 0; y < max.y; y++) {
|
||||
for (let z = 0; z < max.z; z++) {
|
||||
yield this.#structure.getBlockPermutation({ x, y, z });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*getLayerBlocks(y) {
|
||||
const max = this.#structure.size;
|
||||
for (let x = 0; x < max.x; x++) {
|
||||
for (let z = 0; z < max.z; z++) {
|
||||
yield this.#structure.getBlockPermutation({ x, y, z });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getBlock(structureLocation) {
|
||||
return this.#structure.getBlockPermutation(structureLocation);
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
return {
|
||||
min: { x: 0, y: 0, z: 0 },
|
||||
max: this.#structure.size
|
||||
};
|
||||
}
|
||||
|
||||
getLayeredBounds() {
|
||||
if (!this.#options.isEnabled)
|
||||
throw new Error(`[StrucTool] Instance '${this.name}' is not placed.`);
|
||||
return {
|
||||
min: { x: 0, y: this.#options.currentLayer - 1, z: 0 },
|
||||
max: { x: this.#structure.size.x, y: this.#options.currentLayer, z: this.#structure.size.z }
|
||||
};
|
||||
}
|
||||
|
||||
rename(newName) {
|
||||
world.setDynamicProperty(`structOptions:${this.name}`, void 0);
|
||||
this.name = newName;
|
||||
world.setDynamicProperty(`structOptions:${this.name}`, JSON.stringify(this.#options));
|
||||
}
|
||||
|
||||
place(dimensionId, worldLocation) {
|
||||
this.move(dimensionId, worldLocation);
|
||||
this.enable();
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.#options.isEnabled = true;
|
||||
this.updateOptions();
|
||||
this.refreshOutliner();
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.#options.isEnabled = false;
|
||||
this.updateOptions();
|
||||
this.outliner.stopDraw();
|
||||
}
|
||||
|
||||
move(dimensionId, location) {
|
||||
this.#options.dimensionId = dimensionId;
|
||||
this.#options.worldLocation = { x: Math.floor(location.x), y: Math.floor(location.y), z: Math.floor(location.z) };
|
||||
this.updateOptions();
|
||||
this.refreshOutliner();
|
||||
}
|
||||
|
||||
setLayer(layer) {
|
||||
if (layer < 0 || layer > this.#structure.size.y)
|
||||
throw new Error(`[StrucTool] Layer ${layer} is out of bounds.`);
|
||||
this.#options.currentLayer = layer;
|
||||
this.updateOptions();
|
||||
this.refreshOutliner();
|
||||
}
|
||||
|
||||
refreshOutliner() {
|
||||
if (!this.#options.isEnabled)
|
||||
return;
|
||||
if (this.outliner)
|
||||
this.outliner.stopDraw();
|
||||
if (this.#options.currentLayer > 0) {
|
||||
const { min, max } = this.getLayeredBounds();
|
||||
this.outliner = new Outliner(this.#options.dimensionId, this.toGlobalCoords(min), this.toGlobalCoords(max));
|
||||
} else {
|
||||
this.outliner = new Outliner(this.#options.dimensionId, this.toGlobalCoords(this.getBounds().min), this.toGlobalCoords(this.getBounds().max));
|
||||
}
|
||||
}
|
||||
|
||||
isLocationInStructure(dimensionId, structureLocation) {
|
||||
if (this.#options.dimensionId !== dimensionId)
|
||||
return false
|
||||
const { min, max } = this.getBounds();
|
||||
return structureLocation.x >= min.x && structureLocation.x < max.x
|
||||
&& structureLocation.y >= min.y && structureLocation.y < max.y
|
||||
&& structureLocation.z >= min.z && structureLocation.z < max.z;
|
||||
}
|
||||
|
||||
isLocationInLayer(dimensionId, structureLocation) {
|
||||
if (!this.#options.isEnabled || this.#options.dimensionId !== dimensionId)
|
||||
return false
|
||||
const { min, max } = this.getLayeredBounds(true);
|
||||
return structureLocation.x >= min.x && structureLocation.x < max.x
|
||||
&& structureLocation.y >= min.y && structureLocation.y < max.y
|
||||
&& structureLocation.z >= min.z && structureLocation.z < max.z;
|
||||
}
|
||||
|
||||
isLocationActive(dimensionId, structureLocation, { useLayers = true } = {}) {
|
||||
if (!this.#options.isEnabled || this.#options.dimensionId !== dimensionId)
|
||||
return false
|
||||
if (useLayers && this.#options.currentLayer !== 0)
|
||||
return this.isLocationInLayer(dimensionId, structureLocation);
|
||||
return this.isLocationInStructure(dimensionId, structureLocation);
|
||||
}
|
||||
|
||||
toGlobalCoords(structureLocation) {
|
||||
return {
|
||||
x: this.#options.worldLocation.x + structureLocation.x,
|
||||
y: this.#options.worldLocation.y + structureLocation.y,
|
||||
z: this.#options.worldLocation.z + structureLocation.z
|
||||
};
|
||||
}
|
||||
|
||||
toStructureCoords(worldLocation) {
|
||||
return {
|
||||
x: worldLocation.x - this.#options.worldLocation.x,
|
||||
y: worldLocation.y - this.#options.worldLocation.y,
|
||||
z: worldLocation.z - this.#options.worldLocation.z
|
||||
};
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.#options.isEnabled;
|
||||
}
|
||||
|
||||
hasLocation() {
|
||||
return this.#options.dimensionId && this.#options.worldLocation.x !== 0 && this.#options.worldLocation.y !== 0 && this.#options.worldLocation.z !== 0;
|
||||
}
|
||||
|
||||
hasLayers() {
|
||||
return this.#structure.size.y > 1;
|
||||
}
|
||||
|
||||
isAtMaxLayer() {
|
||||
return !this.hasLayers || this.#options.currentLayer >= this.#structure.size.y;
|
||||
}
|
||||
|
||||
isAtMinLayer() {
|
||||
return !this.hasLayers || this.#options.currentLayer <= 0;
|
||||
}
|
||||
|
||||
increaseLayer() {
|
||||
if (this.isAtMaxLayer())
|
||||
this.setLayer(0);
|
||||
else
|
||||
this.setLayer(this.#options.currentLayer + 1);
|
||||
}
|
||||
|
||||
decreaseLayer() {
|
||||
if (this.isAtMinLayer())
|
||||
this.setLayer(this.#structure.size.y);
|
||||
else
|
||||
this.setLayer(this.#options.currentLayer - 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Command } from '../lib/canopy/CanopyExtension';
|
||||
import { extension } from '../config';
|
||||
import { world, system } from '@minecraft/server';
|
||||
import { MenuForm } from '../classes/MenuForm';
|
||||
|
||||
const ACTION_ITEM = 'minecraft:paper';
|
||||
|
||||
const structCmd = new Command({
|
||||
name: 'menu',
|
||||
description: { text: 'Manages current StrucTool structures.' },
|
||||
usage: 'menu',
|
||||
callback: structCommand
|
||||
});
|
||||
extension.addCommand(structCmd);
|
||||
|
||||
world.beforeEvents.itemUse.subscribe((event) => {
|
||||
if (!event.source || event.itemStack?.typeId !== ACTION_ITEM) return;
|
||||
event.cancel = true;
|
||||
system.run(() => structCommand(event.source));
|
||||
});
|
||||
|
||||
function structCommand(sender) {
|
||||
new MenuForm(sender);
|
||||
}
|
||||
@@ -17,7 +17,7 @@ const structCmd = new Command({
|
||||
extension.addCommand(structCmd);
|
||||
|
||||
function structCommand(sender, args) {
|
||||
const { option, name, arg3 } = args;
|
||||
const { name, option, arg3 } = args;
|
||||
switch (option) {
|
||||
case 'add':
|
||||
addStructure(sender, name);
|
||||
@@ -41,7 +41,7 @@ function structCommand(sender, args) {
|
||||
|
||||
function addStructure(sender, name) {
|
||||
try {
|
||||
structureCollection.add(name);
|
||||
structureCollection.add(name, name);
|
||||
} catch (e) {
|
||||
if (e.message.includes('already exists')) {
|
||||
sender.sendMessage({ text: `§cStructure '${name}' already exists.` });
|
||||
@@ -70,7 +70,7 @@ function placeStructure(sender, name) {
|
||||
structure = structureCollection.get(name);
|
||||
} catch (e) {
|
||||
try {
|
||||
structure = structureCollection.add(name);
|
||||
structure = structureCollection.add(name, name);
|
||||
} catch (e) {
|
||||
if (e.message.includes('already exists')) {
|
||||
sender.sendMessage({ text: `§cStructure '${name}' already exists.` });
|
||||
@@ -108,8 +108,8 @@ function printInfo(sender, name) {
|
||||
sender.sendMessage({ text: `§cStructure '${name}' not found.` });
|
||||
return;
|
||||
}
|
||||
const location = structure.getLocation();
|
||||
sender.sendMessage({ text: `§7Structure '${name}' at [${location.x} ${location.y} ${location.z}]` });
|
||||
const { dimensionId, location } = structure.getLocation();
|
||||
sender.sendMessage({ text: `§7Structure '${name}' at [${location.x} ${location.y} ${location.z}] in '${dimensionId}'` });
|
||||
sender.sendMessage({ text: `§7Current Layer: ${structure.getLayer()}` });
|
||||
sender.sendMessage({ text: `§7Materials: ${MaterialCounter.getPrintable(name)}` });
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import './rules/fastEasyPlace';
|
||||
|
||||
// Commands
|
||||
import './commands/struct';
|
||||
import './commands/menu';
|
||||
|
||||
// Other
|
||||
import './classes/BlockInfo';
|
||||
|
||||
+16
-16
@@ -5,11 +5,11 @@ import { structureCollection } from '../classes/StructureCollection';
|
||||
import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, specialItemPlacementConversions,
|
||||
blockIdToItemStackMap } from '../data';
|
||||
|
||||
const ARROW_SLOT = 35;
|
||||
const ACTION_SLOT = 35;
|
||||
|
||||
const easyPlace = new Rule({
|
||||
identifier: 'easyPlace',
|
||||
description: { text: 'Simplifies placing blocks in a structure (arrow in bottom right inventory slot).' },
|
||||
description: { text: "Simplifies placing blocks in a structure (paper named 'easyPlace' in bottom right inventory slot)." },
|
||||
onEnableCallback: () => { world.beforeEvents.playerPlaceBlock.subscribe(onPlayerPlaceBlock); },
|
||||
onDisableCallback: () => { world.beforeEvents.playerPlaceBlock.unsubscribe(onPlayerPlaceBlock); }
|
||||
})
|
||||
@@ -17,31 +17,24 @@ extension.addRule(easyPlace);
|
||||
|
||||
function onPlayerPlaceBlock(event) {
|
||||
const { player, block } = event;
|
||||
if (!player || !block || !hasArrowInCorrectSlot(player)) return;
|
||||
const structureBlock = fetchStructureBlock(block.location);
|
||||
if (!player || !block || !hasActionItemInCorrectSlot(player)) return;
|
||||
const structureBlock = structureCollection.fetchStructureBlock(block.dimension.id, block.location);
|
||||
if (!structureBlock)
|
||||
return;
|
||||
tryPlaceBlock(event, player, block, structureBlock);
|
||||
}
|
||||
|
||||
function hasArrowInCorrectSlot(player) {
|
||||
function hasActionItemInCorrectSlot(player) {
|
||||
const inventory = player.getComponent(EntityComponentTypes.Inventory)?.container;
|
||||
if (!inventory)
|
||||
return false;
|
||||
const arrowSlot = inventory.getSlot(ARROW_SLOT);
|
||||
return arrowSlot.hasItem() && arrowSlot.typeId === 'minecraft:arrow';
|
||||
}
|
||||
|
||||
function fetchStructureBlock(location) {
|
||||
const locatedStructures = structureCollection.getStructuresAtLocation(location);
|
||||
if (locatedStructures.length === 0)
|
||||
return void 0;
|
||||
const structure = locatedStructures[0];
|
||||
return structure.getBlock(structure.toStructureCoords(location));
|
||||
const actionSlot = inventory.getSlot(ACTION_SLOT);
|
||||
return actionSlot.hasItem() && actionSlot.typeId === 'minecraft:paper' && actionSlot.nameTag === 'easyPlace';
|
||||
}
|
||||
|
||||
function tryPlaceBlock(event, player, block, structureBlock) {
|
||||
if (isBannedBlock(player, structureBlock)) return;
|
||||
if (isBannedBlock(player, structureBlock))
|
||||
preventAction(event, player);
|
||||
structureBlock = tryConvertBannedToValidBlock(structureBlock);
|
||||
if (player.getGameMode() === GameMode.creative) {
|
||||
placeBlock(block, structureBlock);
|
||||
@@ -51,6 +44,13 @@ function tryPlaceBlock(event, player, block, structureBlock) {
|
||||
}
|
||||
}
|
||||
|
||||
function preventAction(event, player) {
|
||||
event.cancel = true;
|
||||
system.run(() => {
|
||||
player.onScreenDisplay.setActionBar('§cAction prevented by easyPlace.');
|
||||
});
|
||||
}
|
||||
|
||||
function isBannedBlock(player, structureBlock) {
|
||||
const blockId = structureBlock.type.id.replace('minecraft:', '');
|
||||
if (bannedBlocks.includes(blockId))
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Raycaster } from '../classes/Raycaster';
|
||||
let runner = void 0;
|
||||
const easyPlace = new Rule({
|
||||
identifier: 'fastEasyPlace',
|
||||
description: { text: 'Looking at structure blocks with an arrow in your hand will place them.' },
|
||||
description: { text: "Looking at structure blocks with a paper named 'easyPlace' in your hand will place them." },
|
||||
onEnableCallback: () => { runner = system.runInterval(onTick, 2); },
|
||||
onDisableCallback: () => { system.clearRun(runner); }
|
||||
})
|
||||
@@ -23,7 +23,7 @@ function onTick() {
|
||||
}
|
||||
|
||||
function processEasyPlace(player) {
|
||||
if (!player || !isHoldingArrow(player)) return;
|
||||
if (!player || !isHoldingActionItem(player)) return;
|
||||
const structureBlock = Raycaster.getTargetedStructureBlock(player, { isFirst: true });
|
||||
if (!structureBlock)
|
||||
return;
|
||||
@@ -31,15 +31,15 @@ function processEasyPlace(player) {
|
||||
tryPlaceBlock(player, worldBlock, structureBlock.permutation);
|
||||
}
|
||||
|
||||
function isHoldingArrow(player) {
|
||||
function isHoldingActionItem(player) {
|
||||
const mainhandItemStack = player.getComponent(EntityComponentTypes.Equippable).getEquipment(EquipmentSlot.Mainhand);
|
||||
if (!mainhandItemStack)
|
||||
return false;
|
||||
return mainhandItemStack.typeId === 'minecraft:arrow';
|
||||
return mainhandItemStack.typeId === 'minecraft:paper' && mainhandItemStack.nameTag === 'easyPlace';
|
||||
}
|
||||
|
||||
function tryPlaceBlock(player, worldBlock, structureBlock) {
|
||||
if (isBannedBlock(player, structureBlock) || !locationIsPlaceable(worldBlock)) return;
|
||||
if (isBannedBlock(player, structureBlock) || !locationIsPlaceable(player, worldBlock)) return;
|
||||
structureBlock = tryConvertBannedToValidBlock(structureBlock);
|
||||
if (player.getGameMode() === GameMode.creative) {
|
||||
placeBlock(worldBlock, structureBlock);
|
||||
@@ -49,11 +49,13 @@ function tryPlaceBlock(player, worldBlock, structureBlock) {
|
||||
}
|
||||
}
|
||||
|
||||
function locationIsPlaceable(worldBlock) {
|
||||
function locationIsPlaceable(player, worldBlock) {
|
||||
return worldBlock.isAir;
|
||||
}
|
||||
|
||||
function isBannedBlock(player, structureBlock) {
|
||||
if (!structureBlock)
|
||||
return true;
|
||||
const blockId = structureBlock.type.id.replace('minecraft:', '');
|
||||
if (bannedBlocks.includes(blockId))
|
||||
return true;
|
||||
|
||||
+3
-2
@@ -1,3 +1,4 @@
|
||||
import { system } from '@minecraft/server';
|
||||
import { FormCancelationReason } from '@minecraft/server-ui';
|
||||
|
||||
export async function forceShow(player, form, timeout = Infinity) {
|
||||
@@ -5,9 +6,9 @@ export async function forceShow(player, form, timeout = Infinity) {
|
||||
while ((system.currentTick - startTick) < timeout) {
|
||||
const response = await form.show(player);
|
||||
if (startTick + 1 === system.currentTick && response.cancelationReason === FormCancelationReason.UserBusy)
|
||||
player.sendMessage({ translate: 'commands.canopy.menu.busy' });
|
||||
player.sendMessage("§8Close your chat window to access the menu.");
|
||||
if (response.cancelationReason !== FormCancelationReason.UserBusy)
|
||||
return response;
|
||||
}
|
||||
throw new Error({ translate: 'commands.canopy.menu.timeout', with: [String(timeout)] });
|
||||
throw new Error("Menu timed out.");
|
||||
};
|
||||
Reference in New Issue
Block a user