Merge branch 'StructureVerifier'

This commit is contained in:
ForestOfLight
2025-04-15 09:13:45 -07:00
Unverified
8 changed files with 191 additions and 16 deletions
@@ -0,0 +1,8 @@
export const BlockVerificationLevel = Object.freeze({
Unknown: 0,
NoMatch: 1,
TypeMatch: 2,
TypeAndStateMatch: 3,
Missing: 4,
isAir: 5
});
+62
View File
@@ -0,0 +1,62 @@
import { BlockVerificationLevel } from "./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 this.air();
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();
}
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());
}
air() {
return BlockVerificationLevel.isAir;
}
missing() {
return BlockVerificationLevel.Missing;
}
matchingPermutations() {
return BlockVerificationLevel.TypeAndStateMatch;
}
matchingTypes() {
return BlockVerificationLevel.TypeMatch;
}
matchingNone() {
return BlockVerificationLevel.NoMatch;
}
}
+13 -2
View File
@@ -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);
}
}
@@ -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)}%%)`;
}
}
+2 -1
View File
@@ -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',
});
+2 -2
View File
@@ -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()
+16 -10
View File
@@ -86,14 +86,14 @@ export class StructureInstance {
return dimension;
}
getBlock(structureLocation) {
return this.#structure.getBlockPermutation(structureLocation);
}
*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 });
}
}
yield * this.getLayerBlocks(y);
}
}
@@ -101,15 +101,13 @@ 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;
}
}
}
getBlock(structureLocation) {
return this.#structure.getBlockPermutation(structureLocation);
}
getBounds() {
return {
min: { x: 0, y: 0, z: 0 },
@@ -126,6 +124,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;
@@ -247,4 +249,8 @@ export class StructureInstance {
else
this.setLayer(this.#options.currentLayer - 1);
}
getDimension() {
return world.getDimension(this.#options.dimensionId);
}
}
+65
View File
@@ -0,0 +1,65 @@
import { BlockVerifier } from "./BlockVerifier";
import { BlockVerificationLevel } from "./BlockVerificationLevel";
export class StructureVerifier {
constructor(instance) {
this.instance = instance;
this.blockVerificationLevels = {};
this.statistics = {};
this.initStatistics();
}
async verifyStructure() {
await this.runVerifier();
this.parseStatistics();
return this.statistics;
}
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.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.instance);
return blockVerifier.verify();
}
initStatistics() {
for (const verificationlevel of Object.values(BlockVerificationLevel)) {
this.statistics[verificationlevel] = 0;
}
this.statistics.percentages = {};
this.statistics.correctlyAir = 0;
}
parseStatistics() {
for (const blockVerificationLevel of Object.values(BlockVerificationLevel)) {
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.instance.getTotalVolume() - this.statistics.correctlyAir) * 100;
}
}