Refactoring StructureInstance

This commit is contained in:
ForestOfLight
2025-04-18 01:22:50 -07:00
Unverified
parent 35b7575d2f
commit 80aed5f349
16 changed files with 372 additions and 313 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ class BlockInfo {
} }
static showStructureBlockInfo(player) { static showStructureBlockInfo(player) {
const block = Raycaster.getTargetedStructureBlock(player, { isFirst: true, collideWithWorldBlocks: true, useLayers: false }); const block = Raycaster.getTargetedStructureBlock(player, { isFirst: true, collideWithWorldBlocks: true, useActiveLayer: false });
if (!block && this.shownToLastTick.has(player.id)) { if (!block && this.shownToLastTick.has(player.id)) {
player.onScreenDisplay.setActionBar({ text: 'Structure:\n§7None' }); player.onScreenDisplay.setActionBar({ text: 'Structure:\n§7None' });
this.shownToLastTick.delete(player.id); this.shownToLastTick.delete(player.id);
@@ -4,13 +4,13 @@ import { Vector } from "../lib/Vector";
export class BlockVerificationLevelRender { export class BlockVerificationLevelRender {
opacity = 0.2; opacity = 0.2;
lifetime = 0; lifetimeSeconds = 0;
constructor(dimensionLocation, verificationLevel, lifetime = 5) { constructor(dimensionLocation, verificationLevel, lifetimeSeconds = 5) {
this.dimension = dimensionLocation.dimension; this.dimension = dimensionLocation.dimension;
this.location = new Vector(dimensionLocation.location.x, dimensionLocation.location.y, dimensionLocation.location.z); this.location = Vector.from(dimensionLocation.location);
this.verificationLevel = verificationLevel; this.verificationLevel = verificationLevel;
this.lifetime = lifetime; this.lifetimeSeconds = lifetimeSeconds;
this.renderBlock(); this.renderBlock();
} }
@@ -19,7 +19,7 @@ export class BlockVerificationLevelRender {
const color = this.getRGBAMolang(); const color = this.getRGBAMolang();
if (!color) if (!color)
return; return;
color.setFloat("lifetime", this.lifetime); color.setFloat("lifetime", this.lifetimeSeconds);
try { try {
this.dimension.spawnParticle(particleLocation.particleType, particleLocation.location, color); this.dimension.spawnParticle(particleLocation.particleType, particleLocation.location, color);
} catch { } catch {
@@ -1,7 +1,7 @@
import { structureCollection } from './StructureCollection'; import { structureCollection } from './StructureCollection';
import { MenuForm } from '../classes/MenuForm'; import { MenuForm } from '../classes/MenuForm';
import { forceShow } from '../utils'; import { forceShow } from '../utils';
import { InstanceEditOptions } from './enums/InstanceEditOptions'; import { InstanceEditButtons } from './enums/InstanceEditButtons';
import { InstanceEditFormBuilder } from './InstanceEditFormBuilder'; import { InstanceEditFormBuilder } from './InstanceEditFormBuilder';
import { FormCancelationReason } from '@minecraft/server-ui'; import { FormCancelationReason } from '@minecraft/server-ui';
@@ -9,26 +9,26 @@ export class InstanceEditForm {
instanceName; instanceName;
#buttons = { #buttons = {
isEnabled: [ isEnabled: [
InstanceEditOptions.NextLayer, InstanceEditButtons.NextLayer,
InstanceEditOptions.PreviousLayer, InstanceEditButtons.PreviousLayer,
InstanceEditOptions.SetLayer, InstanceEditButtons.SetLayer,
InstanceEditOptions.Move, InstanceEditButtons.Move,
InstanceEditOptions.Statistics, InstanceEditButtons.Statistics,
InstanceEditOptions.Settings, InstanceEditButtons.Settings,
InstanceEditOptions.Rename, InstanceEditButtons.Rename,
InstanceEditOptions.Disable, InstanceEditButtons.Disable,
], ],
isNotEnabledAndIsNotPlaced: [ isNotEnabledAndIsNotPlaced: [
InstanceEditOptions.Place, InstanceEditButtons.Place,
InstanceEditOptions.Rename InstanceEditButtons.Rename
], ],
isNotEnabledButIsPlaced: [ isNotEnabledButIsPlaced: [
InstanceEditOptions.Enable, InstanceEditButtons.Enable,
InstanceEditOptions.Rename InstanceEditButtons.Rename
], ],
common: [ common: [
InstanceEditOptions.Delete, InstanceEditButtons.Delete,
InstanceEditOptions.MainMenu InstanceEditButtons.MainMenu
] ]
} }
@@ -59,51 +59,51 @@ export class InstanceEditForm {
if (!this.instance.hasLayers()) if (!this.instance.hasLayers())
currentOptions = currentOptions.filter(option => currentOptions = currentOptions.filter(option =>
option !== InstanceEditOptions.SetLayer option !== InstanceEditButtons.SetLayer
&& option !== InstanceEditOptions.NextLayer && option !== InstanceEditButtons.NextLayer
&& option !== InstanceEditOptions.PreviousLayer && option !== InstanceEditButtons.PreviousLayer
); );
return currentOptions; return currentOptions;
} }
handleOption(option) { handleOption(option) {
switch (option) { switch (option) {
case InstanceEditOptions.Enable: case InstanceEditButtons.Enable:
this.instance.enable(); this.instance.enable();
break; break;
case InstanceEditOptions.Disable: case InstanceEditButtons.Disable:
this.instance.disable(); this.instance.disable();
break; break;
case InstanceEditOptions.Place: case InstanceEditButtons.Place:
this.instance.place(this.player.dimension.id, this.player.location); this.instance.place(this.player.dimension.id, this.player.location);
break; break;
case InstanceEditOptions.Rename: case InstanceEditButtons.Rename:
this.renameInstanceForm(); this.renameInstanceForm();
break; break;
case InstanceEditOptions.Delete: case InstanceEditButtons.Delete:
structureCollection.delete(this.instanceName); structureCollection.delete(this.instanceName);
break; break;
case InstanceEditOptions.NextLayer: case InstanceEditButtons.NextLayer:
this.instance.increaseLayer(); this.instance.increaseLayer();
new InstanceEditForm(this.player, this.instanceName); new InstanceEditForm(this.player, this.instanceName);
break; break;
case InstanceEditOptions.PreviousLayer: case InstanceEditButtons.PreviousLayer:
this.instance.decreaseLayer(); this.instance.decreaseLayer();
new InstanceEditForm(this.player, this.instanceName); new InstanceEditForm(this.player, this.instanceName);
break; break;
case InstanceEditOptions.SetLayer: case InstanceEditButtons.SetLayer:
this.setLayerForm(); this.setLayerForm();
break; break;
case InstanceEditOptions.Move: case InstanceEditButtons.Move:
this.instance.move(this.player.dimension.id, this.player.location); this.instance.move(this.player.dimension.id, this.player.location);
break; break;
case InstanceEditOptions.Statistics: case InstanceEditButtons.Statistics:
this.statisticsForm(); this.statisticsForm();
break; break;
case InstanceEditOptions.MainMenu: case InstanceEditButtons.MainMenu:
new MenuForm(this.player, { jumpToInstance: false }); new MenuForm(this.player, { jumpToInstance: false });
break; break;
case InstanceEditOptions.Settings: case InstanceEditButtons.Settings:
this.settingsForm(); this.settingsForm();
break; break;
default: default:
@@ -135,8 +135,7 @@ export class InstanceEditForm {
InstanceEditFormBuilder.buildSetLayer(this.instance.getBounds().max.y, this.instance.getLayer()).show(this.player).then((response) => { InstanceEditFormBuilder.buildSetLayer(this.instance.getBounds().max.y, this.instance.getLayer()).show(this.player).then((response) => {
if (response.canceled) if (response.canceled)
return; return;
const selectedLayer = response.formValues[0]; this.instance.setLayer(parseInt(response.formValues[0]));
this.instance.setLayer(parseInt(selectedLayer));
}); });
} }
@@ -152,9 +151,8 @@ export class InstanceEditForm {
InstanceEditFormBuilder.buildSettings(this.instance).show(this.player).then((response) => { InstanceEditFormBuilder.buildSettings(this.instance).show(this.player).then((response) => {
if (response.canceled) if (response.canceled)
return; return;
const shouldRender = response.formValues[0]; this.instance.setVerifierEnabled(response.formValues[0]);
const trackPlayerDistance = response.formValues[1]; this.instance.setVerifierDistance(response.formValues[1]);
this.instance.setVerifierOptions({ shouldRender, trackPlayerDistance });
}); });
} }
} }
@@ -9,7 +9,7 @@ export class InstanceEditFormBuilder {
const location = instance.getLocation(); const location = instance.getLocation();
const form = new ActionFormData() const form = new ActionFormData()
.title(MenuFormBuilder.menuTitle) .title(MenuFormBuilder.menuTitle)
let body = `Instance: §a${instance.name}\n§fStructure: §2${instance.getStructureId()}\n`; let body = `Instance: §a${instance.name}\n§fStructure: §2${instance.options.getStructureId()}\n`;
if (instance.hasLocation()) if (instance.hasLocation())
body += `§7(${location.location.x} ${location.location.y} ${location.location.z} in ${location.dimensionId})\n`; body += `§7(${location.location.x} ${location.location.y} ${location.location.z} in ${location.dimensionId})\n`;
form.body(body); form.body(body);
@@ -37,7 +37,7 @@ export class InstanceEditFormBuilder {
static async buildStatistics(instance) { static async buildStatistics(instance) {
const buildStatisticsForm = new ActionFormData() const buildStatisticsForm = new ActionFormData()
.title(MenuFormBuilder.menuTitle) .title(MenuFormBuilder.menuTitle)
const structureVerifier = new StructureVerifier(instance, { shouldRender: true, trackPlayerDistance: 0, intervalOrLifetime: 30 * TicksPerSecond }); const structureVerifier = new StructureVerifier(instance, { isEnabled: true, trackPlayerDistance: 0, intervalOrLifetime: 30 * TicksPerSecond });
const verification = await structureVerifier.verifyStructure(); const verification = await structureVerifier.verifyStructure();
const statistics = new StructureStatistics(instance, verification); const statistics = new StructureStatistics(instance, verification);
const statsMessage = statistics.getMessage(); const statsMessage = statistics.getMessage();
@@ -48,7 +48,7 @@ export class InstanceEditFormBuilder {
static buildSettings(instance) { static buildSettings(instance) {
return new ModalFormData() return new ModalFormData()
.title(MenuFormBuilder.menuTitle) .title(MenuFormBuilder.menuTitle)
.toggle('Toggle block validation.', instance.verifier.shouldRender) .toggle('Toggle block validation.', instance.options.verifier.isEnabled)
.slider('Use the slider to restrict how far from players block validation occurs. Use 0 for no restriction.', 0, 10, 1, instance.verifier.trackPlayerDistance) .slider('Use the slider to restrict how far from players block validation occurs. Use 0 for no restriction.', 0, 10, 1, instance.verifier.trackPlayerDistance)
.submitButton('§aApply'); .submitButton('§aApply');
} }
@@ -0,0 +1,85 @@
import { Vector } from "../lib/Vector";
export class InstanceOptions {
instanceName = void 0;
structureId = void 0;
isEnabled = false;
dimensionId = void 0;
worldLocation = new Vector();
currentLayer = 0;
verifier = {
isEnabled: true,
trackPlayerDistance: 5,
intervalOrLifetime: 10
};
constructor(instanceName, structureId) {
this.instanceName = instanceName;
this.structureId = structureId;
this.load();
}
save() {
world.setDynamicProperty(`instanceOptions:${this.instanceName}`, JSON.stringify(this));
}
load() {
try {
const options = JSON.parse(world.getDynamicProperty(`instanceOptions:${this.instanceName}`));
if (options)
Object.assign(this, options);
else
throw new Error("Options not found");
} catch {
this.save();
const options = JSON.parse(world.getDynamicProperty(`instanceOptions:${this.instanceName}`) || "{}");
Object.assign(this, options);
}
this.worldLocation = Vector.from(this.worldLocation);
}
clear() {
world.setDynamicProperty(`instanceOptions:${this.instanceName}`, void 0);
}
getDimension() {
return world.getDimension(this.options.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();
}
}
@@ -4,7 +4,7 @@ class MaterialCounter {
static getAll(name) { static getAll(name) {
const structure = structureCollection.get(name); const structure = structureCollection.get(name);
const materials = {}; const materials = {};
for (const block of structure.getBlocks()) { for (const block of structure.getAllBlocks()) {
const typeId = block?.getItemStack()?.typeId.replace('minecraft:', ''); const typeId = block?.getItemStack()?.typeId.replace('minecraft:', '');
if (!typeId) continue; if (!typeId) continue;
if (!materials[typeId]) { if (!materials[typeId]) {
@@ -32,7 +32,7 @@ class MaterialCounter {
static getPrintable(name) { static getPrintable(name) {
const structure = structureCollection.get(name); const structure = structureCollection.get(name);
const materials = {}; const materials = {};
for (const block of structure.getBlocks()) { for (const block of structure.getAllBlocks()) {
const itemStack = block?.getItemStack(); const itemStack = block?.getItemStack();
const typeId = itemStack?.typeId.replace('minecraft:', ''); const typeId = itemStack?.typeId.replace('minecraft:', '');
if (!typeId) continue; if (!typeId) continue;
+1 -1
View File
@@ -12,7 +12,7 @@ export class MenuForm {
async show(jumpToInstance = true) { async show(jumpToInstance = true) {
let instanceName; let instanceName;
if (jumpToInstance) { if (jumpToInstance) {
instanceName = structureCollection.getStructure(this.player.dimension.id, this.player.location, { useLayers: false })?.name; instanceName = structureCollection.getStructure(this.player.dimension.id, this.player.location, { useActiveLayer: false })?.name;
if (instanceName) { if (instanceName) {
new InstanceEditForm(this.player, instanceName); new InstanceEditForm(this.player, instanceName);
return; return;
+5 -5
View File
@@ -13,8 +13,8 @@ export class Outliner {
constructor(dimension, min, max) { constructor(dimension, min, max) {
this.dimension = dimension; this.dimension = dimension;
this.min = new Vector(min.x, min.y, min.z); this.min = Vector.from(min);
this.max = new Vector(max.x, max.y, max.z); this.max = Vector.from(max);
this.vertices = this.getVertices(min, max); this.vertices = this.getVertices(min, max);
} }
@@ -65,8 +65,8 @@ export class Outliner {
setVertices(dimension, min, max) { setVertices(dimension, min, max) {
this.dimension = dimension; this.dimension = dimension;
this.min = new Vector(min.x, min.y, min.z); this.min = Vector.from(min);
this.max = new Vector(max.x, max.y, max.z); this.max = Vector.from(max);
this.vertices = this.getVertices(min, max); this.vertices = this.getVertices(min, max);
} }
@@ -103,7 +103,7 @@ export class Outliner {
addStandaloneParticles(locations) { addStandaloneParticles(locations) {
for (const location of locations) for (const location of locations)
this.vertices.push(new Vector(location.x, location.y, location.z)); this.vertices.push(Vector.from(location));
} }
getNextParticleColor() { getNextParticleColor() {
+4 -4
View File
@@ -4,13 +4,13 @@ import { world } from "@minecraft/server";
export class Raycaster { export class Raycaster {
static STEP_SIZE = 0.2; static STEP_SIZE = 0.2;
static getStructureBlocks(dimension, startLocation, direction, { maxDistance = 7, getFirst = true, collideWithWorldBlocks = true, useLayers = true }) { 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 // Can probably be optimized by the fact that we only need full blocks and aren't checking for partial blocks
const blocks = []; const blocks = [];
let location = startLocation; let location = startLocation;
let distance = 0; let distance = 0;
while (distance < maxDistance) { while (distance < maxDistance) {
const structure = structureCollection.getStructure(dimension.id, location, { useLayers }); const structure = structureCollection.getStructure(dimension.id, location, { useActiveLayer });
if (structure) { if (structure) {
const block = structure.getBlock(structure.toStructureCoords(location)); const block = structure.getBlock(structure.toStructureCoords(location));
if (block?.type.id !== 'minecraft:air') { if (block?.type.id !== 'minecraft:air') {
@@ -41,11 +41,11 @@ export class Raycaster {
return blocks; return blocks;
} }
static getTargetedStructureBlock(player, { isFirst = true, collideWithWorldBlocks = true, useLayers = true } = {}) { static getTargetedStructureBlock(player, { isFirst = true, collideWithWorldBlocks = true, useActiveLayer = true } = {}) {
const startLocation = player.getHeadLocation(); const startLocation = player.getHeadLocation();
const direction = player.getViewDirection(); const direction = player.getViewDirection();
const maxDistance = 7; const maxDistance = 7;
const blocks = this.getStructureBlocks(player.dimension, startLocation, direction, { maxDistance, getFirst: isFirst, collideWithWorldBlocks, useLayers }); const blocks = this.getStructureBlocks(player.dimension, startLocation, direction, { maxDistance, getFirst: isFirst, collideWithWorldBlocks, useActiveLayer });
if (blocks.length === 0) if (blocks.length === 0)
return void 0; return void 0;
return isFirst ? blocks[0] : blocks[blocks.length - 1]; return isFirst ? blocks[0] : blocks[blocks.length - 1];
@@ -0,0 +1,56 @@
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(`[StrucTool] 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(y) {
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, z });
}
}
}
*getAllBlocks() {
for (let y = 0; y < this.#structure.size.y; y++) {
yield * this.getLayerBlocks(y);
}
}
}
@@ -1,191 +1,28 @@
import { world } from "@minecraft/server";
import { Vector } from "../lib/Vector"; import { Vector } from "../lib/Vector";
import { StructureOutliner } from "./StructureOutliner"; import { StructureOutliner } from "./StructureOutliner";
import { StructureVerifier } from "./StructureVerifier"; import { StructureVerifier } from "./StructureVerifier";
import { InstanceOptions } from "./InstanceOptions";
import { Structure } from "./Structure";
export class StructureInstance { export class StructureInstance {
name; options;
#structure; structure = void 0;
#options = {
structureId: void 0,
isEnabled: false,
dimensionId: void 0,
worldLocation: { x: 0, y: 0, z: 0 },
rotation: 0,
mirror: false,
currentLayer: 0,
verifier: {
shouldRender: true,
trackPlayerDistance: 5,
intervalOrLifetime: 10
}
};
outliner = void 0;
verifier = void 0; verifier = void 0;
outliner = void 0;
constructor(instanceName, structureId) { constructor(instanceName, structureId) {
this.name = instanceName; this.structure = new Structure(structureId);
this.#structure = world.structureManager.get(structureId); this.options = new InstanceOptions(instanceName, structureId);
if (!this.#structure)
throw new Error(`[StrucTool] Structure '${structureId}' not found.`);
this.#structure.saveToWorld();
this.#options = this.loadOptions();
this.#options.structureId = structureId;
this.updateOptions();
this.refreshBox(); this.refreshBox();
} }
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() { delete() {
this.disable(); this.disable();
world.setDynamicProperty(`structOptions:${this.name}`, void 0); this.options.clear();
this.#structure = void 0; delete this.options;
this.#options = void 0; delete this.structure;
delete this.outliner; delete this.outliner;
} delete this.verifier;
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;
}
getDimension() {
let dimension;
try {
dimension = world.getDimension(this.#options.dimensionId);
} catch (e) {
console.warn(`[StrucTool] Dimension '${this.#options.dimensionId}' not found. Defaulting to 'minecraft:overworld'.`);
dimension = world.getDimension("minecraft:overworld");
}
return dimension;
}
getBlock(structureLocation) {
const blockPermutation = this.#structure.getBlockPermutation({ x: structureLocation.x, y: structureLocation.y, z: structureLocation.z });
if (!blockPermutation)
return void 0;
blockPermutation.location = structureLocation;
return blockPermutation;
}
*getBlocks(locations = void 0) {
if (locations === void 0) {
for (let y = 0; y < this.#structure.size.y; y++) {
yield * this.getLayerBlocks(y);
}
} else {
for (const location of locations) {
yield this.getBlock(location);
}
}
}
*getLayerBlocks(y) {
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, z });
}
}
}
getBounds() {
return {
min: new Vector(0, 0, 0),
max: new Vector(this.#structure.size.x, this.#structure.size.y, this.#structure.size.z)
};
}
getLayeredBounds() {
if (!this.#options.isEnabled)
throw new Error(`[StrucTool] Instance '${this.name}' is not placed.`);
return {
min: new Vector(0, this.#options.currentLayer - 1, 0),
max: new Vector(this.#structure.size.x, this.#options.currentLayer, this.#structure.size.z)
};
}
getTotalVolume() {
return this.#structure.size.x * this.#structure.size.y * this.#structure.size.z;
}
getActiveVolume() {
if (!this.#options.isEnabled)
return 0;
if (this.#options.currentLayer === 0)
return this.getTotalVolume();
return this.#structure.size.x * 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.refreshBox();
}
disable() {
this.#options.isEnabled = false;
this.updateOptions();
this.refreshBox();
}
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.refreshBox();
}
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.refreshBox();
} }
refreshBox() { refreshBox() {
@@ -194,101 +31,174 @@ export class StructureInstance {
if (!this.outliner) if (!this.outliner)
this.outliner = new StructureOutliner(this); this.outliner = new StructureOutliner(this);
if (!this.verifier) if (!this.verifier)
this.verifier = new StructureVerifier(this, { shouldRender: this.#options.verifier.shouldRender, trackPlayerDistance: this.#options.verifier.trackPlayerDistance }); this.verifier = new StructureVerifier(this, { isEnabled: this.options.verifier.isEnabled, trackPlayerDistance: this.options.verifier.trackPlayerDistance });
this.outliner.refresh(); this.outliner.refresh();
this.verifier.refresh(); this.verifier.refresh();
} }
isLocationInStructure(dimensionId, structureLocation) { getStructureId() {
if (this.#options.dimensionId !== dimensionId) return this.options.structureId;
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) { getLocation() {
if (!this.#options.isEnabled || this.#options.dimensionId !== dimensionId) return { dimensionId: this.options.dimensionId, location: this.options.worldLocation };
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 } = {}) { getDimension() {
if (!this.#options.isEnabled || this.#options.dimensionId !== dimensionId) return this.options.getDimension();
return false
if (useLayers && this.#options.currentLayer !== 0)
return this.isLocationInLayer(dimensionId, structureLocation);
return this.isLocationInStructure(dimensionId, structureLocation);
} }
toGlobalCoords(structureLocation) { getMaxLayer() {
return new Vector( return this.structure.getHeight();
this.#options.worldLocation.x + structureLocation.x,
this.#options.worldLocation.y + structureLocation.y,
this.#options.worldLocation.z + structureLocation.z
);
} }
toStructureCoords(worldLocation) { getLayer() {
return new Vector( return this.options.currentLayer;
worldLocation.x - this.#options.worldLocation.x, }
worldLocation.y - this.#options.worldLocation.y,
worldLocation.z - this.#options.worldLocation.z getBounds() {
); return {
min: this.structure.getMin(),
max: this.structure.getMax()
}
}
getActiveBounds() {
if (!this.options.isEnabled)
throw new Error(`[StrucTool] Instance '${this.options.instanceName}' is not placed.`);
if (this.hasLayerSelected())
return this.getLayeredBounds();
return this.getBounds();
}
getLayeredBounds() {
if (!this.options.isEnabled)
throw new Error(`[StrucTool] Instance '${this.options.instanceName}' is not placed.`);
const min = this.structure.getMin();
const max = this.structure.getMax();
return {
min: new Vector(min.x, this.options.currentLayer - 1, min.z),
max: new Vector(max.x, this.options.currentLayer, 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();
}
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;
} }
isEnabled() { isEnabled() {
return this.#options.isEnabled; return this.options.isEnabled;
} }
hasLocation() { hasLocation() {
return this.#options.dimensionId && this.#options.worldLocation.x !== 0 && this.#options.worldLocation.y !== 0 && this.#options.worldLocation.z !== 0; return this.options.dimensionId && this.options.worldLocation.x !== 0 && this.options.worldLocation.y !== 0 && this.options.worldLocation.z !== 0;
}
isUsingLayers() {
return this.hasLayers() && this.#options.currentLayer !== 0;
} }
hasLayers() { hasLayers() {
return this.#structure.size.y > 1; return this.getMaxLayer() > 1;
}
hasLayerSelected() {
return this.hasLayers() && this.options.currentLayer !== 0;
}
hasWholeStructureSelected() {
return this.hasLocation() && this.options.currentLayer === 0;
} }
isAtMaxLayer() { isAtMaxLayer() {
return !this.hasLayers || this.#options.currentLayer >= this.#structure.size.y; return !this.hasLayers || this.options.currentLayer >= this.getMaxLayer();
} }
isAtMinLayer() { isAtMinLayer() {
return !this.hasLayers || this.#options.currentLayer <= 0; 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.move(dimensionId, worldLocation);
this.enable();
}
move(dimensionId, worldLocation) {
this.options.move(dimensionId, worldLocation);
this.refreshBox();
}
setLayer(layer) {
if (layer < 0 || layer > this.getMaxLayer())
throw new Error(`[StrucTool] 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);
this.verifier.refresh();
} }
increaseLayer() { increaseLayer() {
if (this.isAtMaxLayer()) if (this.isAtMaxLayer())
this.setLayer(0); this.setLayer(0);
else else
this.setLayer(this.#options.currentLayer + 1); this.setLayer(this.options.currentLayer + 1);
} }
decreaseLayer() { decreaseLayer() {
if (this.isAtMinLayer()) if (this.isAtMinLayer())
this.setLayer(this.#structure.size.y); this.setLayer(this.getMaxLayer());
else else
this.setLayer(this.#options.currentLayer - 1); this.setLayer(this.options.currentLayer - 1);
} }
getDimension() { toGlobalCoords(structureLocation) {
return world.getDimension(this.#options.dimensionId); return Vector.from(structureLocation).add(this.options.worldLocation);
} }
setVerifierOptions({ shouldRender, trackPlayerDistance, intervalOrLifetime }) { toStructureCoords(worldLocation) {
this.#options.verifier.shouldRender = shouldRender; return Vector.from(worldLocation).subtract(this.options.worldLocation);
this.#options.verifier.trackPlayerDistance = trackPlayerDistance;
this.#options.verifier.intervalOrLifetime = intervalOrLifetime;
this.updateOptions();
this.verifier.setOptions(this.#options.verifier);
this.verifier.refresh();
} }
} }
@@ -30,7 +30,7 @@ export class StructureOutliner {
this.outliner.stopDraw(); this.outliner.stopDraw();
if (!this.instance.isEnabled()) if (!this.instance.isEnabled())
return; return;
if (this.instance.isUsingLayers()) if (this.instance.hasLayerSelected())
this.layeredDraw(); this.layeredDraw();
else else
this.boxDraw(); this.boxDraw();
@@ -31,7 +31,8 @@ export class StructureStatistics {
} }
getNonAirBlocks() { getNonAirBlocks() {
return this.instance.getActiveVolume() - this.verification.correctlyAir; const activeBounds = this.instance.getActiveBounds();
return activeBounds.min.volume(activeBounds.max) - this.verification.correctlyAir;
} }
getStat(blockVerificationLevel) { getStat(blockVerificationLevel) {
@@ -45,7 +46,7 @@ export class StructureStatistics {
getMessage() { getMessage() {
let message = ''; let message = '';
message += `§fStatistics for §a${this.instance.name}§f:`; message += `§fStatistics for §a${this.instance.name}§f:`;
if (this.instance.isUsingLayers()) if (this.instance.hasLayerSelected())
message += ` §7(layer ${this.instance.getLayer()})`; message += ` §7(layer ${this.instance.getLayer()})`;
message += `\n§7Blocks: §2${this.getNonAirBlocks()}\n`; message += `\n§7Blocks: §2${this.getNonAirBlocks()}\n`;
const skipped = this.getSkipped(); const skipped = this.getSkipped();
@@ -1,7 +1,7 @@
import { BlockVerifier } from "./BlockVerifier"; import { BlockVerifier } from "./BlockVerifier";
import { BlockVerificationLevel } from "./enums/BlockVerificationLevel"; import { BlockVerificationLevel } from "./enums/BlockVerificationLevel";
import { BlockVerificationLevelRender } from "./BlockVerificationLevelRender"; import { BlockVerificationLevelRender } from "./BlockVerificationLevelRender";
import { system, TicksPerSecond, world } from "@minecraft/server"; import { system, TicksPerSecond } from "@minecraft/server";
const MIN_TRACK_PLAYER_DISTANCE = 0; const MIN_TRACK_PLAYER_DISTANCE = 0;
const MAX_TRACK_PLAYER_DISTANCE = 7; const MAX_TRACK_PLAYER_DISTANCE = 7;
@@ -9,18 +9,20 @@ const MIN_LIFETIME = 8;
export class StructureVerifier { export class StructureVerifier {
instance; instance;
blockVerificationLevels;
shouldRender;
trackPlayerDistance;
intervalOrLifetime; intervalOrLifetime;
locationsToVerify;
blocksToVerify;
blockVerificationLevels;
isBlockPopulationComplete; isBlockPopulationComplete;
isVerificationComplete; isVerificationComplete;
#runner; #runner;
constructor(instance, { shouldRender = false, trackPlayerDistance = 1, intervalOrLifetime = 10 } = {}) { constructor(instance, { isEnabled = false, trackPlayerDistance = 1, intervalOrLifetime = 10 } = {}) {
this.instance = instance; this.instance = instance;
this.intervalOrLifetime = Math.max(intervalOrLifetime, MIN_LIFETIME); this.intervalOrLifetime = Math.max(intervalOrLifetime, MIN_LIFETIME);
this.setOptions({ shouldRender, trackPlayerDistance }); this.instance.options.setVerifierEnabled(isEnabled);
this.instance.options.setVerifierDistance(trackPlayerDistance);
} }
startContinuousVerification() { startContinuousVerification() {
@@ -43,6 +45,14 @@ export class StructureVerifier {
this.startContinuousVerification(); this.startContinuousVerification();
} }
isEnabled() {
return this.instance.options.verifier.isEnabled;
}
getTrackPlayerDistance() {
return Math.min(MAX_TRACK_PLAYER_DISTANCE, Math.max(MIN_TRACK_PLAYER_DISTANCE, this.instance.options.trackPlayerDistance));
}
init() { init() {
this.locationsToVerify = new Set(); this.locationsToVerify = new Set();
this.blocksToVerify = []; this.blocksToVerify = [];
@@ -51,18 +61,13 @@ export class StructureVerifier {
this.isVerificationComplete = false; this.isVerificationComplete = false;
} }
setOptions({ shouldRender = false, trackPlayerDistance = 0 }) { async verifyStructure(shouldRender = true) {
this.blockVerificationLevels = { correctlyAir: 0 }; if (!this.isEnabled())
this.shouldRender = shouldRender; return;
this.trackPlayerDistance = Math.min(trackPlayerDistance, MAX_TRACK_PLAYER_DISTANCE);
this.isVerificationComplete = false;
}
async verifyStructure() {
this.init(); this.init();
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
await this.populateBlocksToVerify(); await this.populateBlocksToVerify();
system.runJob(this.verifyBlocks(this.blocksToVerify)); system.runJob(this.verifyBlocks(this.blocksToVerify, shouldRender));
const checker = system.runInterval(() => { const checker = system.runInterval(() => {
if (this.isVerificationComplete) { if (this.isVerificationComplete) {
system.clearRun(checker); system.clearRun(checker);
@@ -74,8 +79,10 @@ export class StructureVerifier {
populateBlocksToVerify() { populateBlocksToVerify() {
return new Promise((resolve) => { return new Promise((resolve) => {
if (this.trackPlayerDistance == 0) if (this.getTrackPlayerDistance == 0) {
return this.instance.getBlocks(); this.blocksToVerify = this.instance.getAllBlocks();
resolve();
}
this.locationsToVerify = new Set(); this.locationsToVerify = new Set();
for (const player of this.instance.getDimension().getPlayers()) for (const player of this.instance.getDimension().getPlayers())
system.runJob(this.populateActiveLocationsNearPlayer(player)); system.runJob(this.populateActiveLocationsNearPlayer(player));
@@ -90,11 +97,12 @@ export class StructureVerifier {
} }
*populateActiveLocationsNearPlayer(player) { *populateActiveLocationsNearPlayer(player) {
for (let x = -this.trackPlayerDistance; x < this.trackPlayerDistance; x++) { const distance = this.getTrackPlayerDistance();
for (let y = -this.trackPlayerDistance; y < this.trackPlayerDistance; y++) { for (let x = -distance; x < distance; x++) {
for (let z = -this.trackPlayerDistance; z < this.trackPlayerDistance; z++) { for (let y = -distance; y < distance; y++) {
for (let z = -distance; z < distance; z++) {
const structureLocation = this.instance.toStructureCoords({ x: player.location.x + x, y: player.location.y + y, z: player.location.z + z }); const structureLocation = this.instance.toStructureCoords({ x: player.location.x + x, y: player.location.y + y, z: player.location.z + z });
if (this.instance.isLocationActive(player.dimension.id, structureLocation, { useLayers: true })) { if (this.instance.isLocationActive(player.dimension.id, structureLocation, { useActiveLayer: true })) {
this.locationsToVerify.add({ x: structureLocation.x, y: structureLocation.y, z: structureLocation.z }); this.locationsToVerify.add({ x: structureLocation.x, y: structureLocation.y, z: structureLocation.z });
yield void 0; yield void 0;
} }
@@ -104,14 +112,14 @@ export class StructureVerifier {
this.isBlockPopulationComplete = true; this.isBlockPopulationComplete = true;
} }
*verifyBlocks(blocks) { *verifyBlocks(blocks, shouldRender) {
for (const block of blocks) { for (const block of blocks) {
const verificationLevel = this.verifyBlock(block.location); const verificationLevel = this.verifyBlock(block.location);
if (verificationLevel === BlockVerificationLevel.Air) { if (verificationLevel === BlockVerificationLevel.Air) {
this.blockVerificationLevels.correctlyAir++; this.blockVerificationLevels.correctlyAir++;
} else { } else {
this.blockVerificationLevels[JSON.stringify(block.location)] = verificationLevel; this.blockVerificationLevels[JSON.stringify(block.location)] = verificationLevel;
if (this.shouldRender) { if (shouldRender) {
const dimensionLocation = { dimension: this.instance.getDimension(), location: this.instance.toGlobalCoords(block.location) }; const dimensionLocation = { dimension: this.instance.getDimension(), location: this.instance.toGlobalCoords(block.location) };
new BlockVerificationLevelRender(dimensionLocation, verificationLevel, this.intervalOrLifetime/TicksPerSecond); new BlockVerificationLevelRender(dimensionLocation, verificationLevel, this.intervalOrLifetime/TicksPerSecond);
} }
@@ -1,4 +1,4 @@
export const InstanceEditOptions = Object.freeze({ export const InstanceEditButtons = Object.freeze({
Unknown: 'Unknown', Unknown: 'Unknown',
MainMenu: '<<', MainMenu: '<<',
Place: '§aPlace Instance', Place: '§aPlace Instance',
+3 -2
View File
@@ -1,6 +1,5 @@
/** /**
* Part of ItemStack Database by @gameza_src * Unknown author, with additions.
* Unknown author
*/ */
const isVec3Symbol = Symbol("isVec3"); const isVec3Symbol = Symbol("isVec3");
export function Vector(x = 0, y = 0, z = 0) { export function Vector(x = 0, y = 0, z = 0) {
@@ -23,6 +22,7 @@ Vector.multiply = function multiply(vec, num) {
} }
Vector.isVec3 = function isVec3(vec) { return vec[isVec3Symbol] === true; } Vector.isVec3 = function isVec3(vec) { return vec[isVec3Symbol] === true; }
Vector.floor = function floor(vec) { return { x: Math.floor(vec.x), y: Math.floor(vec.y), z: Math.floor(vec.z), __proto__: Vector.prototype }; } Vector.floor = function floor(vec) { return { x: Math.floor(vec.x), y: Math.floor(vec.y), z: Math.floor(vec.z), __proto__: Vector.prototype }; }
Vector.volume = function volume(a, b) { const [min, max] = Vector.sort(a, b); return (max.x - min.x) * (max.y - min.y) * (max.z - min.z); }
Vector.projection = function projection(a, b) { return Vector.multiply(b, Vector.dot(a, b) / ((b.x * b.x + b.y * b.y + b.z * b.z) ** 2)); } Vector.projection = function projection(a, b) { return Vector.multiply(b, Vector.dot(a, b) / ((b.x * b.x + b.y * b.y + b.z * b.z) ** 2)); }
Vector.rejection = function rejection(a, b) { return Vector.subtract(a, Vector.projection(a, b)); } Vector.rejection = function rejection(a, b) { return Vector.subtract(a, Vector.projection(a, b)); }
Vector.reflect = function reflect(v, n) { return Vector.subtract(v, Vector.multiply(n, 2 * Vector.dot(v, n))); } Vector.reflect = function reflect(v, n) { return Vector.subtract(v, Vector.multiply(n, 2 * Vector.dot(v, n))); }
@@ -56,6 +56,7 @@ Vector.prototype = {
cross(vec) { return Vector.cross(this, vec); }, cross(vec) { return Vector.cross(this, vec); },
dot(vec) { return Vector.dot(this, vec); }, dot(vec) { return Vector.dot(this, vec); },
floor() { return Vector.floor(this); }, floor() { return Vector.floor(this); },
volume(vec) { return Vector.volume(this, vec); },
add(vec) { return Vector.add(this, vec); }, add(vec) { return Vector.add(this, vec); },
subtract(vec) { return Vector.subtract(this, vec); }, subtract(vec) { return Vector.subtract(this, vec); },
multiply(num) { return Vector.multiply(this, num); }, multiply(num) { return Vector.multiply(this, num); },