diff --git a/Construct[BP]/scripts/classes/Builder/BuilderFormBuilder.js b/Construct[BP]/scripts/classes/Builder/BuilderFormBuilder.js index 887b6fc..dbcef2c 100644 --- a/Construct[BP]/scripts/classes/Builder/BuilderFormBuilder.js +++ b/Construct[BP]/scripts/classes/Builder/BuilderFormBuilder.js @@ -8,7 +8,7 @@ export class BuilderFormBuilder { .title(MenuFormBuilder.menuTitle); for (const optionId of BuilderOptions.getOptionIds()) { const option = BuilderOptions.get(optionId); - form.toggle(`${option.displayName} - ${option.description}`, { defaultValue: option.isEnabled(player.id) }); + form.toggle(`${option.displayName}`, { defaultValue: option.isEnabled(player.id), tooltip: option.description }); } form.submitButton('§2Apply'); return form; diff --git a/Construct[BP]/scripts/classes/Instance/InstanceForm.js b/Construct[BP]/scripts/classes/Instance/InstanceForm.js index e5a3f85..f9729c3 100644 --- a/Construct[BP]/scripts/classes/Instance/InstanceForm.js +++ b/Construct[BP]/scripts/classes/Instance/InstanceForm.js @@ -148,6 +148,11 @@ export class InstanceForm { this.player.sendMessage('§cA verification is already in progress. Please wait until it finishes.'); return; } + throw e; + } + if (!statsForm) { + this.player.sendMessage('§cFailed to build statistics form.'); + return; } statsForm.form.show(this.player).then((response) => { if (response.canceled && response.cancelationReason === FormCancelationReason.UserBusy) @@ -160,11 +165,7 @@ export class InstanceForm { 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])); + this.instance.setLayer(parseInt(response.formValues[1])); }); } diff --git a/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js b/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js index 2fd0fe0..8f5b953 100644 --- a/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js +++ b/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js @@ -3,10 +3,9 @@ import { MenuFormBuilder } from '../MenuFormBuilder'; import { StructureVerifier } from '../Verifier/StructureVerifier'; import { StructureStatistics } from '../Structure/StructureStatistics'; import { EntityComponentTypes, TicksPerSecond } from '@minecraft/server'; +import { BlockVerificationLevel } from '../Enums/BlockVerificationLevel'; export class InstanceFormBuilder { - static structureVerifier; - static buildInstance(instance, options) { const location = instance.getLocation(); const form = new ActionFormData() @@ -33,10 +32,9 @@ export class InstanceFormBuilder { .title(MenuFormBuilder.menuTitle) if (this.structureVerifier) throw new Error('StructureVerifier is already running.'); - this.structureVerifier = new StructureVerifier(instance, { isEnabled: true, trackPlayerDistance: 0, intervalOrLifetime: 30 * TicksPerSecond, isStandalone: true }); - const verification = await this.structureVerifier.verifyStructure(); + const structureVerifier = new StructureVerifier(instance, { isEnabled: true, particleLifetime: 1*TicksPerSecond, isStandalone: true }); + const verification = await structureVerifier.verifyStructure(true); const statistics = new StructureStatistics(instance, verification); - this.structureVerifier = void 0; const statsMessage = statistics.getMessage(); buildStatisticsForm.body(statsMessage); return { form: buildStatisticsForm, stats: statsMessage }; @@ -46,7 +44,6 @@ export class InstanceFormBuilder { return new ModalFormData() .title(MenuFormBuilder.menuTitle) .toggle('Block Validation', { defaultValue: instance.options.verifier.isEnabled, tooltip: 'Shows missing and incorrect block overlay.' }) - .toggle('Distance-Based Block Validation', { defaultValue: instance.verifier.getTrackPlayerDistance() !== 0, tooltip: `If enabled, the verifier will only check within ${instance.verifier.getTrackPlayerDistance()} blocks of the player. This should stay enabled unless your structure is very small or in layer mode.` }) .slider("Layer", 0, instance.getMaxLayer(), { defaultValue: instance.getLayer(), valueStep: 1, tooltip: 'Changes the active layer. Use 0 for all layers.' }) .submitButton('§2Apply'); } diff --git a/Construct[BP]/scripts/classes/Instance/InstanceOptions.js b/Construct[BP]/scripts/classes/Instance/InstanceOptions.js index 8558355..6596863 100644 --- a/Construct[BP]/scripts/classes/Instance/InstanceOptions.js +++ b/Construct[BP]/scripts/classes/Instance/InstanceOptions.js @@ -13,7 +13,7 @@ export class InstanceOptions extends Option { verifier = { isEnabled: true, trackPlayerDistance: 5, - intervalOrLifetime: 10 + particleLifetime: 10 }; static getInstanceStrucetureId(instanceName) { diff --git a/Construct[BP]/scripts/classes/Instance/StructureInstance.js b/Construct[BP]/scripts/classes/Instance/StructureInstance.js index 8d62d4b..1af87d6 100644 --- a/Construct[BP]/scripts/classes/Instance/StructureInstance.js +++ b/Construct[BP]/scripts/classes/Instance/StructureInstance.js @@ -1,17 +1,19 @@ import { Vector } from "../../lib/Vector"; -import { StructureOutliner } from "../Structure/StructureOutliner"; +import { StructureOutliner } from "../Render/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"; +import { VerificationRenderer } from "../Render/VerificationRenderer"; export class StructureInstance { options; structure = void 0; - verifier = void 0; outliner = void 0; + verifier = void 0; + verificationRenderer = void 0; materials = void 0; constructor(instanceName, structureId) { @@ -27,6 +29,7 @@ export class StructureInstance { delete this.structure; delete this.outliner; delete this.verifier; + delete this.verificationRenderer; delete this.materials; } @@ -36,11 +39,14 @@ export class StructureInstance { 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 }); + this.verifier = new StructureVerifier(this, { isEnabled: this.options.verifier.isEnabled }); + if (!this.verificationRenderer) + this.verificationRenderer = new VerificationRenderer(this); if (!this.materials) this.materials = new StructureMaterials(this); this.outliner.refresh(); this.verifier.refresh(); + this.verificationRenderer.refresh(); this.materials.refresh(); } @@ -57,7 +63,7 @@ export class StructureInstance { } getDimension() { - return this.options.getDimension(); + return this.options?.getDimension(); } getLayer() { @@ -206,17 +212,18 @@ export class StructureInstance { setVerifierEnabled(enable) { this.options.setVerifierEnabled(enable); this.verifier.refresh(); + this.verificationRenderer.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); + this.options.verifier.particleLifetime = Math.max(bounds.min.volume(bounds.max) / TicksPerSecond, 2*TicksPerSecond); } else { - this.options.verifier.intervalOrLifetime = 10; + this.options.verifier.particleLifetime = 10; } - this.verifier.refresh(); + this.verificationRenderer.refresh(); } increaseLayer() { diff --git a/Construct[BP]/scripts/classes/Verifier/BlockVerificationLevelRender.js b/Construct[BP]/scripts/classes/Render/BlockVerificationLevelRender.js similarity index 100% rename from Construct[BP]/scripts/classes/Verifier/BlockVerificationLevelRender.js rename to Construct[BP]/scripts/classes/Render/BlockVerificationLevelRender.js diff --git a/Construct[BP]/scripts/classes/Structure/StructureOutliner.js b/Construct[BP]/scripts/classes/Render/StructureOutliner.js similarity index 100% rename from Construct[BP]/scripts/classes/Structure/StructureOutliner.js rename to Construct[BP]/scripts/classes/Render/StructureOutliner.js diff --git a/Construct[BP]/scripts/classes/Render/VerificationRenderer.js b/Construct[BP]/scripts/classes/Render/VerificationRenderer.js new file mode 100644 index 0000000..707d272 --- /dev/null +++ b/Construct[BP]/scripts/classes/Render/VerificationRenderer.js @@ -0,0 +1,103 @@ +import { TicksPerSecond } from "@minecraft/server"; +import { BlockVerificationLevelRender } from "./BlockVerificationLevelRender"; +import { system } from "@minecraft/server"; + +const RENDER_LIFETIME_FACTOR_TICKS = 1; + +export class VerificationRenderer { + instance; + lastRenderedChunk; + bounds; + #runner; + #renderQueue = []; + + constructor(instance) { + this.instance = instance; + const bounds = this.instance.getActiveBounds(); + this.lastRenderedChunk = 0; + } + + startContinuousRendering() { + this.#runner = system.runInterval(() => { + if (this.#renderQueue.length === 0) + this.prepareRenderQueue(); + this.renderNextChunk(); + }, RENDER_LIFETIME_FACTOR_TICKS); + } + + stopContinuousRendering() { + if (!this.#runner) + return; + system.clearRun(this.#runner); + this.#runner = void 0; + this.#renderQueue = []; + } + + refresh() { + this.stopContinuousRendering(); + if (!this.instance.isEnabled() || !this.instance.options.verifier.isEnabled) + return; + this.startContinuousRendering(); + } + + prepareRenderQueue() { + this.#renderQueue = []; + const bounds = this.instance.getActiveBounds(); + for (let y = bounds.min.y; y < bounds.max.y; y++) { + for (let x = bounds.min.x; x < bounds.max.x; x++) { + for (let z = bounds.min.z; z < bounds.max.z; z++) { + this.#renderQueue.push({ x, y, z }); + } + } + } + this.lastRenderedChunk = 0; + } + + renderNextChunk() { + if (this.shouldUseLargeStructureRendering()) + this.renderNextChunkForLargeStructure(); + else + this.renderNextChunkForSmallStructure(); + } + + renderNextChunkForLargeStructure() { + const bounds = this.instance.getActiveBounds(); + const maxChunk = (bounds.min.volume(bounds.max) / bounds.max.x) / (bounds.max.y - bounds.min.y); + const lifetime = (maxChunk * RENDER_LIFETIME_FACTOR_TICKS) / TicksPerSecond; + const verificationLevels = this.instance.verifier.getLastVerificationLevels(); + const dimension = this.instance.getDimension(); + const chunk = this.#renderQueue.splice(0, bounds.max.x); + for (const location of chunk) { + const verificationLevel = verificationLevels[JSON.stringify(location)]; + if (!verificationLevel) + continue; + const dimensionLocation = { + dimension: dimension, + location: this.instance.toGlobalCoords(location) + }; + new BlockVerificationLevelRender(dimensionLocation, verificationLevel, lifetime); + } + } + + renderNextChunkForSmallStructure() { + const bounds = this.instance.getActiveBounds(); + const lifetime = (bounds.max.x * (bounds.max.y - bounds.min.y) * bounds.max.z * RENDER_LIFETIME_FACTOR_TICKS) / TicksPerSecond; + const verificationLevels = this.instance.verifier.getLastVerificationLevels(); + const dimension = this.instance.getDimension(); + for (const location of this.#renderQueue.splice(0, 1)) { + const verificationLevel = verificationLevels[JSON.stringify(location)]; + if (!verificationLevel) + continue; + const dimensionLocation = { + dimension: dimension, + location: this.instance.toGlobalCoords(location) + }; + new BlockVerificationLevelRender(dimensionLocation, verificationLevel, lifetime); + } + } + + shouldUseLargeStructureRendering() { + const bounds = this.instance.getActiveBounds(); + return bounds.min.volume(bounds.max) > 300; + } +} \ No newline at end of file diff --git a/Construct[BP]/scripts/classes/Structure/StructureStatistics.js b/Construct[BP]/scripts/classes/Structure/StructureStatistics.js index 20b96ff..1c89c53 100644 --- a/Construct[BP]/scripts/classes/Structure/StructureStatistics.js +++ b/Construct[BP]/scripts/classes/Structure/StructureStatistics.js @@ -9,24 +9,23 @@ export class StructureStatistics { init() { this.statistics = {}; - for (const verificationLevel of Object.values(BlockVerificationLevel)) { + 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)) { + 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]++; - } + for (const [location, verificationLevel] of Object.entries(this.verification)) { + if (location === 'correctlyAir') + continue; + if (blockVerificationLevel === verificationLevel) + this.statistics[verificationLevel]++; } } diff --git a/Construct[BP]/scripts/classes/Verifier/StructureVerifier.js b/Construct[BP]/scripts/classes/Verifier/StructureVerifier.js index 84dbdc8..d4686fc 100644 --- a/Construct[BP]/scripts/classes/Verifier/StructureVerifier.js +++ b/Construct[BP]/scripts/classes/Verifier/StructureVerifier.js @@ -1,6 +1,6 @@ import { BlockVerifier } from "./BlockVerifier"; import { BlockVerificationLevel } from "../Enums/BlockVerificationLevel"; -import { BlockVerificationLevelRender } from "../Verifier/BlockVerificationLevelRender"; +import { BlockVerificationLevelRender } from "../Render/BlockVerificationLevelRender"; import { system, TicksPerSecond } from "@minecraft/server"; import { Vector } from "../../lib/Vector"; @@ -10,22 +10,24 @@ const MIN_LIFETIME = 8; export class StructureVerifier { instance; - intervalOrLifetime; + particleLifetime; locationsToVerify; blockVerificationLevels; isLocationPopulationComplete; isVerificationComplete; + shouldStartNextVerification; + lastCompleteVerificationLevels; #runner; #verifyJob; #populateJob = {}; - constructor(instance, { isEnabled = false, trackPlayerDistance = 0, intervalOrLifetime = 10, isStandalone: isIndependent = false } = {}) { + constructor(instance, { isEnabled = false, trackPlayerDistance = 0, particleLifetime = 10, isStandalone = false } = {}) { this.instance = instance; - this.intervalOrLifetime = Math.max(intervalOrLifetime, MIN_LIFETIME); - if (isIndependent) { - this.isIndependent = isIndependent; + this.particleLifetime = Math.max(particleLifetime, MIN_LIFETIME); + if (isStandalone) { + this.isStandalone = isStandalone; this.enabled = isEnabled; this.trackPlayerDistance = trackPlayerDistance; } else { @@ -36,9 +38,11 @@ export class StructureVerifier { } startContinuousVerification() { + this.shouldStartNextVerification = true; this.#runner = system.runInterval(() => { - this.verifyStructure(); - }, this.intervalOrLifetime); + if (this.shouldStartNextVerification) + this.verifyStructure(); + }); } stopContinuousVerification() { @@ -56,45 +60,48 @@ export class StructureVerifier { } isEnabled() { - if (this.isIndependent) + if (this.isStandalone) return this.enabled; return this.instance.options.verifier.isEnabled; } getTrackPlayerDistance() { let distance; - if (this.isIndependent) + if (this.isStandalone) 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) { + async verifyStructure(shouldRender = false) { if (!this.isEnabled()) return; - this.init(); + this.initVerification(); return new Promise(async (resolve) => { await this.populateLocationsToVerify(); if (this.#verifyJob) system.clearJob(this.#verifyJob); - this.verifyJob = system.runJob(this.verifyBlocks(this.locationsToVerify, shouldRender)); + this.#verifyJob = system.runJob(this.verifyBlocks(this.locationsToVerify, shouldRender)); const checker = system.runInterval(() => { if (this.isVerificationComplete) { system.clearRun(checker); + this.lastCompleteVerificationLevels = JSON.parse(JSON.stringify(this.blockVerificationLevels)); + this.shouldStartNextVerification = true; resolve(this.blockVerificationLevels); } }, 1); }); } + initVerification() { + this.shouldStartNextVerification = false; + this.locationsToVerify.clear(); + this.blockVerificationLevels = { correctlyAir: 0 }; + this.isLocationPopulationComplete = false; + this.isVerificationComplete = false; + } + async populateLocationsToVerify() { return new Promise((resolve) => { if (this.getTrackPlayerDistance() === 0) { @@ -144,7 +151,7 @@ export class StructureVerifier { 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); + new BlockVerificationLevelRender(dimensionLocation, verificationLevel, this.particleLifetime/TicksPerSecond); } } yield void 0; @@ -153,10 +160,16 @@ export class StructureVerifier { } verifyBlock(location) { - const worldBlock = this.instance.getDimension().getBlock(this.instance.toGlobalCoords(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(); } + + getLastVerificationLevels() { + if (!this.lastCompleteVerificationLevels) + return {}; + return this.lastCompleteVerificationLevels; + } } \ No newline at end of file diff --git a/Construct[BP]/scripts/options/fastEasyPlace.js b/Construct[BP]/scripts/options/fastEasyPlace.js index e7ba9fc..6163ae9 100644 --- a/Construct[BP]/scripts/options/fastEasyPlace.js +++ b/Construct[BP]/scripts/options/fastEasyPlace.js @@ -10,7 +10,7 @@ let runner = void 0; const builderOption = new BuilderOption({ identifier: 'fastEasyPlace', displayName: 'Fast Easy Place', - description: 'Place structure blocks just by looking at them.', + description: 'Place correct structure blocks just by looking at them.', howToUse: "Look at structure blocks with a paper named 'Easy Place' in your hand to place them.", });