rename RP & BP folders to remove spaces
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { GameMode, system, world } from '@minecraft/server';
|
||||
import { Raycaster } from '../classes/Raycaster';
|
||||
import { fetchMatchingItemSlot } from '../utils';
|
||||
|
||||
class BlockInfo {
|
||||
static shownToLastTick = new Set();
|
||||
|
||||
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, useActiveLayer: true });
|
||||
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(player, block.permutation) });
|
||||
this.shownToLastTick.add(player.id);
|
||||
}
|
||||
|
||||
static getFormattedBlockInfo(player, block) {
|
||||
return 'Structure:' + this.getSupplyMessage(player, block) + '\n' + this.getBlockMessage(block);
|
||||
}
|
||||
|
||||
static getBlockMessage(block) {
|
||||
if (!block)
|
||||
return '§7Unknown';
|
||||
const states = block.getAllStates();
|
||||
if (Object.keys(states).length === 0)
|
||||
return `§a${block.type.id}`;
|
||||
else
|
||||
return `§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');
|
||||
}
|
||||
|
||||
static getSupplyMessage(player, block) {
|
||||
const itemStack = fetchMatchingItemSlot(player, block.getItemStack()?.typeId);
|
||||
const isInSurvival = player.getGameMode() === GameMode.survival;
|
||||
if (!itemStack && isInSurvival)
|
||||
return ' §c[No Supply]';
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
system.runInterval(() => BlockInfo.onTick());
|
||||
@@ -0,0 +1,18 @@
|
||||
import { BuilderOptions } from "./BuilderOptions";
|
||||
|
||||
export class Builder {
|
||||
playerId;
|
||||
materialInstanceName = void 0;
|
||||
|
||||
constructor(playerId) {
|
||||
this.playerId = playerId;
|
||||
}
|
||||
|
||||
isOptionEnabled(optionId) {
|
||||
return BuilderOptions.isEnabled(optionId, this.playerId);
|
||||
}
|
||||
|
||||
setOption(optionId, value) {
|
||||
return BuilderOptions.setValue(optionId, this.playerId, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { BuilderFormBuilder } from "./BuilderFormBuilder";
|
||||
import { BuilderOptions } from "./BuilderOptions";
|
||||
import { forceShow } from '../../utils';
|
||||
|
||||
export class BuilderForm {
|
||||
constructor(player) {
|
||||
this.player = player;
|
||||
this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
forceShow(this.player, BuilderFormBuilder.buildBuilderOptions(this.player)).then((response) => {
|
||||
if (response.canceled) return;
|
||||
this.applySettings(response.formValues);
|
||||
});
|
||||
}
|
||||
|
||||
applySettings(formValues) {
|
||||
const optionIds = BuilderOptions.getOptionIds();
|
||||
for (let i = 0; i < optionIds.length; i++) {
|
||||
const option = BuilderOptions.get(optionIds[i]);
|
||||
const changedToValue = option.setValue(this.player.id, formValues[i]);
|
||||
if (changedToValue === true)
|
||||
this.player.sendMessage(`§a${option.displayName} is now enabled!§7 ${option.howToUse}`);
|
||||
else if (changedToValue === false)
|
||||
this.player.sendMessage(`§c${option.displayName} is now disabled.`)
|
||||
}
|
||||
}
|
||||
|
||||
valueChangedToEnabled(hasChanged, newValue) {
|
||||
return hasChanged && newValue === true;
|
||||
}
|
||||
|
||||
valueChangedToDisabled(hasChanged, newValue) {
|
||||
return hasChanged && newValue === false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ModalFormData } from "@minecraft/server-ui";
|
||||
import { MenuFormBuilder } from "../MenuFormBuilder";
|
||||
import { BuilderOptions } from "./BuilderOptions";
|
||||
|
||||
export class BuilderFormBuilder {
|
||||
static buildBuilderOptions(player) {
|
||||
const form = new ModalFormData()
|
||||
.title(MenuFormBuilder.menuTitle);
|
||||
for (const optionId of BuilderOptions.getOptionIds()) {
|
||||
const option = BuilderOptions.get(optionId);
|
||||
form.toggle(`${option.displayName} - ${option.description}`, option.isEnabled(player.id));
|
||||
}
|
||||
form.submitButton('§2Apply');
|
||||
return form;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { BuilderOptions } from "./BuilderOptions";
|
||||
import { world } from "@minecraft/server";
|
||||
|
||||
export class BuilderOption {
|
||||
identifier;
|
||||
displayName;
|
||||
description;
|
||||
howToUse;
|
||||
#onEnable;
|
||||
#onDisable;
|
||||
#DP_NAMESPACE = "builderOptions";
|
||||
|
||||
constructor({ identifier, displayName, description, howToUse, onEnableCallback = () => {}, onDisableCallback = () => {} }) {
|
||||
this.identifier = identifier;
|
||||
this.displayName = displayName;
|
||||
this.description = description;
|
||||
this.howToUse = howToUse;
|
||||
this.#onEnable = onEnableCallback;
|
||||
this.#onDisable = onDisableCallback;
|
||||
BuilderOptions.add(this);
|
||||
}
|
||||
|
||||
isEnabled(playerId) {
|
||||
return world.getDynamicProperty(`${this.#DP_NAMESPACE}:${playerId}:${this.identifier}`) === true;
|
||||
}
|
||||
|
||||
setValue(playerId, value) {
|
||||
if (value)
|
||||
this.#onEnable(playerId);
|
||||
else
|
||||
this.#onDisable(playerId);
|
||||
if (this.isEnabled(playerId) !== value) {
|
||||
this.save(playerId, value);
|
||||
return value;
|
||||
}
|
||||
this.save(playerId, value);
|
||||
return void 0;
|
||||
}
|
||||
|
||||
save(playerId, value) {
|
||||
world.setDynamicProperty(`${this.#DP_NAMESPACE}:${playerId}:${this.identifier}`, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export class BuilderOptions {
|
||||
static options = {};
|
||||
|
||||
static add(builderOption) {
|
||||
this.options[builderOption.identifier] = builderOption;
|
||||
}
|
||||
|
||||
static get(optionId) {
|
||||
return this.options[optionId];
|
||||
}
|
||||
|
||||
static getOptionIds() {
|
||||
return Object.keys(this.options).sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
static isEnabled(optionId, playerId) {
|
||||
return this.options[optionId].isEnabled(playerId);
|
||||
}
|
||||
|
||||
static setValue(optionId, playerId, value) {
|
||||
return this.options[optionId].setValue(playerId, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { world } from "@minecraft/server";
|
||||
import { Builder } from "./Builder";
|
||||
|
||||
export class Builders {
|
||||
static builders = {};
|
||||
|
||||
static add(playerId) {
|
||||
if (this.builders[playerId])
|
||||
return;
|
||||
this.builders[playerId] = new Builder(playerId);
|
||||
}
|
||||
|
||||
static remove(playerId) {
|
||||
delete this.builders[playerId];
|
||||
}
|
||||
|
||||
static get(id) {
|
||||
return this.builders[id];
|
||||
}
|
||||
|
||||
static onJoin(playerId) {
|
||||
this.add(playerId);
|
||||
}
|
||||
|
||||
static onLeave(playerId) {
|
||||
this.remove(playerId);
|
||||
}
|
||||
}
|
||||
|
||||
world.afterEvents.playerJoin.subscribe((event) => Builders.onJoin(event.playerId));
|
||||
world.beforeEvents.playerLeave.subscribe((event) => Builders.onLeave(event.player.id));
|
||||
world.afterEvents.worldLoad.subscribe((event) => {
|
||||
for (const player of world.getAllPlayers()) {
|
||||
Builders.onJoin(player.id);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
export const BlockVerificationLevel = Object.freeze({
|
||||
Unknown: 0,
|
||||
NoMatch: 1,
|
||||
TypeMatch: 2,
|
||||
Match: 3,
|
||||
Missing: 4,
|
||||
Air: 5,
|
||||
Skipped: 6
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
export const InstanceButtons = Object.freeze({
|
||||
Unknown: 'Unknown',
|
||||
MainMenu: '<<',
|
||||
Place: '§aPlace Instance',
|
||||
Enable: '§aEnable Instance',
|
||||
Disable: '§cDisable Instance',
|
||||
Rename: 'Rename Instance',
|
||||
Delete: '§cDelete Instance',
|
||||
NextLayer: 'Increase Layer',
|
||||
PreviousLayer: 'Decrease Layer',
|
||||
Move: 'Move Here',
|
||||
Statistics: 'Statistics',
|
||||
Settings: 'Settings'
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
export class InstanceNotPlacedError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = 'InstanceNotPlacedError';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class InvalidInstanceError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = 'InvalidInstanceError';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { structureCollection } from '../Structure/StructureCollection';
|
||||
import { MenuForm } from '../MenuForm';
|
||||
import { forceShow } from '../../utils';
|
||||
import { InstanceButtons } from '../Enums/InstanceButtons';
|
||||
import { InstanceFormBuilder } from './InstanceFormBuilder';
|
||||
import { FormCancelationReason } from '@minecraft/server-ui';
|
||||
|
||||
export class InstanceForm {
|
||||
instanceName;
|
||||
#buttons = {
|
||||
isEnabled: [
|
||||
InstanceButtons.NextLayer,
|
||||
InstanceButtons.PreviousLayer,
|
||||
InstanceButtons.Move,
|
||||
InstanceButtons.Statistics,
|
||||
InstanceButtons.Settings,
|
||||
InstanceButtons.Rename,
|
||||
InstanceButtons.Disable
|
||||
],
|
||||
isNotEnabledAndIsNotPlaced: [
|
||||
InstanceButtons.Place,
|
||||
InstanceButtons.Rename
|
||||
],
|
||||
isNotEnabledButIsPlaced: [
|
||||
InstanceButtons.Enable,
|
||||
InstanceButtons.Rename
|
||||
],
|
||||
common: [
|
||||
InstanceButtons.Delete,
|
||||
InstanceButtons.MainMenu
|
||||
]
|
||||
}
|
||||
|
||||
constructor(player, instanceName) {
|
||||
this.player = player;
|
||||
this.instanceName = instanceName;
|
||||
this.instance = structureCollection.get(this.instanceName);
|
||||
this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
const currentOptions = this.getActiveOptions();
|
||||
forceShow(this.player, InstanceFormBuilder.buildInstance(this.instance, currentOptions)).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 !== InstanceButtons.SetLayer
|
||||
&& option !== InstanceButtons.NextLayer
|
||||
&& option !== InstanceButtons.PreviousLayer
|
||||
);
|
||||
return currentOptions;
|
||||
}
|
||||
|
||||
handleOption(option) {
|
||||
switch (option) {
|
||||
case InstanceButtons.Enable:
|
||||
this.instance.enable();
|
||||
break;
|
||||
case InstanceButtons.Disable:
|
||||
this.instance.disable();
|
||||
break;
|
||||
case InstanceButtons.Place:
|
||||
this.instance.place(this.player.dimension.id, this.player.location);
|
||||
break;
|
||||
case InstanceButtons.Rename:
|
||||
this.renameInstanceForm();
|
||||
break;
|
||||
case InstanceButtons.Delete:
|
||||
structureCollection.delete(this.instanceName);
|
||||
break;
|
||||
case InstanceButtons.NextLayer:
|
||||
this.instance.increaseLayer();
|
||||
new InstanceForm(this.player, this.instanceName);
|
||||
break;
|
||||
case InstanceButtons.PreviousLayer:
|
||||
this.instance.decreaseLayer();
|
||||
new InstanceForm(this.player, this.instanceName);
|
||||
break;
|
||||
case InstanceButtons.Settings:
|
||||
this.settingsForm();
|
||||
break;
|
||||
case InstanceButtons.Move:
|
||||
this.instance.move(this.player.dimension.id, this.player.location);
|
||||
break;
|
||||
case InstanceButtons.Statistics:
|
||||
this.statisticsForm();
|
||||
break;
|
||||
case InstanceButtons.MainMenu:
|
||||
new MenuForm(this.player, { jumpToInstance: false });
|
||||
break;
|
||||
default:
|
||||
this.player.sendMessage(`§cUnknown option: ${option}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
renameInstanceForm() {
|
||||
InstanceFormBuilder.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() {
|
||||
InstanceFormBuilder.buildSetLayer(this.instance.getBounds().max.y, this.instance.getLayer()).show(this.player).then((response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
this.instance.setLayer(parseInt(response.formValues[0]));
|
||||
});
|
||||
}
|
||||
|
||||
async statisticsForm() {
|
||||
const statsForm = await InstanceFormBuilder.buildStatistics(this.instance)
|
||||
statsForm.form.show(this.player).then((response) => {
|
||||
if (response.canceled && response.cancelationReason === FormCancelationReason.UserBusy)
|
||||
this.player.sendMessage(statsForm.stats);
|
||||
});
|
||||
}
|
||||
|
||||
settingsForm() {
|
||||
InstanceFormBuilder.buildSettings(this.instance).show(this.player).then((response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
this.instance.setVerifierEnabled(response.formValues[0]);
|
||||
if (response.formValues[1])
|
||||
this.instance.setVerifierDistance(5);
|
||||
else
|
||||
this.instance.setVerifierDistance(0);
|
||||
this.instance.setLayer(parseInt(response.formValues[2]));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
|
||||
import { MenuFormBuilder } from '../MenuFormBuilder';
|
||||
import { StructureVerifier } from '../Verifier/StructureVerifier';
|
||||
import { StructureStatistics } from '../Structure/StructureStatistics';
|
||||
import { TicksPerSecond } from '@minecraft/server';
|
||||
|
||||
export class InstanceFormBuilder {
|
||||
static buildInstance(instance, options) {
|
||||
const location = instance.getLocation();
|
||||
const form = new ActionFormData()
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
let body = `Instance: §a${instance.getName()}\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 async buildStatistics(instance) {
|
||||
const buildStatisticsForm = new ActionFormData()
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
const structureVerifier = new StructureVerifier(instance, { isEnabled: true, trackPlayerDistance: 0, intervalOrLifetime: 30 * TicksPerSecond, isStandalone: true });
|
||||
const verification = await structureVerifier.verifyStructure();
|
||||
const statistics = new StructureStatistics(instance, verification);
|
||||
const statsMessage = statistics.getMessage();
|
||||
buildStatisticsForm.body(statsMessage);
|
||||
return { form: buildStatisticsForm, stats: statsMessage };
|
||||
}
|
||||
|
||||
static buildSettings(instance) {
|
||||
return new ModalFormData()
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
.label('Use the slider to select the layer. Use 0 for all layers.')
|
||||
.toggle('Block Validation', instance.options.verifier.isEnabled)
|
||||
.toggle('Distance-Based Block Validation', instance.verifier.getTrackPlayerDistance() !== 0)
|
||||
.slider("Layer", 0, instance.getMaxLayer(), 1, instance.getLayer())
|
||||
.submitButton('§2Apply');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Vector } from "../../lib/Vector";
|
||||
import { world } from "@minecraft/server";
|
||||
import { Option } from "../Option";
|
||||
|
||||
export class InstanceOptions extends Option {
|
||||
#DP_NAMESPACE = "instanceOptions";
|
||||
instanceName = void 0;
|
||||
structureId = void 0;
|
||||
isEnabled = false;
|
||||
dimensionId = void 0;
|
||||
worldLocation = new Vector();
|
||||
currentLayer = 0;
|
||||
verifier = {
|
||||
isEnabled: true,
|
||||
trackPlayerDistance: 5,
|
||||
intervalOrLifetime: 10
|
||||
};
|
||||
|
||||
static getInstanceStrucetureId(instanceName) {
|
||||
const options = new InstanceOptions(instanceName, void 0);
|
||||
return options.structureId;
|
||||
}
|
||||
|
||||
constructor(instanceName, structureId) {
|
||||
super();
|
||||
this.instanceName = instanceName;
|
||||
this.structureId = structureId;
|
||||
this.load();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.saveToDP(this.#DP_NAMESPACE, this.instanceName, this);
|
||||
}
|
||||
|
||||
load() {
|
||||
this.loadFromDP(this.#DP_NAMESPACE, this.instanceName);
|
||||
this.worldLocation = Vector.from(this.worldLocation);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.clearDP(this.#DP_NAMESPACE, this.instanceName);
|
||||
}
|
||||
|
||||
getDimension() {
|
||||
return world.getDimension(this.dimensionId);
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.isEnabled = true;
|
||||
this.save();
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.isEnabled = false;
|
||||
this.save();
|
||||
}
|
||||
|
||||
rename(newName) {
|
||||
this.clear();
|
||||
this.instanceName = newName;
|
||||
this.save();
|
||||
}
|
||||
|
||||
move(dimensionId, worldLocation) {
|
||||
this.dimensionId = dimensionId;
|
||||
this.worldLocation = Vector.from(worldLocation).floor();
|
||||
this.save();
|
||||
}
|
||||
|
||||
setLayer(layer) {
|
||||
this.currentLayer = layer;
|
||||
this.save();
|
||||
}
|
||||
|
||||
setVerifierEnabled(enable) {
|
||||
this.verifier.isEnabled = enable;
|
||||
this.save();
|
||||
}
|
||||
|
||||
setVerifierDistance(distance) {
|
||||
this.verifier.trackPlayerDistance = distance;
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
import { Vector } from "../../lib/Vector";
|
||||
import { StructureOutliner } from "../Structure/StructureOutliner";
|
||||
import { StructureVerifier } from "../Verifier/StructureVerifier";
|
||||
import { Structure } from "../Structure/Structure";
|
||||
import { InstanceOptions } from "./InstanceOptions";
|
||||
import { TicksPerSecond } from "@minecraft/server";
|
||||
import { InstanceNotPlacedError } from "../Errors/InstanceNotPlacedError";
|
||||
import { StructureMaterials } from "../Materials/StructureMaterials";
|
||||
|
||||
export class StructureInstance {
|
||||
options;
|
||||
structure = void 0;
|
||||
verifier = void 0;
|
||||
outliner = void 0;
|
||||
materials = void 0;
|
||||
|
||||
constructor(instanceName, structureId) {
|
||||
this.structure = new Structure(structureId);
|
||||
this.options = new InstanceOptions(instanceName, structureId);
|
||||
this.refreshBox();
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.disable();
|
||||
this.options.clear();
|
||||
delete this.options;
|
||||
delete this.structure;
|
||||
delete this.outliner;
|
||||
delete this.verifier;
|
||||
delete this.materials;
|
||||
}
|
||||
|
||||
refreshBox() {
|
||||
if (!this.hasLocation())
|
||||
return;
|
||||
if (!this.outliner)
|
||||
this.outliner = new StructureOutliner(this);
|
||||
if (!this.verifier)
|
||||
this.verifier = new StructureVerifier(this, { isEnabled: this.options.verifier.isEnabled, trackPlayerDistance: this.options.verifier.trackPlayerDistance });
|
||||
if (!this.materials)
|
||||
this.materials = new StructureMaterials(this);
|
||||
this.outliner.refresh();
|
||||
this.verifier.refresh();
|
||||
this.materials.refresh();
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.options.instanceName;
|
||||
}
|
||||
|
||||
getStructureId() {
|
||||
return this.options.structureId;
|
||||
}
|
||||
|
||||
getLocation() {
|
||||
return { dimensionId: this.options.dimensionId, location: this.options.worldLocation };
|
||||
}
|
||||
|
||||
getDimension() {
|
||||
return this.options.getDimension();
|
||||
}
|
||||
|
||||
getLayer() {
|
||||
return this.options.currentLayer;
|
||||
}
|
||||
|
||||
getMaxLayer() {
|
||||
return this.structure.getHeight();
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
return {
|
||||
min: this.structure.getMin(),
|
||||
max: this.structure.getMax()
|
||||
}
|
||||
}
|
||||
|
||||
getActiveBounds() {
|
||||
if (!this.options.isEnabled)
|
||||
throw new InstanceNotPlacedError(`[Construct] Instance '${this.options.instanceName}' is not placed.`);
|
||||
if (this.hasLayerSelected())
|
||||
return this.getLayerBounds(this.getLayer());
|
||||
return this.getBounds();
|
||||
}
|
||||
|
||||
getLayerBounds(layer) {
|
||||
if (!this.options.isEnabled)
|
||||
throw new InstanceNotPlacedError(`[Construct] Instance '${this.options.instanceName}' is not placed.`);
|
||||
const min = this.structure.getMin();
|
||||
const max = this.structure.getMax();
|
||||
return {
|
||||
min: new Vector(min.x, layer - 1, min.z),
|
||||
max: new Vector(max.x, layer, max.z)
|
||||
};
|
||||
}
|
||||
|
||||
getBlock(structureLocation) {
|
||||
return this.structure.getBlock(structureLocation);
|
||||
}
|
||||
|
||||
getBlocks(structureLocations) {
|
||||
return this.structure.getBlocks(structureLocations);
|
||||
}
|
||||
|
||||
getLayerBlocks(layer) {
|
||||
return this.structure.getLayerBlocks(layer);
|
||||
}
|
||||
|
||||
getAllBlocks() {
|
||||
return this.structure.getAllBlocks();
|
||||
}
|
||||
|
||||
getActiveBlocks() {
|
||||
if (!this.options.isEnabled)
|
||||
throw new InstanceNotPlacedError(`[Construct] Instance '${this.options.instanceName}' is not placed.`);
|
||||
if (this.hasLayerSelected())
|
||||
return this.getLayerBlocks(this.getLayer() - 1);
|
||||
return this.getAllBlocks();
|
||||
}
|
||||
|
||||
isLocationActive(dimensionId, structureLocation, { useActiveLayer = true } = {}) {
|
||||
if (!this.options.isEnabled || this.options.dimensionId !== dimensionId)
|
||||
return false;
|
||||
let bounds;
|
||||
if (useActiveLayer)
|
||||
bounds = this.getActiveBounds();
|
||||
else
|
||||
bounds = this.getBounds();
|
||||
return structureLocation.x >= bounds.min.x && structureLocation.x < bounds.max.x
|
||||
&& structureLocation.y >= bounds.min.y && structureLocation.y < bounds.max.y
|
||||
&& structureLocation.z >= bounds.min.z && structureLocation.z < bounds.max.z;
|
||||
}
|
||||
|
||||
getAllActiveLocations() {
|
||||
if (!this.options.isEnabled)
|
||||
throw new InstanceNotPlacedError(`[Construct] Instance '${this.options.instanceName}' is not placed.`);
|
||||
if (this.hasLayerSelected())
|
||||
return this.structure.getLayerLocations(this.getLayer()-1);
|
||||
else
|
||||
return this.structure.getAllLocations();
|
||||
}
|
||||
|
||||
getActiveMaterials() {
|
||||
return this.materials;
|
||||
}
|
||||
|
||||
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.getMaxLayer() > 1;
|
||||
}
|
||||
|
||||
hasLayerSelected() {
|
||||
return this.hasLayers() && this.options.currentLayer !== 0;
|
||||
}
|
||||
|
||||
hasWholeStructureSelected() {
|
||||
return this.hasLocation() && this.options.currentLayer === 0;
|
||||
}
|
||||
|
||||
isAtMaxLayer() {
|
||||
return !this.hasLayers || this.options.currentLayer >= this.getMaxLayer();
|
||||
}
|
||||
|
||||
isAtMinLayer() {
|
||||
return !this.hasLayers || this.options.currentLayer <= 0;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.options.enable();
|
||||
this.refreshBox();
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.options.disable();
|
||||
this.refreshBox();
|
||||
}
|
||||
|
||||
rename(newName) {
|
||||
this.options.rename(newName);
|
||||
}
|
||||
|
||||
place(dimensionId, worldLocation) {
|
||||
this.enable();
|
||||
this.move(dimensionId, worldLocation);
|
||||
}
|
||||
|
||||
move(dimensionId, worldLocation) {
|
||||
this.options.move(dimensionId, worldLocation);
|
||||
this.refreshBox();
|
||||
}
|
||||
|
||||
setLayer(layer) {
|
||||
if (layer < 0 || layer > this.getMaxLayer())
|
||||
throw new Error(`[Construct] Layer ${layer} is out of bounds.`);
|
||||
this.options.setLayer(layer);
|
||||
this.refreshBox();
|
||||
}
|
||||
|
||||
setVerifierEnabled(enable) {
|
||||
this.options.setVerifierEnabled(enable);
|
||||
this.verifier.refresh();
|
||||
}
|
||||
|
||||
setVerifierDistance(distance) {
|
||||
this.options.setVerifierDistance(distance);
|
||||
if (this.options.verifier.trackPlayerDistance === 0) {
|
||||
const bounds = this.getBounds();
|
||||
this.options.verifier.intervalOrLifetime = Math.max(bounds.min.volume(bounds.max) / TicksPerSecond, 2*TicksPerSecond);
|
||||
} else {
|
||||
this.options.verifier.intervalOrLifetime = 10;
|
||||
}
|
||||
this.verifier.refresh();
|
||||
}
|
||||
|
||||
increaseLayer() {
|
||||
if (this.isAtMaxLayer())
|
||||
this.setLayer(0);
|
||||
else
|
||||
this.setLayer(this.options.currentLayer + 1);
|
||||
}
|
||||
|
||||
decreaseLayer() {
|
||||
if (this.isAtMinLayer())
|
||||
this.setLayer(this.getMaxLayer());
|
||||
else
|
||||
this.setLayer(this.options.currentLayer - 1);
|
||||
}
|
||||
|
||||
toGlobalCoords(structureLocation) {
|
||||
return Vector.from(structureLocation).add(this.options.worldLocation);
|
||||
}
|
||||
|
||||
toStructureCoords(worldLocation) {
|
||||
return Vector.from(worldLocation).subtract(this.options.worldLocation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { MaterialsFormBuilder } from './MaterialsFormBuilder';
|
||||
import { forceShow } from '../../utils';
|
||||
import { Builders } from '../Builder/Builders';
|
||||
import { structureCollection } from '../Structure/StructureCollection';
|
||||
|
||||
export class MaterialsForm {
|
||||
constructor(player) {
|
||||
this.player = player;
|
||||
this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
try {
|
||||
return forceShow(this.player, MaterialsFormBuilder.buildInstanceSelector(this.player)).then((response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
const selectedInstanceName = structureCollection.getInstanceNames()[response.selection];
|
||||
if (selectedInstanceName) {
|
||||
this.setActiveInstance(selectedInstanceName);
|
||||
this.player.sendMessage(`§7Selected instance for material grabber: §2${selectedInstanceName}`);
|
||||
return;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.message === 'Menu timed out.') {
|
||||
this.player.sendMessage('§8Menu timed out.');
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
setActiveInstance(instanceName) {
|
||||
const builder = Builders.get(this.player.id);
|
||||
builder.materialInstanceName = instanceName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ActionFormData } from "@minecraft/server-ui";
|
||||
import { MenuFormBuilder } from "../MenuFormBuilder";
|
||||
import { structureCollection } from "../Structure/StructureCollection";
|
||||
import { Builders } from "../Builder/Builders";
|
||||
|
||||
export class MaterialsFormBuilder {
|
||||
static menuTitle = MenuFormBuilder.menuTitle + ' Material Grabber';
|
||||
|
||||
static buildInstanceSelector(player) {
|
||||
const allInstanceNameForm = new ActionFormData()
|
||||
.title(this.menuTitle);
|
||||
const currInstanceName = Builders.get(player.id).materialInstanceName;
|
||||
let body = '§7Current instance: ';
|
||||
if (currInstanceName)
|
||||
body += `§2${currInstanceName}`;
|
||||
else
|
||||
body += '§7None';
|
||||
body += '\n§7Select an instance:';
|
||||
allInstanceNameForm.body(body);
|
||||
structureCollection.getInstanceNames().forEach(instanceName => {
|
||||
allInstanceNameForm.button(`§2${instanceName}`);
|
||||
});
|
||||
return allInstanceNameForm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
class StructureMaterials {
|
||||
instance;
|
||||
materials;
|
||||
|
||||
constructor(instance) {
|
||||
this.instance = instance;
|
||||
this.materials = {};
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.clear();
|
||||
this.populateInstance();
|
||||
}
|
||||
|
||||
populateInstance() {
|
||||
try {
|
||||
if (this.instance.hasLocation())
|
||||
this.populateActive();
|
||||
else
|
||||
this.populateAll();
|
||||
} catch (e) {
|
||||
if (e.name === 'InstanceNotPlacedError')
|
||||
this.clear();
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
get(itemType) {
|
||||
return this.materials[itemType];
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return Object.keys(this.materials).length === 0;
|
||||
}
|
||||
|
||||
has(itemType) {
|
||||
return this.materials[itemType] !== undefined;
|
||||
}
|
||||
|
||||
remove(itemType, amount) {
|
||||
if (!this.materials[itemType]) return;
|
||||
this.materials[itemType].count -= amount;
|
||||
if (this.materials[itemType].count <= 0)
|
||||
delete this.materials[itemType];
|
||||
}
|
||||
|
||||
populateAll() {
|
||||
for (let layer = 0; layer < this.instance.getMaxLayer(); layer++)
|
||||
this.populateLayer(layer)
|
||||
}
|
||||
|
||||
populateLayer(layer) {
|
||||
for (const block of this.instance.getLayerBlocks(layer))
|
||||
this.countBlock(block)
|
||||
}
|
||||
|
||||
populateActive() {
|
||||
for (const block of this.instance.getActiveBlocks())
|
||||
this.countBlock(block)
|
||||
}
|
||||
|
||||
countBlock(block) {
|
||||
const itemStack = block?.getItemStack();
|
||||
const typeId = itemStack?.typeId;
|
||||
if (!typeId) return;
|
||||
if (!this.materials[typeId])
|
||||
this.materials[typeId] = { count: 0, stackSize: itemStack.maxAmount };
|
||||
this.materials[typeId].count++;
|
||||
}
|
||||
|
||||
clear() {
|
||||
for (const key in this.materials)
|
||||
delete this.materials[key];
|
||||
}
|
||||
|
||||
toString() {
|
||||
let message = [];
|
||||
for (const blockType in this.materials) {
|
||||
let count = this.materials[blockType].count;
|
||||
let countStr = '';
|
||||
const stackSize = this.materials[blockType].stackSize;
|
||||
const fullShulker = 27 * stackSize;
|
||||
if (count >= fullShulker)
|
||||
countStr = `${Math.floor(count / fullShulker)} sb`;
|
||||
if (count > fullShulker)
|
||||
countStr += ' + ';
|
||||
count %= fullShulker;
|
||||
if (count >= stackSize)
|
||||
countStr += `${Math.floor(count / stackSize)} stack`;
|
||||
if (count > stackSize)
|
||||
countStr += ' + ';
|
||||
count %= stackSize;
|
||||
if (count > 0)
|
||||
countStr += count;
|
||||
message.push(` ${blockType}: ${countStr}`);
|
||||
}
|
||||
return message.sort().join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
export { StructureMaterials };
|
||||
@@ -0,0 +1,97 @@
|
||||
import { forceShow } from '../utils';
|
||||
import { structureCollection } from './Structure/StructureCollection';
|
||||
import { MenuFormBuilder } from './MenuFormBuilder';
|
||||
import { InstanceForm } from './Instance/InstanceForm';
|
||||
import { BuilderForm } from './Builder/BuilderForm';
|
||||
|
||||
export class MenuForm {
|
||||
constructor(player, { jumpToInstance = false, instanceName = void 0 } = {}) {
|
||||
this.player = player;
|
||||
this.show(jumpToInstance, instanceName);
|
||||
}
|
||||
|
||||
async show(jumpToInstance = false, instanceName = void 0) {
|
||||
if (jumpToInstance) {
|
||||
if (!instanceName)
|
||||
instanceName = structureCollection.getStructure(this.player.dimension.id, this.player.location, { useActiveLayer: false })?.getName();
|
||||
if (instanceName) {
|
||||
new InstanceForm(this.player, instanceName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
instanceName = await this.getInstanceNameFromForm();
|
||||
if (!instanceName)
|
||||
return;
|
||||
new InstanceForm(this.player, instanceName);
|
||||
}
|
||||
|
||||
async getInstanceNameFromForm() {
|
||||
try {
|
||||
return forceShow(this.player, MenuFormBuilder.buildAllInstanceName()).then((response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
let selection = response.selection;
|
||||
if (selection === 0) {
|
||||
new BuilderForm(this.player);
|
||||
return void 0;
|
||||
} else {
|
||||
selection--;
|
||||
const selectedInstanceName = structureCollection.getInstanceNames()[selection];
|
||||
return selectedInstanceName || this.createNewInstance();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.message === 'Menu timed out.') {
|
||||
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;
|
||||
try {
|
||||
structureCollection.add(instanceName, structureId);
|
||||
} catch (e) {
|
||||
if (e.name === 'InvalidInstanceError') {
|
||||
this.player.sendMessage(`§cInstance '${instanceName}' already exists. Try again with a new name.`);
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
return instanceName;
|
||||
});
|
||||
}
|
||||
|
||||
async getStructureId() {
|
||||
return MenuFormBuilder.buildAllStructures().show(this.player).then((response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
if (response.selection === structureCollection.getWorldStructureIds().length + 1) {
|
||||
MenuFormBuilder.buildHowTo().show(this.player);
|
||||
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,57 @@
|
||||
import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
|
||||
import { structureCollection } from './Structure/StructureCollection';
|
||||
|
||||
export class MenuFormBuilder {
|
||||
static menuTitle = '§l§2Construct';
|
||||
|
||||
static buildAllInstanceName() {
|
||||
const allInstanceNameForm = new ActionFormData()
|
||||
.title(this.menuTitle)
|
||||
.body('Select an instance:');
|
||||
allInstanceNameForm.button('Builder Settings');
|
||||
structureCollection.getInstanceNames().forEach(instanceName => {
|
||||
allInstanceNameForm.button(`§2${instanceName}`);
|
||||
});
|
||||
allInstanceNameForm.button('Create New Instance');
|
||||
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');
|
||||
allStructuresForm.button('How to Add/Remove Structures');
|
||||
return allStructuresForm;
|
||||
}
|
||||
|
||||
static buildOtherStructure() {
|
||||
return new ModalFormData()
|
||||
.title(this.menuTitle)
|
||||
.textField('Enter the Structure ID:', 'example_structure')
|
||||
.submitButton('Submit');
|
||||
}
|
||||
|
||||
static buildHowTo() {
|
||||
let body = "§aHow to Add Structures:\n"
|
||||
body += "§7- Save a structure using a §fstructure block§7 or the §f/structure§7 command.\n"
|
||||
body += "§7§lOR§r\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.";
|
||||
body += "\n\n§cHow to Remove Structures:\n"
|
||||
body += "§7- Use the §f/structure delete§7 command to remove a structure from the world.\n"
|
||||
return new ActionFormData()
|
||||
.title(this.menuTitle)
|
||||
.body(body);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { world } from "@minecraft/server";
|
||||
|
||||
export class Option {
|
||||
saveToDP(namespace, id, data) {
|
||||
world.setDynamicProperty(`${namespace}:${id}`, JSON.stringify(data));
|
||||
}
|
||||
|
||||
loadFromDP(namespace, id) {
|
||||
try {
|
||||
const options = JSON.parse(world.getDynamicProperty(`${namespace}:${id}`));
|
||||
if (options)
|
||||
Object.assign(this, options);
|
||||
else
|
||||
throw new Error("Options not found");
|
||||
} catch {
|
||||
this.saveToDP(namespace, id, {});
|
||||
const options = JSON.parse(world.getDynamicProperty(`${namespace}:${id}`) || "{}");
|
||||
Object.assign(this, options);
|
||||
}
|
||||
}
|
||||
|
||||
clearDP(namespace, id) {
|
||||
world.setDynamicProperty(`${namespace}:${id}`, void 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { MolangVariableMap, system, world } from "@minecraft/server";
|
||||
import { Vector } from "../lib/Vector";
|
||||
|
||||
export class Outliner {
|
||||
dimension;
|
||||
min = new Vector();
|
||||
max = new Vector();
|
||||
drawParticle = "construct:outline";
|
||||
drawFrequency = 10;
|
||||
|
||||
#drawParticles = [];
|
||||
#runner = void 0;
|
||||
|
||||
constructor(dimension, min, max) {
|
||||
this.dimension = dimension;
|
||||
this.min = Vector.from(min);
|
||||
this.max = Vector.from(max);
|
||||
this.vertices = this.getVertices(min, max);
|
||||
}
|
||||
|
||||
startDraw() {
|
||||
this.#runner = system.runInterval(() => this.draw(), this.drawFrequency);
|
||||
}
|
||||
|
||||
stopDraw() {
|
||||
if (!this.#runner)
|
||||
return;
|
||||
system.clearRun(this.#runner);
|
||||
this.#runner = void 0;
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.drawParticles(this.getVerticeParticles(), () => {
|
||||
return { red: 1, green: 1, blue: 1, alpha: 1 }
|
||||
});
|
||||
this.drawParticles(this.getCubiodEdgeParticles(), this.getNextParticleColor.bind(this));
|
||||
}
|
||||
|
||||
drawParticles(particleLocations, colorCallback) {
|
||||
this.#drawParticles.length = 0;
|
||||
this.#drawParticles.push(...particleLocations);
|
||||
for (const [particleType, location] of this.#drawParticles) {
|
||||
const molang = new MolangVariableMap();
|
||||
molang.setColorRGBA("dot_color", colorCallback());
|
||||
try {
|
||||
this.dimension.spawnParticle(particleType, location, molang);
|
||||
} catch (e) {
|
||||
/* pass */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getVertices(min, max) {
|
||||
return [
|
||||
new Vector(min.x, min.y, min.z),
|
||||
new Vector(max.x, min.y, min.z),
|
||||
new Vector(min.x, max.y, min.z),
|
||||
new Vector(max.x, max.y, min.z),
|
||||
new Vector(min.x, min.y, max.z),
|
||||
new Vector(max.x, min.y, max.z),
|
||||
new Vector(min.x, max.y, max.z),
|
||||
new Vector(max.x, max.y, max.z)
|
||||
];
|
||||
}
|
||||
|
||||
setVertices(dimension, min, max) {
|
||||
this.dimension = dimension;
|
||||
this.min = Vector.from(min);
|
||||
this.max = Vector.from(max);
|
||||
this.vertices = this.getVertices(min, max);
|
||||
}
|
||||
|
||||
getVerticeParticles() {
|
||||
return this.vertices.map((v) => [this.drawParticle, v]);
|
||||
}
|
||||
|
||||
getCubiodEdgeParticles() {
|
||||
const edges = [
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
[0, 4],
|
||||
[1, 3],
|
||||
[1, 5],
|
||||
[2, 3],
|
||||
[2, 6],
|
||||
[3, 7],
|
||||
[4, 5],
|
||||
[4, 6],
|
||||
[5, 7],
|
||||
[6, 7]
|
||||
];
|
||||
const edgePoints = [];
|
||||
for (const edge of edges) {
|
||||
const [startVertex, endVertex] = [this.vertices[edge[0]], this.vertices[edge[1]]];
|
||||
const resolution = Math.min(Math.floor(endVertex.subtract(startVertex).length), 16);
|
||||
for (let i = 1; i < resolution; i++) {
|
||||
const t = i / resolution;
|
||||
edgePoints.push(startVertex.lerp(endVertex, t));
|
||||
}
|
||||
}
|
||||
return edgePoints.map((v) => [this.drawParticle, v]);
|
||||
}
|
||||
|
||||
addStandaloneParticles(locations) {
|
||||
for (const location of locations)
|
||||
this.vertices.push(Vector.from(location));
|
||||
}
|
||||
|
||||
getNextParticleColor() {
|
||||
if (this.lastWasBlack) {
|
||||
this.lastWasBlack = false;
|
||||
return { red: 0.93333333, green: 0.77647059, blue: 0.13333333, alpha: 1 };
|
||||
} else {
|
||||
this.lastWasBlack = true;
|
||||
return { red: 0.09019608, green: 0.09019608, blue: 0.09019608, alpha: 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { structureCollection } from "./Structure/StructureCollection";
|
||||
import { world } from "@minecraft/server";
|
||||
|
||||
export class Raycaster {
|
||||
static STEP_SIZE = 0.2;
|
||||
|
||||
static getStructureBlocks(dimension, startLocation, direction, { maxDistance = 7, getFirst = true, collideWithWorldBlocks = true, useActiveLayer = 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 structure = structureCollection.getStructure(dimension.id, location, { useActiveLayer });
|
||||
if (structure) {
|
||||
const block = structure.getBlock(structure.toStructureCoords(location));
|
||||
if (block?.type.id !== 'minecraft:air') {
|
||||
blocks.push({
|
||||
permutation: block,
|
||||
location: location
|
||||
});
|
||||
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),
|
||||
y: location.y + (direction.y*this.STEP_SIZE),
|
||||
z: location.z + (direction.z*this.STEP_SIZE)
|
||||
};
|
||||
distance += this.STEP_SIZE;
|
||||
}
|
||||
// world.getDimension('minecraft:overworld').spawnParticle('minecraft:villager_happy', location);
|
||||
return blocks;
|
||||
}
|
||||
|
||||
static getTargetedStructureBlock(player, { isFirst = true, collideWithWorldBlocks = true, useActiveLayer = true } = {}) {
|
||||
const startLocation = player.getHeadLocation();
|
||||
const direction = player.getViewDirection();
|
||||
const maxDistance = 7;
|
||||
const blocks = this.getStructureBlocks(player.dimension, startLocation, direction, { maxDistance, getFirst: isFirst, collideWithWorldBlocks, useActiveLayer });
|
||||
if (blocks.length === 0)
|
||||
return void 0;
|
||||
return isFirst ? blocks[0] : blocks[blocks.length - 1];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { world } from "@minecraft/server";
|
||||
import { Vector } from "../../lib/Vector";
|
||||
|
||||
export class Structure {
|
||||
structureId;
|
||||
#structure;
|
||||
|
||||
constructor(structureId) {
|
||||
this.structureId = structureId;
|
||||
this.#structure = world.structureManager.get(structureId);
|
||||
if (!this.#structure)
|
||||
throw new Error(`[Construct] Structure '${structureId}' not found.`);
|
||||
this.#structure.saveToWorld();
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return this.#structure.size.y;
|
||||
}
|
||||
|
||||
getMin() {
|
||||
return new Vector(0, 0, 0);
|
||||
}
|
||||
|
||||
getMax() {
|
||||
return Vector.from(this.#structure.size);
|
||||
}
|
||||
|
||||
getBlock(structureLocation) {
|
||||
const blockPermutation = this.#structure.getBlockPermutation(structureLocation);
|
||||
if (!blockPermutation)
|
||||
return void 0;
|
||||
blockPermutation.location = structureLocation;
|
||||
return blockPermutation;
|
||||
}
|
||||
|
||||
*getBlocks(locations) {
|
||||
for (const location of locations) {
|
||||
yield this.getBlock(location);
|
||||
}
|
||||
}
|
||||
|
||||
*getLayerBlocks(layer) {
|
||||
for (let x = 0; x < this.#structure.size.x; x++) {
|
||||
for (let z = 0; z < this.#structure.size.z; z++) {
|
||||
yield this.getBlock({ x, y: layer, z });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*getAllBlocks() {
|
||||
for (let y = 0; y < this.#structure.size.y; y++) {
|
||||
yield * this.getLayerBlocks(y);
|
||||
}
|
||||
}
|
||||
|
||||
getLayerLocations(layer) {
|
||||
const locations = new Set();
|
||||
for (let x = 0; x < this.#structure.size.x; x++) {
|
||||
for (let z = 0; z < this.#structure.size.z; z++) {
|
||||
locations.add(new Vector(x, layer, z));
|
||||
}
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
|
||||
getAllLocations() {
|
||||
const locations = new Set();
|
||||
for (let y = 0; y < this.#structure.size.y; y++) {
|
||||
const layerLocations = this.getLayerLocations(y);
|
||||
for (const location of layerLocations) {
|
||||
locations.add(location);
|
||||
}
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { InvalidInstanceError } from '../Errors/InvalidInstanceError';
|
||||
import { InstanceOptions } from '../Instance/InstanceOptions';
|
||||
import { StructureInstance } from '../Instance/StructureInstance';
|
||||
import { world } from '@minecraft/server';
|
||||
|
||||
class StructureCollection {
|
||||
structures;
|
||||
|
||||
constructor() {
|
||||
this.structures = {};
|
||||
}
|
||||
|
||||
loadExistingInstances() {
|
||||
world.getDynamicPropertyIds().filter(id => id.startsWith('instanceOptions:')).forEach(id => {
|
||||
const instanceName = id.replace('instanceOptions:', '');
|
||||
let structureId;
|
||||
try {
|
||||
structureId = InstanceOptions.getInstanceStrucetureId(instanceName);
|
||||
this.structures[instanceName] = new StructureInstance(instanceName, structureId);
|
||||
} catch (e) {
|
||||
world.sendMessage(`§c[Construct] 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 InvalidInstanceError(`Instance ${instanceName} already exists.`);
|
||||
const structure = new StructureInstance(instanceName, structureId);
|
||||
this.structures[instanceName] = structure;
|
||||
return structure;
|
||||
}
|
||||
|
||||
get(instanceName) {
|
||||
const structure = this.structures[instanceName];
|
||||
if (!structure)
|
||||
throw new InvalidInstanceError(`Instance ${instanceName} not found.`);
|
||||
return structure;
|
||||
}
|
||||
|
||||
delete(instanceName) {
|
||||
const struct = this.get(instanceName);
|
||||
struct.delete();
|
||||
delete this.structures[instanceName];
|
||||
}
|
||||
|
||||
getInstanceNames() {
|
||||
return Object.keys(this.structures);
|
||||
}
|
||||
|
||||
getStructures(dimensionId, location, options = {}) {
|
||||
return Object.values(this.structures).filter(structure => {
|
||||
try {
|
||||
return structure.isLocationActive(dimensionId, structure.toStructureCoords(location), options)
|
||||
} catch (e) {
|
||||
if (e.name === 'InvalidStructureError') {
|
||||
structureCollection.delete(structure.name);
|
||||
return false;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
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();
|
||||
|
||||
world.afterEvents.worldLoad.subscribe(() => {
|
||||
structureCollection.loadExistingInstances();
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Outliner } from '../Outliner';
|
||||
|
||||
export class StructureOutliner {
|
||||
constructor(instance) {
|
||||
this.instance = instance;
|
||||
this.pullInstanceData();
|
||||
this.outliner = new Outliner(this.dimension, this.bounds.min, this.bounds.max);
|
||||
}
|
||||
|
||||
pullInstanceData() {
|
||||
try {
|
||||
this.dimension = this.instance.getDimension();
|
||||
this.bounds = this.instance.getBounds();
|
||||
this.bounds.min = this.instance.toGlobalCoords(this.bounds.min);
|
||||
this.bounds.max = this.instance.toGlobalCoords(this.bounds.max);
|
||||
} catch (e) {
|
||||
if (e.name === 'InvalidStructureError')
|
||||
this.outliner.stopDraw();
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.pullInstanceData();
|
||||
this.refreshDraw();
|
||||
}
|
||||
|
||||
refreshDraw() {
|
||||
this.outliner.stopDraw();
|
||||
if (!this.instance.isEnabled())
|
||||
return;
|
||||
if (this.instance.hasLayerSelected())
|
||||
this.layeredDraw();
|
||||
else
|
||||
this.boxDraw();
|
||||
this.outliner.startDraw();
|
||||
}
|
||||
|
||||
boxDraw() {
|
||||
this.outliner.setVertices(this.dimension, this.bounds.min, this.bounds.max);
|
||||
}
|
||||
|
||||
layeredDraw() {
|
||||
const { min, max } = this.instance.getLayerBounds(this.instance.getLayer());
|
||||
this.outliner.setVertices(this.dimension, this.instance.toGlobalCoords(min), this.instance.toGlobalCoords(max));
|
||||
this.outliner.addStandaloneParticles(this.getCornerVertices());
|
||||
}
|
||||
|
||||
getCornerVertices() {
|
||||
return this.outliner.getVertices(this.bounds.min, this.bounds.max);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { BlockVerificationLevel } from '../Enums/BlockVerificationLevel.js';
|
||||
|
||||
export class StructureStatistics {
|
||||
constructor(instance, verification) {
|
||||
this.instance = instance;
|
||||
this.verification = verification;
|
||||
this.parse();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.statistics = {};
|
||||
for (const verificationLevel of Object.values(BlockVerificationLevel)) {
|
||||
this.statistics[verificationLevel] = 0;
|
||||
}
|
||||
this.statistics.correctlyAir = 0;
|
||||
}
|
||||
|
||||
parse() {
|
||||
this.init();
|
||||
for (const blockVerificationLevel of Object.values(BlockVerificationLevel)) {
|
||||
this.parseStatistic(blockVerificationLevel);
|
||||
}
|
||||
}
|
||||
|
||||
parseStatistic(blockVerificationLevel) {
|
||||
for (const verificationlevel of Object.values(this.verification)) {
|
||||
if (blockVerificationLevel === verificationlevel) {
|
||||
this.statistics[verificationlevel]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getNonAirBlocks() {
|
||||
const activeBounds = this.instance.getActiveBounds();
|
||||
return activeBounds.min.volume(activeBounds.max) - this.verification.correctlyAir;
|
||||
}
|
||||
|
||||
getStat(blockVerificationLevel) {
|
||||
return { num: this.statistics[blockVerificationLevel], percent: this.statistics[blockVerificationLevel] / (this.getNonAirBlocks()) * 100 };
|
||||
}
|
||||
|
||||
getSkipped() {
|
||||
return this.statistics[BlockVerificationLevel.Skipped] || 0;
|
||||
}
|
||||
|
||||
getMessage() {
|
||||
let message = '';
|
||||
message += `§fStatistics for §a${this.instance.getName()}§f:`;
|
||||
if (this.instance.hasLayerSelected())
|
||||
message += ` §7(layer ${this.instance.getLayer()})`;
|
||||
message += `\n§7Blocks: §2${this.getNonAirBlocks()}\n`;
|
||||
const skipped = this.getSkipped();
|
||||
if (skipped > 0)
|
||||
message += `§c[!] This analysis skipped ${skipped} blocks.\n`;
|
||||
message += `§7Correct: §a${this.formatStat(this.getStat(BlockVerificationLevel.Match))}\n`;
|
||||
message += `§7Block State Incorrect: §e${this.formatStat(this.getStat(BlockVerificationLevel.TypeMatch))}\n`;
|
||||
message += `§7Incorrect: §c${this.formatStat(this.getStat(BlockVerificationLevel.NoMatch))}\n`;
|
||||
message += `§7Missing: §3${this.formatStat(this.getStat(BlockVerificationLevel.Missing))}\n`;
|
||||
return message;
|
||||
}
|
||||
|
||||
formatStat(stat) {
|
||||
return `${stat.num} (${stat.percent.toFixed(2)}%%)`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { MolangVariableMap } from "@minecraft/server";
|
||||
import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel";
|
||||
import { Vector } from "../../lib/Vector";
|
||||
|
||||
export class BlockVerificationLevelRender {
|
||||
opacity = 0.2;
|
||||
lifetimeSeconds = 0;
|
||||
|
||||
constructor(dimensionLocation, verificationLevel, lifetimeSeconds = 5) {
|
||||
this.dimension = dimensionLocation.dimension;
|
||||
this.location = Vector.from(dimensionLocation.location);
|
||||
this.verificationLevel = verificationLevel;
|
||||
this.lifetimeSeconds = lifetimeSeconds;
|
||||
this.renderBlock();
|
||||
}
|
||||
|
||||
renderBlock() {
|
||||
for (const particleLocation of this.getParticleLocations()) {
|
||||
const color = this.getRGBAMolang();
|
||||
if (!color)
|
||||
return;
|
||||
color.setFloat("lifetime", this.lifetimeSeconds);
|
||||
try {
|
||||
this.dimension.spawnParticle(particleLocation.particleType, particleLocation.location, color);
|
||||
} catch {
|
||||
/* pass */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getParticleLocations() {
|
||||
const bottomFace = new Vector(0.5, 0, 0.5);
|
||||
const topFace = new Vector(0.5, 1, 0.5);
|
||||
const leftFace = new Vector(1, 0.5, 0.5);
|
||||
const rightFace = new Vector(0, 0.5, 0.5);
|
||||
const frontFace = new Vector(0.5, 0.5, 1);
|
||||
const backFace = new Vector(0.5, 0.5, 0);
|
||||
return [
|
||||
{ particleType: "construct:blockoverlay_xz", location: this.location.add(topFace) },
|
||||
{ particleType: "construct:blockoverlay_xz", location: this.location.add(bottomFace) },
|
||||
{ particleType: "construct:blockoverlay_yz", location: this.location.add(leftFace) },
|
||||
{ particleType: "construct:blockoverlay_yz", location: this.location.add(rightFace) },
|
||||
{ particleType: "construct:blockoverlay_xy", location: this.location.add(frontFace) },
|
||||
{ particleType: "construct:blockoverlay_xy", location: this.location.add(backFace) }
|
||||
];
|
||||
}
|
||||
|
||||
getRGBAMolang() {
|
||||
const rgb = this.verificationLevelToRGB();
|
||||
if (!rgb) return;
|
||||
rgb.alpha = this.opacity;
|
||||
const molang = new MolangVariableMap();
|
||||
molang.setColorRGBA("face_color", rgb);
|
||||
return molang;
|
||||
}
|
||||
|
||||
verificationLevelToRGB() {
|
||||
switch (this.verificationLevel) {
|
||||
case BlockVerificationLevel.NoMatch:
|
||||
return { red: 1, green: 0, blue: 0};
|
||||
case BlockVerificationLevel.TypeMatch:
|
||||
return { red: 1, green: 1, blue: 0};
|
||||
case BlockVerificationLevel.Missing:
|
||||
return { red: 0, green: 0, blue: 1};
|
||||
default:
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel";
|
||||
|
||||
export class BlockVerifier {
|
||||
constructor(block, instance) {
|
||||
this.block = block;
|
||||
this.instance = instance;
|
||||
this.blockLocationInStructure = instance.toStructureCoords(block.location);
|
||||
}
|
||||
|
||||
verify() {
|
||||
const structPermutation = this.instance.getBlock(this.blockLocationInStructure);
|
||||
return this.evaluatePermutations(this.block.permutation, structPermutation);
|
||||
}
|
||||
|
||||
evaluatePermutations(worldPermutation, structPermutation) {
|
||||
if (this.isCorrectlyAir(worldPermutation, structPermutation))
|
||||
return BlockVerificationLevel.Air;
|
||||
if (this.isMissing(worldPermutation, structPermutation))
|
||||
return BlockVerificationLevel.Missing;
|
||||
if (this.isExactMatch(worldPermutation, structPermutation))
|
||||
return BlockVerificationLevel.Match;
|
||||
if (this.isTypeMatch(worldPermutation, structPermutation))
|
||||
return BlockVerificationLevel.TypeMatch;
|
||||
return BlockVerificationLevel.NoMatch;
|
||||
}
|
||||
|
||||
isCorrectlyAir(worldPermutation, structPermutation) {
|
||||
return worldPermutation.type.id === "minecraft:air" && structPermutation.type.id === "minecraft:air";
|
||||
}
|
||||
|
||||
isMissing(worldPermutation, structPermutation) {
|
||||
return worldPermutation.type.id === "minecraft:air" && structPermutation.type.id !== "minecraft:air";
|
||||
}
|
||||
|
||||
isTypeMatch(worldPermuation, structurePermuation) {
|
||||
return worldPermuation.type.id === structurePermuation.type.id;
|
||||
}
|
||||
|
||||
isExactMatch(worldPermuation, structurePermuation) {
|
||||
return worldPermuation.matches(structurePermuation.type.id, structurePermuation.getAllStates());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { BlockVerifier } from "./BlockVerifier";
|
||||
import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel";
|
||||
import { BlockVerificationLevelRender } from "../Verifier/BlockVerificationLevelRender";
|
||||
import { system, TicksPerSecond } from "@minecraft/server";
|
||||
import { Vector } from "../../lib/Vector";
|
||||
|
||||
const MIN_TRACK_PLAYER_DISTANCE = 0;
|
||||
const MAX_TRACK_PLAYER_DISTANCE = 7;
|
||||
const MIN_LIFETIME = 8;
|
||||
|
||||
export class StructureVerifier {
|
||||
instance;
|
||||
intervalOrLifetime;
|
||||
|
||||
locationsToVerify;
|
||||
blockVerificationLevels;
|
||||
isLocationPopulationComplete;
|
||||
isVerificationComplete;
|
||||
|
||||
#runner;
|
||||
#verifyJob;
|
||||
#populateJob = {};
|
||||
|
||||
constructor(instance, { isEnabled = false, trackPlayerDistance = 0, intervalOrLifetime = 10, isStandalone: isIndependent = false } = {}) {
|
||||
this.instance = instance;
|
||||
this.intervalOrLifetime = Math.max(intervalOrLifetime, MIN_LIFETIME);
|
||||
if (isIndependent) {
|
||||
this.isIndependent = isIndependent;
|
||||
this.enabled = isEnabled;
|
||||
this.trackPlayerDistance = trackPlayerDistance;
|
||||
} else {
|
||||
this.instance.options.setVerifierEnabled(isEnabled);
|
||||
this.instance.options.setVerifierDistance(trackPlayerDistance);
|
||||
}
|
||||
this.locationsToVerify = new Set();
|
||||
}
|
||||
|
||||
startContinuousVerification() {
|
||||
this.#runner = system.runInterval(() => {
|
||||
this.verifyStructure();
|
||||
}, this.intervalOrLifetime);
|
||||
}
|
||||
|
||||
stopContinuousVerification() {
|
||||
if (!this.#runner)
|
||||
return;
|
||||
system.clearRun(this.#runner);
|
||||
this.#runner = void 0;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.stopContinuousVerification();
|
||||
if (!this.instance.isEnabled())
|
||||
return;
|
||||
this.startContinuousVerification();
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
if (this.isIndependent)
|
||||
return this.enabled;
|
||||
return this.instance.options.verifier.isEnabled;
|
||||
}
|
||||
|
||||
getTrackPlayerDistance() {
|
||||
let distance;
|
||||
if (this.isIndependent)
|
||||
distance = this.trackPlayerDistance;
|
||||
else
|
||||
distance = this.instance.options.verifier.trackPlayerDistance
|
||||
return Math.min(MAX_TRACK_PLAYER_DISTANCE, Math.max(MIN_TRACK_PLAYER_DISTANCE, distance));
|
||||
}
|
||||
|
||||
init() {
|
||||
this.locationsToVerify.clear();
|
||||
this.blockVerificationLevels = { correctlyAir: 0 };
|
||||
this.isLocationPopulationComplete = false;
|
||||
this.isVerificationComplete = false;
|
||||
}
|
||||
|
||||
async verifyStructure(shouldRender = true) {
|
||||
if (!this.isEnabled())
|
||||
return;
|
||||
this.init();
|
||||
return new Promise(async (resolve) => {
|
||||
await this.populateLocationsToVerify();
|
||||
if (this.#verifyJob)
|
||||
system.clearJob(this.#verifyJob);
|
||||
this.verifyJob = system.runJob(this.verifyBlocks(this.locationsToVerify, shouldRender));
|
||||
const checker = system.runInterval(() => {
|
||||
if (this.isVerificationComplete) {
|
||||
system.clearRun(checker);
|
||||
resolve(this.blockVerificationLevels);
|
||||
}
|
||||
}, 1);
|
||||
});
|
||||
}
|
||||
|
||||
async populateLocationsToVerify() {
|
||||
return new Promise((resolve) => {
|
||||
if (this.getTrackPlayerDistance() === 0) {
|
||||
this.locationsToVerify = this.instance.getAllActiveLocations();
|
||||
resolve();
|
||||
} else {
|
||||
for (const job of Object.values(this.#populateJob))
|
||||
system.clearJob(job);
|
||||
for (const player of this.instance.getDimension().getPlayers())
|
||||
this.#populateJob[player.id] = system.runJob(this.populateActiveLocationsNearPlayer(player));
|
||||
const checker = system.runInterval(() => {
|
||||
if (this.isLocationPopulationComplete) {
|
||||
system.clearRun(checker);
|
||||
resolve();
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
*populateActiveLocationsNearPlayer(player) {
|
||||
const distance = this.getTrackPlayerDistance();
|
||||
for (let x = -distance; x < distance; x++) {
|
||||
for (let y = -distance; y < distance; y++) {
|
||||
for (let z = -distance; z < distance; z++) {
|
||||
const worldLocation = Vector.from(player.location).add(new Vector(x, y, z)).floor();;
|
||||
const structureLocation = this.instance.toStructureCoords(worldLocation);
|
||||
if (this.instance.isLocationActive(player.dimension.id, structureLocation, { useActiveLayer: true })) {
|
||||
this.locationsToVerify.add(structureLocation);
|
||||
}
|
||||
yield void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.isLocationPopulationComplete = true;
|
||||
}
|
||||
|
||||
*verifyBlocks(locations, shouldRender) {
|
||||
for (const location of locations) {
|
||||
const verificationLevel = this.verifyBlock(location);
|
||||
if (verificationLevel === BlockVerificationLevel.Air) {
|
||||
this.blockVerificationLevels.correctlyAir++;
|
||||
} else {
|
||||
this.blockVerificationLevels[JSON.stringify(location)] = verificationLevel;
|
||||
if (shouldRender) {
|
||||
const dimensionLocation = { dimension: this.instance.getDimension(), location: this.instance.toGlobalCoords(location) };
|
||||
new BlockVerificationLevelRender(dimensionLocation, verificationLevel, this.intervalOrLifetime/TicksPerSecond);
|
||||
}
|
||||
}
|
||||
yield void 0;
|
||||
}
|
||||
this.isVerificationComplete = true;
|
||||
}
|
||||
|
||||
verifyBlock(location) {
|
||||
const worldBlock = this.instance.getDimension().getBlock(this.instance.toGlobalCoords(location));
|
||||
if (!worldBlock)
|
||||
return BlockVerificationLevel.Skipped;
|
||||
const blockVerifier = new BlockVerifier(worldBlock, this.instance);
|
||||
return blockVerifier.verify();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user