Merge branch 'StructureVerifier'
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"format_version": 2,
|
"format_version": 2,
|
||||||
"header": {
|
"header": {
|
||||||
"name": "StrucTool",
|
"name": "StrucTool [BP] v1.0.0",
|
||||||
"description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.",
|
"description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.",
|
||||||
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
|
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
|
||||||
"min_engine_version": [1, 21, 70],
|
"min_engine_version": [1, 21, 70],
|
||||||
@@ -32,6 +32,10 @@
|
|||||||
"module_name": "@minecraft/server-ui",
|
"module_name": "@minecraft/server-ui",
|
||||||
"version": "2.0.0-beta"
|
"version": "2.0.0-beta"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", // StrucTool RP
|
||||||
|
"version": [1, 0, 0]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"uuid": "bcf34368-ed0c-4cf7-938e-582cccf9950d", // Canopy RP
|
"uuid": "bcf34368-ed0c-4cf7-938e-582cccf9950d", // Canopy RP
|
||||||
"version": [1, 0, 3]
|
"version": [1, 0, 3]
|
||||||
|
|||||||
@@ -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;
|
||||||
|
lifetime = 0;
|
||||||
|
|
||||||
|
constructor(dimensionLocation, verificationLevel, lifetime = 5) {
|
||||||
|
this.dimension = dimensionLocation.dimension;
|
||||||
|
this.location = new Vector(dimensionLocation.location.x, dimensionLocation.location.y, dimensionLocation.location.z);
|
||||||
|
this.verificationLevel = verificationLevel;
|
||||||
|
this.lifetime = lifetime;
|
||||||
|
this.renderBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBlock() {
|
||||||
|
for (const particleLocation of this.getParticleLocations()) {
|
||||||
|
const color = this.getRGBAMolang();
|
||||||
|
if (!color)
|
||||||
|
return;
|
||||||
|
color.setFloat("lifetime", this.lifetime);
|
||||||
|
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: "structool:blockoverlay_xz", location: this.location.add(topFace) },
|
||||||
|
{ particleType: "structool:blockoverlay_xz", location: this.location.add(bottomFace) },
|
||||||
|
{ particleType: "structool:blockoverlay_yz", location: this.location.add(leftFace) },
|
||||||
|
{ particleType: "structool:blockoverlay_yz", location: this.location.add(rightFace) },
|
||||||
|
{ particleType: "structool:blockoverlay_xy", location: this.location.add(frontFace) },
|
||||||
|
{ particleType: "structool: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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BlockVerificationLevel } from "./BlockVerificationLevel";
|
import { BlockVerificationLevel } from "./enums/BlockVerificationLevel";
|
||||||
|
|
||||||
export class BlockVerifier {
|
export class BlockVerifier {
|
||||||
constructor(block, instance) {
|
constructor(block, instance) {
|
||||||
@@ -14,14 +14,14 @@ export class BlockVerifier {
|
|||||||
|
|
||||||
evaluatePermutations(worldPermutation, structPermutation) {
|
evaluatePermutations(worldPermutation, structPermutation) {
|
||||||
if (this.isCorrectlyAir(worldPermutation, structPermutation))
|
if (this.isCorrectlyAir(worldPermutation, structPermutation))
|
||||||
return this.air();
|
return BlockVerificationLevel.Air;
|
||||||
if (this.isMissing(worldPermutation, structPermutation))
|
if (this.isMissing(worldPermutation, structPermutation))
|
||||||
return this.missing();
|
return BlockVerificationLevel.Missing;
|
||||||
if (this.isExactMatch(worldPermutation, structPermutation))
|
if (this.isExactMatch(worldPermutation, structPermutation))
|
||||||
return this.matchingPermutations();
|
return BlockVerificationLevel.Match;
|
||||||
if (this.isTypeMatch(worldPermutation, structPermutation))
|
if (this.isTypeMatch(worldPermutation, structPermutation))
|
||||||
return this.matchingTypes();
|
return BlockVerificationLevel.TypeMatch;
|
||||||
return this.matchingNone();
|
return BlockVerificationLevel.NoMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
isCorrectlyAir(worldPermutation, structPermutation) {
|
isCorrectlyAir(worldPermutation, structPermutation) {
|
||||||
@@ -39,24 +39,4 @@ export class BlockVerifier {
|
|||||||
isExactMatch(worldPermuation, structurePermuation) {
|
isExactMatch(worldPermuation, structurePermuation) {
|
||||||
return worldPermuation.matches(structurePermuation.type.id, structurePermuation.getAllStates());
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { structureCollection } from './StructureCollection';
|
import { structureCollection } from './StructureCollection';
|
||||||
import { MenuForm } from '../classes/MenuForm';
|
import { MenuForm } from '../classes/MenuForm';
|
||||||
import { InstanceEditOptions } from './InstanceEditOptions';
|
import { InstanceEditOptions } from './enums/InstanceEditOptions';
|
||||||
import { InstanceEditFormBuilder } from './InstanceEditFormBuilder';
|
import { InstanceEditFormBuilder } from './InstanceEditFormBuilder';
|
||||||
|
import { FormCancelationReason } from '@minecraft/server-ui';
|
||||||
|
|
||||||
export class InstanceEditForm {
|
export class InstanceEditForm {
|
||||||
instanceName;
|
instanceName;
|
||||||
@@ -135,7 +136,10 @@ export class InstanceEditForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async statisticsForm() {
|
async statisticsForm() {
|
||||||
const form = await InstanceEditFormBuilder.buildStatistics(this.instance)
|
const statsForm = await InstanceEditFormBuilder.buildStatistics(this.instance)
|
||||||
form.show(this.player);
|
statsForm.form.show(this.player).then((response) => {
|
||||||
|
if (response.canceled && response.cancelationReason === FormCancelationReason.UserBusy)
|
||||||
|
this.player.sendMessage(statsForm.stats);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
|
import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
|
||||||
import { MenuFormBuilder } from './MenuFormBuilder';
|
import { MenuFormBuilder } from './MenuFormBuilder';
|
||||||
import { StructureVerifier } from './StructureVerifier';
|
import { StructureVerifier } from './StructureVerifier';
|
||||||
import { BlockVerificationLevel } from './BlockVerificationLevel';
|
import { StructureStatistics } from './StructureStatistics';
|
||||||
|
|
||||||
export class InstanceEditFormBuilder {
|
export class InstanceEditFormBuilder {
|
||||||
static buildInstance(instance, options) {
|
static buildInstance(instance, options) {
|
||||||
@@ -36,20 +36,11 @@ 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)
|
||||||
let message = '';
|
|
||||||
const structureVerifier = new StructureVerifier(instance);
|
const structureVerifier = new StructureVerifier(instance);
|
||||||
const statistics = await structureVerifier.verifyStructure();
|
const verification = await structureVerifier.verifyStructure();
|
||||||
message += `§fStatistics for §a${instance.name}§f:\n`;
|
const statistics = new StructureStatistics(instance, verification);
|
||||||
message += `§7Blocks: §2${instance.getTotalVolume() - statistics.correctlyAir}\n`;
|
const statsMessage = statistics.getMessage();
|
||||||
message += `§7Correct: §a${this.getFormattedStatistic(statistics, BlockVerificationLevel.TypeAndStateMatch)}\n`;
|
buildStatisticsForm.body(statsMessage);
|
||||||
message += `§7Block State Incorrect: §e${this.getFormattedStatistic(statistics, BlockVerificationLevel.TypeMatch)}\n`;
|
return { form: buildStatisticsForm, stats: statsMessage };
|
||||||
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)}%%)`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,21 @@
|
|||||||
import { system, world } from "@minecraft/server";
|
import { MolangVariableMap, system, world } from "@minecraft/server";
|
||||||
import { Vector } from "../lib/Vector";
|
import { Vector } from "../lib/Vector";
|
||||||
|
|
||||||
|
|
||||||
export class Outliner {
|
export class Outliner {
|
||||||
dimension;
|
dimension;
|
||||||
min = new Vector();
|
min = new Vector();
|
||||||
max = new Vector();
|
max = new Vector();
|
||||||
drawParticle = "minecraft:villager_happy";
|
drawParticle = "structool:outline";
|
||||||
drawFrequency = 8;
|
drawFrequency = 10;
|
||||||
|
|
||||||
#drawParticles = [];
|
#drawParticles = [];
|
||||||
#runner = null;
|
#runner = void 0;
|
||||||
|
|
||||||
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 = new Vector(min.x, min.y, min.z);
|
||||||
this.max = new Vector(max.x, max.y, max.z);
|
this.max = new Vector(max.x, max.y, max.z);
|
||||||
this.vertices = this.getVertices(min, max);
|
this.vertices = this.getVertices(min, max);
|
||||||
this.startDraw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startDraw() {
|
startDraw() {
|
||||||
@@ -25,18 +23,28 @@ export class Outliner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stopDraw() {
|
stopDraw() {
|
||||||
|
if (!this.#runner)
|
||||||
|
return;
|
||||||
system.clearRun(this.#runner);
|
system.clearRun(this.#runner);
|
||||||
|
this.#runner = void 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
this.#drawParticles.length = 0;
|
this.drawParticles(this.getVerticeParticles(), () => {
|
||||||
this.#drawParticles.push(...this.getVerticeParticleLocations());
|
return { red: 1, green: 1, blue: 1, alpha: 1 }
|
||||||
this.#drawParticles.push(...this.getCubiodParticleLocations());
|
});
|
||||||
|
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) {
|
for (const [particleType, location] of this.#drawParticles) {
|
||||||
|
const molang = new MolangVariableMap();
|
||||||
|
molang.setColorRGBA("dot_color", colorCallback());
|
||||||
try {
|
try {
|
||||||
this.dimension.spawnParticle(particleType, location);
|
this.dimension.spawnParticle(particleType, location, molang);
|
||||||
} catch {
|
} catch (e) {
|
||||||
/* pass */
|
/* pass */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,11 +70,11 @@ export class Outliner {
|
|||||||
this.vertices = this.getVertices(min, max);
|
this.vertices = this.getVertices(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
getVerticeParticleLocations() {
|
getVerticeParticles() {
|
||||||
return this.vertices.map((v) => [this.drawParticle, v]);
|
return this.vertices.map((v) => [this.drawParticle, v]);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCubiodParticleLocations() {
|
getCubiodEdgeParticles() {
|
||||||
const edges = [
|
const edges = [
|
||||||
[0, 1],
|
[0, 1],
|
||||||
[0, 2],
|
[0, 2],
|
||||||
@@ -97,4 +105,14 @@ export class Outliner {
|
|||||||
for (const location of locations)
|
for (const location of locations)
|
||||||
this.vertices.push(new Vector(location.x, location.y, location.z));
|
this.vertices.push(new Vector(location.x, location.y, location.z));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNextParticleColor() {
|
||||||
|
if (this.lastWasBlack) {
|
||||||
|
this.lastWasBlack = false;
|
||||||
|
return { red: 1, green: 1, blue: 0, alpha: 1 };
|
||||||
|
} else {
|
||||||
|
this.lastWasBlack = true;
|
||||||
|
return { red: 0.15, green: 0.15, blue: 0.15, alpha: 1 };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { world } from "@minecraft/server";
|
import { world } from "@minecraft/server";
|
||||||
import { Outliner } from "./Outliner";
|
import { Outliner } from "./Outliner";
|
||||||
import { StructureOutliner } from "./StructureOutliner";
|
import { StructureOutliner } from "./StructureOutliner";
|
||||||
|
import { StructureVerifier } from "./StructureVerifier";
|
||||||
|
|
||||||
export class StructureInstance {
|
export class StructureInstance {
|
||||||
name;
|
name;
|
||||||
@@ -15,6 +16,7 @@ export class StructureInstance {
|
|||||||
currentLayer: 0
|
currentLayer: 0
|
||||||
};
|
};
|
||||||
outliner = void 0;
|
outliner = void 0;
|
||||||
|
verifier = void 0;
|
||||||
|
|
||||||
constructor(instanceName, structureId) {
|
constructor(instanceName, structureId) {
|
||||||
this.name = instanceName;
|
this.name = instanceName;
|
||||||
@@ -25,7 +27,7 @@ export class StructureInstance {
|
|||||||
this.#options = this.loadOptions();
|
this.#options = this.loadOptions();
|
||||||
this.#options.structureId = structureId;
|
this.#options.structureId = structureId;
|
||||||
this.updateOptions();
|
this.updateOptions();
|
||||||
this.outliner = new StructureOutliner(this);
|
this.refreshBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOptions() {
|
loadOptions() {
|
||||||
@@ -102,6 +104,8 @@ export class StructureInstance {
|
|||||||
for (let x = 0; x < max.x; x++) {
|
for (let x = 0; x < max.x; x++) {
|
||||||
for (let z = 0; z < max.z; z++) {
|
for (let z = 0; z < max.z; z++) {
|
||||||
const blockPermutation = this.#structure.getBlockPermutation({ x, y, z });
|
const blockPermutation = this.#structure.getBlockPermutation({ x, y, z });
|
||||||
|
if (!blockPermutation)
|
||||||
|
yield void 0;
|
||||||
blockPermutation.location = { x, y, z };
|
blockPermutation.location = { x, y, z };
|
||||||
yield blockPermutation;
|
yield blockPermutation;
|
||||||
}
|
}
|
||||||
@@ -128,6 +132,14 @@ export class StructureInstance {
|
|||||||
return this.#structure.size.x * this.#structure.size.y * this.#structure.size.z;
|
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) {
|
rename(newName) {
|
||||||
world.setDynamicProperty(`structOptions:${this.name}`, void 0);
|
world.setDynamicProperty(`structOptions:${this.name}`, void 0);
|
||||||
this.name = newName;
|
this.name = newName;
|
||||||
@@ -142,20 +154,20 @@ export class StructureInstance {
|
|||||||
enable() {
|
enable() {
|
||||||
this.#options.isEnabled = true;
|
this.#options.isEnabled = true;
|
||||||
this.updateOptions();
|
this.updateOptions();
|
||||||
this.refreshOutliner();
|
this.refreshBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
this.#options.isEnabled = false;
|
this.#options.isEnabled = false;
|
||||||
this.updateOptions();
|
this.updateOptions();
|
||||||
this.refreshOutliner();
|
this.refreshBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
move(dimensionId, location) {
|
move(dimensionId, location) {
|
||||||
this.#options.dimensionId = dimensionId;
|
this.#options.dimensionId = dimensionId;
|
||||||
this.#options.worldLocation = { x: Math.floor(location.x), y: Math.floor(location.y), z: Math.floor(location.z) };
|
this.#options.worldLocation = { x: Math.floor(location.x), y: Math.floor(location.y), z: Math.floor(location.z) };
|
||||||
this.updateOptions();
|
this.updateOptions();
|
||||||
this.refreshOutliner();
|
this.refreshBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
setLayer(layer) {
|
setLayer(layer) {
|
||||||
@@ -163,11 +175,18 @@ export class StructureInstance {
|
|||||||
throw new Error(`[StrucTool] Layer ${layer} is out of bounds.`);
|
throw new Error(`[StrucTool] Layer ${layer} is out of bounds.`);
|
||||||
this.#options.currentLayer = layer;
|
this.#options.currentLayer = layer;
|
||||||
this.updateOptions();
|
this.updateOptions();
|
||||||
this.refreshOutliner();
|
this.refreshBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshOutliner() {
|
refreshBox() {
|
||||||
|
if (!this.hasLocation())
|
||||||
|
return;
|
||||||
|
if (!this.outliner)
|
||||||
|
this.outliner = new StructureOutliner(this);
|
||||||
|
if (!this.verifier)
|
||||||
|
this.verifier = new StructureVerifier(this, { shouldRender: true });
|
||||||
this.outliner.refresh();
|
this.outliner.refresh();
|
||||||
|
this.verifier.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
isLocationInStructure(dimensionId, structureLocation) {
|
isLocationInStructure(dimensionId, structureLocation) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export class StructureOutliner {
|
|||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.pullInstanceData();
|
this.pullInstanceData();
|
||||||
this.outliner = new Outliner(this.dimension, this.bounds.min, this.bounds.max);
|
this.outliner = new Outliner(this.dimension, this.bounds.min, this.bounds.max);
|
||||||
this.refresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pullInstanceData() {
|
pullInstanceData() {
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
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() {
|
||||||
|
return this.instance.getActiveVolume() - 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.name}§f:`;
|
||||||
|
if (this.instance.isUsingLayers())
|
||||||
|
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)}%%)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,65 +1,85 @@
|
|||||||
import { BlockVerifier } from "./BlockVerifier";
|
import { BlockVerifier } from "./BlockVerifier";
|
||||||
import { BlockVerificationLevel } from "./BlockVerificationLevel";
|
import { BlockVerificationLevel } from "./enums/BlockVerificationLevel";
|
||||||
|
import { BlockVerificationLevelRender } from "./BlockVerificationLevelRender";
|
||||||
|
import { system, TicksPerSecond } from "@minecraft/server";
|
||||||
|
|
||||||
export class StructureVerifier {
|
export class StructureVerifier {
|
||||||
constructor(instance) {
|
shouldRender;
|
||||||
|
isComplete;
|
||||||
|
interval = 5*20;
|
||||||
|
#runner;
|
||||||
|
|
||||||
|
constructor(instance, { shouldRender = false } = {}) {
|
||||||
|
this.shouldRender = shouldRender;
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.blockVerificationLevels = {};
|
this.interval = Math.max(instance.getActiveVolume() / 50, 20);
|
||||||
this.statistics = {};
|
}
|
||||||
this.initStatistics();
|
|
||||||
|
startContinuousVerification() {
|
||||||
|
this.#runner = system.runInterval(() => {
|
||||||
|
this.verifyStructure();
|
||||||
|
}, this.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopContinuousVerification() {
|
||||||
|
if (!this.#runner)
|
||||||
|
return;
|
||||||
|
system.clearRun(this.#runner);
|
||||||
|
this.#runner = void 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.stopContinuousVerification();
|
||||||
|
if (!this.instance.isEnabled())
|
||||||
|
return;
|
||||||
|
this.startContinuousVerification();
|
||||||
|
}
|
||||||
|
|
||||||
|
init(shouldRender) {
|
||||||
|
this.blockVerificationLevels = { correctlyAir: 0 };
|
||||||
|
this.shouldRender = shouldRender;
|
||||||
|
this.isComplete = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyStructure() {
|
async verifyStructure() {
|
||||||
await this.runVerifier();
|
this.init(this.shouldRender);
|
||||||
this.parseStatistics();
|
|
||||||
return this.statistics;
|
|
||||||
}
|
|
||||||
|
|
||||||
async runVerifier() {
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.verifyBlocks();
|
if (this.instance.isUsingLayers())
|
||||||
resolve();
|
system.runJob(this.verifyBlocks(this.instance.getLayerBlocks(this.instance.getLayer()-1)));
|
||||||
|
else
|
||||||
|
system.runJob(this.verifyBlocks(this.instance.getBlocks()));
|
||||||
|
const checker = system.runInterval(() => {
|
||||||
|
if (this.isComplete) {
|
||||||
|
system.clearRun(checker);
|
||||||
|
resolve(this.blockVerificationLevels);
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyBlocks() {
|
*verifyBlocks(blocks) {
|
||||||
for (const block of this.instance.getBlocks()) {
|
for (const block of blocks) {
|
||||||
const verificationLevel = this.verifyBlock(block.location);
|
const verificationLevel = this.verifyBlock(block.location);
|
||||||
if (verificationLevel !== BlockVerificationLevel.isAir)
|
if (verificationLevel === BlockVerificationLevel.Air) {
|
||||||
|
this.blockVerificationLevels.correctlyAir++;
|
||||||
|
} else {
|
||||||
this.blockVerificationLevels[JSON.stringify(block.location)] = verificationLevel;
|
this.blockVerificationLevels[JSON.stringify(block.location)] = verificationLevel;
|
||||||
else
|
if (this.shouldRender) {
|
||||||
this.statistics.correctlyAir++;
|
const dimensionLocation = { dimension: this.instance.getDimension(), location: this.instance.toGlobalCoords(block.location) };
|
||||||
|
new BlockVerificationLevelRender(dimensionLocation, verificationLevel, this.interval/TicksPerSecond);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield void 0;
|
||||||
}
|
}
|
||||||
|
this.isComplete = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyBlock(location) {
|
verifyBlock(location) {
|
||||||
const worldBlock = this.instance.getDimension().getBlock(this.instance.toGlobalCoords(location));
|
const worldBlock = this.instance.getDimension().getBlock(this.instance.toGlobalCoords(location));
|
||||||
if (!worldBlock)
|
if (!worldBlock) {
|
||||||
throw new Error(`Block at ${JSON.stringify(location)} could not be accessed.`);
|
return BlockVerificationLevel.Skipped;
|
||||||
|
}
|
||||||
const blockVerifier = new BlockVerifier(worldBlock, this.instance);
|
const blockVerifier = new BlockVerifier(worldBlock, this.instance);
|
||||||
return blockVerifier.verify();
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+3
-2
@@ -2,7 +2,8 @@ export const BlockVerificationLevel = Object.freeze({
|
|||||||
Unknown: 0,
|
Unknown: 0,
|
||||||
NoMatch: 1,
|
NoMatch: 1,
|
||||||
TypeMatch: 2,
|
TypeMatch: 2,
|
||||||
TypeAndStateMatch: 3,
|
Match: 3,
|
||||||
Missing: 4,
|
Missing: 4,
|
||||||
isAir: 5
|
Air: 5,
|
||||||
|
Skipped: 6
|
||||||
});
|
});
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
import { Command } from '../lib/canopy/CanopyExtension';
|
|
||||||
import { extension } from '../config';
|
|
||||||
import { structureCollection } from '../classes/StructureCollection';
|
|
||||||
import { MaterialCounter } from '../classes/MaterialCounter';
|
|
||||||
|
|
||||||
const structCmd = new Command({
|
|
||||||
name: 'struct',
|
|
||||||
description: { text: 'Manages current StrucTool structures.' },
|
|
||||||
usage: 'struct <name> <add/remove/place/layer/info> [args...]',
|
|
||||||
callback: structCommand,
|
|
||||||
args: [
|
|
||||||
{ type: 'string', name: 'name' },
|
|
||||||
{ type: 'string', name: 'option' },
|
|
||||||
{ type: 'string|number', name: 'arg3' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
extension.addCommand(structCmd);
|
|
||||||
|
|
||||||
function structCommand(sender, args) {
|
|
||||||
const { name, option, arg3 } = args;
|
|
||||||
switch (option) {
|
|
||||||
case 'add':
|
|
||||||
addStructure(sender, name);
|
|
||||||
break;
|
|
||||||
case 'remove':
|
|
||||||
removeStructure(sender, name);
|
|
||||||
break;
|
|
||||||
case 'place':
|
|
||||||
placeStructure(sender, name);
|
|
||||||
break;
|
|
||||||
case 'layer':
|
|
||||||
setLayer(sender, name, arg3);
|
|
||||||
break;
|
|
||||||
case 'info':
|
|
||||||
printInfo(sender, name);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
structCmd.sendUsage(sender);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addStructure(sender, name) {
|
|
||||||
try {
|
|
||||||
structureCollection.add(name, name);
|
|
||||||
} catch (e) {
|
|
||||||
if (e.message.includes('already exists')) {
|
|
||||||
sender.sendMessage({ text: `§cStructure '${name}' already exists.` });
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
sender.sendMessage({ text: `§cFailed to add structure '${name}'.` });
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sender.sendMessage({ text: `§7Added structure '${name}'` });
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeStructure(sender, name) {
|
|
||||||
try {
|
|
||||||
structureCollection.remove(name);
|
|
||||||
} catch (e) {
|
|
||||||
sender.sendMessage({ text: `§cStructure '${name}' not found.` });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sender.sendMessage({ text: `Removed structure '${name}'` });
|
|
||||||
}
|
|
||||||
|
|
||||||
function placeStructure(sender, name) {
|
|
||||||
let structure;
|
|
||||||
try {
|
|
||||||
structure = structureCollection.get(name);
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
structure = structureCollection.add(name, name);
|
|
||||||
} catch (e) {
|
|
||||||
if (e.message.includes('already exists')) {
|
|
||||||
sender.sendMessage({ text: `§cStructure '${name}' already exists.` });
|
|
||||||
return;
|
|
||||||
} else if (e.message.includes('not found')) {
|
|
||||||
sender.sendMessage({ text: `§cStructure '${name}' not found.` });
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
sender.sendMessage({ text: `§cFailed to place structure '${name}'.` });
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
structure.place(sender.dimension.id, sender.location);
|
|
||||||
sender.sendMessage({ text: `§7Placed structure '${name}'.` });
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLayer(sender, name, layer) {
|
|
||||||
let structure;
|
|
||||||
try {
|
|
||||||
structure = structureCollection.get(name);
|
|
||||||
} catch (e) {
|
|
||||||
sender.sendMessage({ text: `§cStructure '${name}' not found.` });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
structure.setLayer(layer);
|
|
||||||
sender.sendMessage({ text: `§7Set layer of structure '${name}' to ${layer}.` });
|
|
||||||
}
|
|
||||||
|
|
||||||
function printInfo(sender, name) {
|
|
||||||
let structure;
|
|
||||||
try {
|
|
||||||
structure = structureCollection.get(name);
|
|
||||||
} catch (e) {
|
|
||||||
sender.sendMessage({ text: `§cStructure '${name}' not found.` });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { dimensionId, location } = structure.getLocation();
|
|
||||||
sender.sendMessage({ text: `§7Structure '${name}' at [${location.x} ${location.y} ${location.z}] in '${dimensionId}'` });
|
|
||||||
sender.sendMessage({ text: `§7Current Layer: ${structure.getLayer()}` });
|
|
||||||
sender.sendMessage({ text: `§7Materials: ${MaterialCounter.getPrintable(name)}` });
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"format_version": 2,
|
||||||
|
"header": {
|
||||||
|
"name": "StrucTool [RP] v1.0.0",
|
||||||
|
"description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.",
|
||||||
|
"uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4",
|
||||||
|
"version": [1, 0, 0],
|
||||||
|
"min_engine_version": [1,17,0]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "resources",
|
||||||
|
"uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0",
|
||||||
|
"version": [1, 0, 0]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", // StrucTool BP
|
||||||
|
"version": [1, 0, 0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0", // Canopy BP
|
||||||
|
"version": [1, 3, 9]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"authors": [ "ForestOfLight" ],
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.10.0",
|
||||||
|
"particle_effect": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "structool:blockoverlay_xy",
|
||||||
|
"basic_render_parameters": {
|
||||||
|
"material": "particles_blend",
|
||||||
|
"texture": "textures/particle/white"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:emitter_rate_instant": {
|
||||||
|
"num_particles": 1
|
||||||
|
},
|
||||||
|
"minecraft:emitter_lifetime_once": {
|
||||||
|
"active_time": 1
|
||||||
|
},
|
||||||
|
"minecraft:emitter_shape_point": {},
|
||||||
|
"minecraft:particle_lifetime_expression": {
|
||||||
|
"max_lifetime": "variable.lifetime"
|
||||||
|
},
|
||||||
|
"minecraft:particle_appearance_billboard": {
|
||||||
|
"size": [0.5, 0.5],
|
||||||
|
"facing_camera_mode": "emitter_transform_xy"
|
||||||
|
},
|
||||||
|
"minecraft:particle_appearance_tinting": {
|
||||||
|
"color": [
|
||||||
|
"variable.face_color.r",
|
||||||
|
"variable.face_color.g",
|
||||||
|
"variable.face_color.b",
|
||||||
|
"variable.face_color.a"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.10.0",
|
||||||
|
"particle_effect": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "structool:blockoverlay_xz",
|
||||||
|
"basic_render_parameters": {
|
||||||
|
"material": "particles_blend",
|
||||||
|
"texture": "textures/particle/white"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:emitter_rate_instant": {
|
||||||
|
"num_particles": 1
|
||||||
|
},
|
||||||
|
"minecraft:emitter_lifetime_once": {
|
||||||
|
"active_time": 1
|
||||||
|
},
|
||||||
|
"minecraft:emitter_shape_point": {},
|
||||||
|
"minecraft:particle_lifetime_expression": {
|
||||||
|
"max_lifetime": "variable.lifetime"
|
||||||
|
},
|
||||||
|
"minecraft:particle_appearance_billboard": {
|
||||||
|
"size": [0.5, 0.5],
|
||||||
|
"facing_camera_mode": "emitter_transform_xz"
|
||||||
|
},
|
||||||
|
"minecraft:particle_appearance_tinting": {
|
||||||
|
"color": [
|
||||||
|
"variable.face_color.r",
|
||||||
|
"variable.face_color.g",
|
||||||
|
"variable.face_color.b",
|
||||||
|
"variable.face_color.a"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.10.0",
|
||||||
|
"particle_effect": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "structool:blockoverlay_yz",
|
||||||
|
"basic_render_parameters": {
|
||||||
|
"material": "particles_blend",
|
||||||
|
"texture": "textures/particle/white"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:emitter_rate_instant": {
|
||||||
|
"num_particles": 1
|
||||||
|
},
|
||||||
|
"minecraft:emitter_lifetime_once": {
|
||||||
|
"active_time": 1
|
||||||
|
},
|
||||||
|
"minecraft:emitter_shape_point": {},
|
||||||
|
"minecraft:particle_lifetime_expression": {
|
||||||
|
"max_lifetime": "variable.lifetime"
|
||||||
|
},
|
||||||
|
"minecraft:particle_appearance_billboard": {
|
||||||
|
"size": [0.5, 0.5],
|
||||||
|
"facing_camera_mode": "emitter_transform_yz"
|
||||||
|
},
|
||||||
|
"minecraft:particle_appearance_tinting": {
|
||||||
|
"color": [
|
||||||
|
"variable.face_color.r",
|
||||||
|
"variable.face_color.g",
|
||||||
|
"variable.face_color.b",
|
||||||
|
"variable.face_color.a"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"format_version": "1.10.0",
|
||||||
|
"particle_effect": {
|
||||||
|
"description": {
|
||||||
|
"identifier": "structool:outline",
|
||||||
|
"basic_render_parameters": {
|
||||||
|
"material": "particles_alpha",
|
||||||
|
"texture": "textures/particle/particles"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"minecraft:emitter_rate_instant": {
|
||||||
|
"num_particles": 1
|
||||||
|
},
|
||||||
|
"minecraft:emitter_lifetime_once": {
|
||||||
|
"active_time": 1
|
||||||
|
},
|
||||||
|
"minecraft:emitter_shape_point": {},
|
||||||
|
"minecraft:particle_lifetime_expression": {
|
||||||
|
"max_lifetime": 1
|
||||||
|
},
|
||||||
|
"minecraft:particle_appearance_billboard": {
|
||||||
|
"size": [0.2, 0.2],
|
||||||
|
"facing_camera_mode": "rotate_xyz",
|
||||||
|
"uv": {
|
||||||
|
"texture_width": 16,
|
||||||
|
"texture_height": 16,
|
||||||
|
"uv": [6, 11],
|
||||||
|
"uv_size": [1, 1]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minecraft:particle_appearance_tinting": {
|
||||||
|
"color": [
|
||||||
|
"variable.dot_color.r",
|
||||||
|
"variable.dot_color.g",
|
||||||
|
"variable.dot_color.b",
|
||||||
|
"variable.dot_color.a"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 83 B |
Reference in New Issue
Block a user