From 184d2abdcd88d81d70582c3f113d50573bb276f2 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Sun, 13 Apr 2025 18:15:28 -0700 Subject: [PATCH 1/3] Start Structure Verifier --- scripts/classes/BlockVerificationLevel.js | 7 +++ scripts/classes/BlockVerifier.js | 56 +++++++++++++++++++++++ scripts/classes/StructureInstance.js | 16 +++++-- scripts/classes/StructureVerifier.js | 53 +++++++++++++++++++++ 4 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 scripts/classes/BlockVerificationLevel.js create mode 100644 scripts/classes/BlockVerifier.js create mode 100644 scripts/classes/StructureVerifier.js diff --git a/scripts/classes/BlockVerificationLevel.js b/scripts/classes/BlockVerificationLevel.js new file mode 100644 index 0000000..7e04270 --- /dev/null +++ b/scripts/classes/BlockVerificationLevel.js @@ -0,0 +1,7 @@ +export const BlockVerificationLevel = Object.freeze({ + Unknown: 0, + NoMatch: 1, + TypeMatch: 2, + TypeAndStateMatch: 3, + Missing: 4 +}); \ No newline at end of file diff --git a/scripts/classes/BlockVerifier.js b/scripts/classes/BlockVerifier.js new file mode 100644 index 0000000..9557c38 --- /dev/null +++ b/scripts/classes/BlockVerifier.js @@ -0,0 +1,56 @@ +import { BlockVerificationLevel } from "./BlockVerificationLevel"; + +export class BlockVerifier { + constructor(block, structure) { + this.block = block; + this.blockLocationInStructure = structure.toStructureCoords(block.location); + this.structure = structure; + } + + verify() { + const worldPermutation = this.block.permutation; + const structPermutation = this.structure.getBlock(this.blockLocationInStructure); + return this.evaluatePermutations(worldPermutation, structPermutation); + } + + evaluatePermutations(worldPermutation, structPermutation) { + if (this.isMissing(worldPermutation, structPermutation)) + return this.missing(); + if (this.isExactMatch(worldPermutation, structPermutation)) + return this.matchingPermutations(); + if (this.isTypeMatch(worldPermutation, structPermutation)) + return this.matchingTypes(); + return this.matchingNone(); + } + + 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()); + } + + missing() { + return BlockVerificationLevel.Missing; + } + + matchingPermutations() { + console.warn(`Block ${this.block.typeId} matches structure block ${structPermutation.typeId}`); + return BlockVerificationLevel.TypeAndStateMatch; + } + + matchingTypes() { + console.warn(`Block ${this.block.typeId} matches structure block ${structPermutation.typeId}`); + return BlockVerificationLevel.TypeMatch; + } + + matchingNone() { + console.warn(`Block ${this.block.typeId} does not match structure block ${structPermutation.typeId}`); + return BlockVerificationLevel.NoMatch; + } +} \ No newline at end of file diff --git a/scripts/classes/StructureInstance.js b/scripts/classes/StructureInstance.js index 1b35582..41ba52b 100644 --- a/scripts/classes/StructureInstance.js +++ b/scripts/classes/StructureInstance.js @@ -75,6 +75,10 @@ export class StructureInstance { return this.#options.currentLayer || 0; } + getBlock(structureLocation) { + return this.#structure.getBlockPermutation(structureLocation); + } + *getBlocks() { const max = this.#structure.size; for (let x = 0; x < max.x; x++) { @@ -95,10 +99,6 @@ export class StructureInstance { } } - getBlock(structureLocation) { - return this.#structure.getBlockPermutation(structureLocation); - } - getBounds() { return { min: { x: 0, y: 0, z: 0 }, @@ -115,6 +115,10 @@ export class StructureInstance { }; } + getTotalVolume() { + return this.#structure.size.x * this.#structure.size.y * this.#structure.size.z; + } + rename(newName) { world.setDynamicProperty(`structOptions:${this.name}`, void 0); this.name = newName; @@ -241,4 +245,8 @@ export class StructureInstance { else this.setLayer(this.#options.currentLayer - 1); } + + getDimension() { + return world.getDimension(this.#options.dimensionId); + } } \ No newline at end of file diff --git a/scripts/classes/StructureVerifier.js b/scripts/classes/StructureVerifier.js new file mode 100644 index 0000000..38cb5c6 --- /dev/null +++ b/scripts/classes/StructureVerifier.js @@ -0,0 +1,53 @@ +import { BlockVerifier } from "./BlockVerifier"; +import { BlockVerificationLevel } from "./BlockVerificationLevel"; +import { system } from "@minecraft/server"; + +export class StructureVerifier { + constructor(stucture) { + this.structure = structure; + this.blockVerificationLevels = {}; + this.statistics = {}; + this.initStatistics(); + } + + verifyStructure() { + system.runJob(this.verifyBlocks()); + this.parseStatistics(); + } + + *verifyBlocks() { + for (const block of this.structure.getBlocks()) { + this.blockVerificationLevels[block.location] = this.verifyBlock(block.location); + yield; + } + } + + verifyBlock(location) { + const worldBlock = this.structure.getDimension().getBlock(location); + if (!worldBlock) + throw new Error(`Block at ${JSON.stringify(location)} could not be accessed.`); + const blockVerifier = new BlockVerifier(worldBlock, this.structure); + return blockVerifier.verify(); + } + + initStatistics() { + for (const verificationlevel of Object.keys(BlockVerificationLevel)) { + this.statistics[verificationlevel] = 0; + } + } + + parseStatistics() { + for (const blockVerificationLevel of Object.keys(this.statistics)) { + this.parseStatistic(blockVerificationLevel); + } + } + + parseStatistic(blockVerificationLevel) { + for (const verificationlevel of Object.values(this.blockVerificationLevels)) { + if (blockVerificationLevel === verificationlevel) { + this.statistics[verificationlevel]++; + } + } + this.statistics.percentages[blockVerificationLevel] = this.statistics[blockVerificationLevel] / this.structure.getTotalVolume() * 100; + } +} \ No newline at end of file From 567abd0f5f34912cee453ee62998be0c8d9c99d3 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Mon, 14 Apr 2025 23:32:38 -0700 Subject: [PATCH 2/3] How-To page keyword highlighting --- scripts/classes/MenuFormBuilder.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/classes/MenuFormBuilder.js b/scripts/classes/MenuFormBuilder.js index e049411..390377a 100644 --- a/scripts/classes/MenuFormBuilder.js +++ b/scripts/classes/MenuFormBuilder.js @@ -45,8 +45,8 @@ export class MenuFormBuilder { 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 += "§f§lOR§r\n" - body += "§7- Add a .mcstructure file to this pack's structures folder. When selecting your structure, select the 'Other' option and then use the filename (without '.mcstructure') as the Structure ID. After its first use, it will be added to the list of structures."; + 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() From 296af6313494e12854f73ab01e1af3f68efe071e Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Tue, 15 Apr 2025 02:23:21 -0700 Subject: [PATCH 3/3] Block verifier correct --- scripts/classes/BlockVerificationLevel.js | 3 +- scripts/classes/BlockVerifier.js | 24 ++++++++----- scripts/classes/InstanceEditForm.js | 15 ++++++-- scripts/classes/InstanceEditFormBuilder.js | 22 ++++++++++++ scripts/classes/InstanceEditOptions.js | 3 +- scripts/classes/StructureInstance.js | 12 +++---- scripts/classes/StructureVerifier.js | 40 ++++++++++++++-------- 7 files changed, 85 insertions(+), 34 deletions(-) diff --git a/scripts/classes/BlockVerificationLevel.js b/scripts/classes/BlockVerificationLevel.js index 7e04270..cdc5a32 100644 --- a/scripts/classes/BlockVerificationLevel.js +++ b/scripts/classes/BlockVerificationLevel.js @@ -3,5 +3,6 @@ export const BlockVerificationLevel = Object.freeze({ NoMatch: 1, TypeMatch: 2, TypeAndStateMatch: 3, - Missing: 4 + Missing: 4, + isAir: 5 }); \ No newline at end of file diff --git a/scripts/classes/BlockVerifier.js b/scripts/classes/BlockVerifier.js index 9557c38..5ac24e9 100644 --- a/scripts/classes/BlockVerifier.js +++ b/scripts/classes/BlockVerifier.js @@ -1,19 +1,20 @@ import { BlockVerificationLevel } from "./BlockVerificationLevel"; export class BlockVerifier { - constructor(block, structure) { + constructor(block, instance) { this.block = block; - this.blockLocationInStructure = structure.toStructureCoords(block.location); - this.structure = structure; + this.instance = instance; + this.blockLocationInStructure = instance.toStructureCoords(block.location); } verify() { - const worldPermutation = this.block.permutation; - const structPermutation = this.structure.getBlock(this.blockLocationInStructure); - return this.evaluatePermutations(worldPermutation, structPermutation); + const structPermutation = this.instance.getBlock(this.blockLocationInStructure); + return this.evaluatePermutations(this.block.permutation, structPermutation); } evaluatePermutations(worldPermutation, structPermutation) { + if (this.isCorrectlyAir(worldPermutation, structPermutation)) + return this.air(); if (this.isMissing(worldPermutation, structPermutation)) return this.missing(); if (this.isExactMatch(worldPermutation, structPermutation)) @@ -23,6 +24,10 @@ export class BlockVerifier { return this.matchingNone(); } + 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"; } @@ -35,22 +40,23 @@ export class BlockVerifier { return worldPermuation.matches(structurePermuation.type.id, structurePermuation.getAllStates()); } + air() { + return BlockVerificationLevel.isAir; + } + missing() { return BlockVerificationLevel.Missing; } matchingPermutations() { - console.warn(`Block ${this.block.typeId} matches structure block ${structPermutation.typeId}`); return BlockVerificationLevel.TypeAndStateMatch; } matchingTypes() { - console.warn(`Block ${this.block.typeId} matches structure block ${structPermutation.typeId}`); return BlockVerificationLevel.TypeMatch; } matchingNone() { - console.warn(`Block ${this.block.typeId} does not match structure block ${structPermutation.typeId}`); return BlockVerificationLevel.NoMatch; } } \ No newline at end of file diff --git a/scripts/classes/InstanceEditForm.js b/scripts/classes/InstanceEditForm.js index c4acdb7..928a281 100644 --- a/scripts/classes/InstanceEditForm.js +++ b/scripts/classes/InstanceEditForm.js @@ -11,6 +11,7 @@ export class InstanceEditForm { InstanceEditOptions.PreviousLayer, InstanceEditOptions.SetLayer, InstanceEditOptions.Move, + InstanceEditOptions.Statistics, InstanceEditOptions.RenameInstance, InstanceEditOptions.DisableInstance, ], @@ -93,6 +94,9 @@ export class InstanceEditForm { case InstanceEditOptions.Move: this.instance.move(this.player.dimension.id, this.player.location); break; + case InstanceEditOptions.Statistics: + this.statisticsForm(); + break; case InstanceEditOptions.MainMenu: new MenuForm(this.player, { jumpToInstance: false }); break; @@ -104,7 +108,8 @@ export class InstanceEditForm { renameInstanceForm() { InstanceEditFormBuilder.buildRenameInstance(this.instanceName).show(this.player).then((response) => { - if (response.canceled) return; + if (response.canceled) + return; const newName = response.formValues[0]; if (newName === '') { this.player.sendMessage('§cInstance name cannot be empty.'); @@ -122,9 +127,15 @@ export class InstanceEditForm { setLayerForm() { InstanceEditFormBuilder.buildSetLayer(this.instance.getBounds().max.y, this.instance.getLayer()).show(this.player).then((response) => { - if (response.canceled) return; + if (response.canceled) + return; const selectedLayer = response.formValues[0]; this.instance.setLayer(parseInt(selectedLayer)); }); } + + async statisticsForm() { + const form = await InstanceEditFormBuilder.buildStatistics(this.instance) + form.show(this.player); + } } \ No newline at end of file diff --git a/scripts/classes/InstanceEditFormBuilder.js b/scripts/classes/InstanceEditFormBuilder.js index 118a062..3564309 100644 --- a/scripts/classes/InstanceEditFormBuilder.js +++ b/scripts/classes/InstanceEditFormBuilder.js @@ -1,5 +1,7 @@ import { ActionFormData, ModalFormData } from '@minecraft/server-ui'; import { MenuFormBuilder } from './MenuFormBuilder'; +import { StructureVerifier } from './StructureVerifier'; +import { BlockVerificationLevel } from './BlockVerificationLevel'; export class InstanceEditFormBuilder { static buildInstance(instance, options) { @@ -30,4 +32,24 @@ export class InstanceEditFormBuilder { .slider("Layer", 0, maxLayer, 1, currentLayer) .submitButton('Set Layer'); } + + static async buildStatistics(instance) { + const buildStatisticsForm = new ActionFormData() + .title(MenuFormBuilder.menuTitle) + let message = ''; + const structureVerifier = new StructureVerifier(instance); + const statistics = await structureVerifier.verifyStructure(); + message += `§fStatistics for §a${instance.name}§f:\n`; + message += `§7Blocks: §2${instance.getTotalVolume() - statistics.correctlyAir}\n`; + message += `§7Correct: §a${this.getFormattedStatistic(statistics, BlockVerificationLevel.TypeAndStateMatch)}\n`; + message += `§7Block State Incorrect: §e${this.getFormattedStatistic(statistics, BlockVerificationLevel.TypeMatch)}\n`; + message += `§7Incorrect: §c${this.getFormattedStatistic(statistics, BlockVerificationLevel.NoMatch)}\n`; + message += `§7Missing: §c${this.getFormattedStatistic(statistics, BlockVerificationLevel.Missing)}\n`; + buildStatisticsForm.body(message); + return buildStatisticsForm; + } + + static getFormattedStatistic(statistics, blockVerificationLevel) { + return `${statistics[blockVerificationLevel]} (${statistics.percentages[blockVerificationLevel].toFixed(2)}%%)`; + } } \ No newline at end of file diff --git a/scripts/classes/InstanceEditOptions.js b/scripts/classes/InstanceEditOptions.js index 7b4fa3b..d74468d 100644 --- a/scripts/classes/InstanceEditOptions.js +++ b/scripts/classes/InstanceEditOptions.js @@ -9,5 +9,6 @@ export const InstanceEditOptions = Object.freeze({ NextLayer: 'Increase Layer', PreviousLayer: 'Decrease Layer', SetLayer: 'Set Layer', - Move: 'Move Here' + Move: 'Move Here', + Statistics: 'Statistics', }); \ No newline at end of file diff --git a/scripts/classes/StructureInstance.js b/scripts/classes/StructureInstance.js index 45efa79..82e3fc7 100644 --- a/scripts/classes/StructureInstance.js +++ b/scripts/classes/StructureInstance.js @@ -92,12 +92,8 @@ export class StructureInstance { *getBlocks() { const max = this.#structure.size; - for (let x = 0; x < max.x; x++) { - for (let y = 0; y < max.y; y++) { - for (let z = 0; z < max.z; z++) { - yield this.#structure.getBlockPermutation({ x, y, z }); - } - } + for (let y = 0; y < max.y; y++) { + yield * this.getLayerBlocks(y); } } @@ -105,7 +101,9 @@ export class StructureInstance { const max = this.#structure.size; for (let x = 0; x < max.x; x++) { for (let z = 0; z < max.z; z++) { - yield this.#structure.getBlockPermutation({ x, y, z }); + const blockPermutation = this.#structure.getBlockPermutation({ x, y, z }); + blockPermutation.location = { x, y, z }; + yield blockPermutation; } } } diff --git a/scripts/classes/StructureVerifier.js b/scripts/classes/StructureVerifier.js index 38cb5c6..10ad29a 100644 --- a/scripts/classes/StructureVerifier.js +++ b/scripts/classes/StructureVerifier.js @@ -1,43 +1,55 @@ import { BlockVerifier } from "./BlockVerifier"; import { BlockVerificationLevel } from "./BlockVerificationLevel"; -import { system } from "@minecraft/server"; export class StructureVerifier { - constructor(stucture) { - this.structure = structure; + constructor(instance) { + this.instance = instance; this.blockVerificationLevels = {}; this.statistics = {}; this.initStatistics(); } - verifyStructure() { - system.runJob(this.verifyBlocks()); + async verifyStructure() { + await this.runVerifier(); this.parseStatistics(); + return this.statistics; } - *verifyBlocks() { - for (const block of this.structure.getBlocks()) { - this.blockVerificationLevels[block.location] = this.verifyBlock(block.location); - yield; + async runVerifier() { + return new Promise((resolve) => { + this.verifyBlocks(); + resolve(); + }); + } + + verifyBlocks() { + for (const block of this.instance.getBlocks()) { + const verificationLevel = this.verifyBlock(block.location); + if (verificationLevel !== BlockVerificationLevel.isAir) + this.blockVerificationLevels[JSON.stringify(block.location)] = verificationLevel; + else + this.statistics.correctlyAir++; } } verifyBlock(location) { - const worldBlock = this.structure.getDimension().getBlock(location); + const worldBlock = this.instance.getDimension().getBlock(this.instance.toGlobalCoords(location)); if (!worldBlock) throw new Error(`Block at ${JSON.stringify(location)} could not be accessed.`); - const blockVerifier = new BlockVerifier(worldBlock, this.structure); + const blockVerifier = new BlockVerifier(worldBlock, this.instance); return blockVerifier.verify(); } initStatistics() { - for (const verificationlevel of Object.keys(BlockVerificationLevel)) { + for (const verificationlevel of Object.values(BlockVerificationLevel)) { this.statistics[verificationlevel] = 0; } + this.statistics.percentages = {}; + this.statistics.correctlyAir = 0; } parseStatistics() { - for (const blockVerificationLevel of Object.keys(this.statistics)) { + for (const blockVerificationLevel of Object.values(BlockVerificationLevel)) { this.parseStatistic(blockVerificationLevel); } } @@ -48,6 +60,6 @@ export class StructureVerifier { this.statistics[verificationlevel]++; } } - this.statistics.percentages[blockVerificationLevel] = this.statistics[blockVerificationLevel] / this.structure.getTotalVolume() * 100; + this.statistics.percentages[blockVerificationLevel] = this.statistics[blockVerificationLevel] / (this.instance.getTotalVolume() - this.statistics.correctlyAir) * 100; } } \ No newline at end of file