Verification with block rendering and all

This commit is contained in:
ForestOfLight
2025-04-15 19:29:43 -07:00
Unverified
parent 1243cb7d8b
commit 72e9b65d38
16 changed files with 251 additions and 137 deletions
@@ -1,57 +1,67 @@
import { MolangVariableMap } from "@minecraft/server"; import { MolangVariableMap } from "@minecraft/server";
import { BlockVerificationLevel } from "./BlockVerificationLevel"; import { BlockVerificationLevel } from "./enums/BlockVerificationLevel";
import { Vector } from "../lib/Vector"; import { Vector } from "../lib/Vector";
export class BlockVerificationLevelRender { export class BlockVerificationLevelRender {
opacity = 0.5; opacity = 0.2;
lifetime = 0;
constructor(dimension, globalLocation, verificationLevel) { constructor(dimensionLocation, verificationLevel, lifetime = 5) {
this.dimension = dimension; this.dimension = dimensionLocation.dimension;
this.location = new Vector(globalLocation.x, globalLocation.y, globalLocation.z); this.location = new Vector(dimensionLocation.location.x, dimensionLocation.location.y, dimensionLocation.location.z);
this.verificationLevel = verificationLevel; this.verificationLevel = verificationLevel;
this.lifetime = lifetime;
this.renderBlock(); this.renderBlock();
} }
renderBlock() { renderBlock() {
for (const [key, value] of Object.entries(this.getParticleLocations())) { for (const particleLocation of this.getParticleLocations()) {
this.dimension.spawnParticle(key, value, this.getRGBAMolang()); const color = this.getRGBAMolang();
if (!color)
return;
color.setFloat("lifetime", this.lifetime);
try {
this.dimension.spawnParticle(particleLocation.particleType, particleLocation.location, color);
} catch {
/* pass */
}
} }
} }
getParticleLocations() { getParticleLocations() {
const bottomFace = new Vector(0, 0, 0); const bottomFace = new Vector(0.5, 0, 0.5);
const topFace = new Vector(0, 1, 0); const topFace = new Vector(0.5, 1, 0.5);
const leftFace = new Vector(0.5, 0.5, 0); const leftFace = new Vector(1, 0.5, 0.5);
const rightFace = new Vector(-0.5, 0.5, 0); const rightFace = new Vector(0, 0.5, 0.5);
const frontFace = new Vector(0, 0.5, 0.5); const frontFace = new Vector(0.5, 0.5, 1);
const backFace = new Vector(0, 0.5, -0.5); const backFace = new Vector(0.5, 0.5, 0);
return { return [
"structool:blockerlay_xz": this.location.add(topFace), { particleType: "structool:blockoverlay_xz", location: this.location.add(topFace) },
"structool:blockerlay_xz": this.location.add(bottomFace), { particleType: "structool:blockoverlay_xz", location: this.location.add(bottomFace) },
"structool:blockerlay_yz": this.location.add(leftFace), { particleType: "structool:blockoverlay_yz", location: this.location.add(leftFace) },
"structool:blockerlay_yz": this.location.add(rightFace), { particleType: "structool:blockoverlay_yz", location: this.location.add(rightFace) },
"structool:blockerlay_xy": this.location.add(frontFace), { particleType: "structool:blockoverlay_xy", location: this.location.add(frontFace) },
"structool:blockerlay_xy": this.location.add(backFace), { particleType: "structool:blockoverlay_xy", location: this.location.add(backFace) }
} ];
} }
getRGBAMolang() { getRGBAMolang() {
const rgb = this.verificationLevelToRGB(); const rgb = this.verificationLevelToRGB();
if (!rgb) return; if (!rgb) return;
rgb.alpha = this.opacity; rgb.alpha = this.opacity;
return new MolangVariableMap().setColorRGBA("minecraft:particle_appearance_tinting", rgb); const molang = new MolangVariableMap();
molang.setColorRGBA("face_color", rgb);
return molang;
} }
verificationLevelToRGB() { verificationLevelToRGB() {
switch (this.verificationLevel) { switch (this.verificationLevel) {
case BlockVerificationLevel.NoMatch: case BlockVerificationLevel.NoMatch:
return [1, 0, 0]; return { red: 1, green: 0, blue: 0};
case BlockVerificationLevel.TypeMatch: case BlockVerificationLevel.TypeMatch:
return [1, 1, 0]; return { red: 1, green: 1, blue: 0};
case BlockVerificationLevel.Match:
return [0, 1, 0];
case BlockVerificationLevel.Missing: case BlockVerificationLevel.Missing:
return [0, 0, 1]; return { red: 0, green: 0, blue: 1};
default: default:
return void 0; 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) {
@@ -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, { shouldRender: true }); const verification = await structureVerifier.verifyStructure();
const statistics = await structureVerifier.verifyStructure(); const statistics = new StructureStatistics(instance, verification);
message += `§fStatistics for §a${instance.name}§f:\n`; const statsMessage = statistics.getMessage();
message += `§7Blocks: §2${instance.getTotalVolume() - statistics.correctlyAir}\n`; buildStatisticsForm.body(statsMessage);
message += `§7Correct: §a${this.getFormattedStatistic(statistics, BlockVerificationLevel.Match)}\n`; return { form: buildStatisticsForm, stats: statsMessage };
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)}%%)`;
} }
} }
+30 -12
View File
@@ -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 = "structool:outline"; drawParticle = "structool:outline";
drawFrequency = 20; 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,97 +1,85 @@
import { BlockVerifier } from "./BlockVerifier"; import { BlockVerifier } from "./BlockVerifier";
import { BlockVerificationLevel } from "./BlockVerificationLevel"; import { BlockVerificationLevel } from "./enums/BlockVerificationLevel";
import { BlockVerificationLevelRender } from "./BlockVerificationLevelRender"; import { BlockVerificationLevelRender } from "./BlockVerificationLevelRender";
import { system, TicksPerSecond } from "@minecraft/server";
export class StructureVerifier { export class StructureVerifier {
shouldRender; shouldRender;
isComplete; isComplete;
interval = 5*20;
#runner;
constructor(instance, { shouldRender = false } = {}) { 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 = {}; }
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.shouldRender = shouldRender;
this.isComplete = false; this.isComplete = false;
this.initStatistics();
} }
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) => {
system.runJob(this.verifyBlocks); if (this.instance.isUsingLayers())
system.runJob(this.verifyBlocks(this.instance.getLayerBlocks(this.instance.getLayer()-1)));
else
system.runJob(this.verifyBlocks(this.instance.getBlocks()));
const checker = system.runInterval(() => { const checker = system.runInterval(() => {
if (this.isComplete) { if (this.isComplete) {
system.clearRun(checker); system.clearRun(checker);
resolve(); resolve(this.blockVerificationLevels);
} }
}, 1); }, 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.Air) 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) };
if (this.shouldRender) new BlockVerificationLevelRender(dimensionLocation, verificationLevel, this.interval/TicksPerSecond);
new BlockVerificationLevelRender(this.instance.getDimension(), this.instance.toGlobalCoords(block.location), verificationLevel); }
yield; }
yield void 0;
} }
this.isComplete = true; this.isComplete = true;
} }
// 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.Air)
// this.blockVerificationLevels[JSON.stringify(block.location)] = verificationLevel;
// else
// this.statistics.correctlyAir++;
// }
// }
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;
}
} }
@@ -4,5 +4,6 @@ export const BlockVerificationLevel = Object.freeze({
TypeMatch: 2, TypeMatch: 2,
Match: 3, Match: 3,
Missing: 4, Missing: 4,
Air: 5 Air: 5,
Skipped: 6
}); });
Binary file not shown.
@@ -17,14 +17,19 @@
}, },
"minecraft:emitter_shape_point": {}, "minecraft:emitter_shape_point": {},
"minecraft:particle_lifetime_expression": { "minecraft:particle_lifetime_expression": {
"max_lifetime": 1 "max_lifetime": "variable.lifetime"
}, },
"minecraft:particle_appearance_billboard": { "minecraft:particle_appearance_billboard": {
"size": [0.5, 0.5], "size": [0.5, 0.5],
"facing_camera_mode": "emitter_transform_xy" "facing_camera_mode": "emitter_transform_xy"
}, },
"minecraft:particle_appearance_tinting": { "minecraft:particle_appearance_tinting": {
"color": [0, 0, 0, 0.5] "color": [
"variable.face_color.r",
"variable.face_color.g",
"variable.face_color.b",
"variable.face_color.a"
]
} }
} }
} }
@@ -17,14 +17,19 @@
}, },
"minecraft:emitter_shape_point": {}, "minecraft:emitter_shape_point": {},
"minecraft:particle_lifetime_expression": { "minecraft:particle_lifetime_expression": {
"max_lifetime": 1 "max_lifetime": "variable.lifetime"
}, },
"minecraft:particle_appearance_billboard": { "minecraft:particle_appearance_billboard": {
"size": [0.5, 0.5], "size": [0.5, 0.5],
"facing_camera_mode": "emitter_transform_xz" "facing_camera_mode": "emitter_transform_xz"
}, },
"minecraft:particle_appearance_tinting": { "minecraft:particle_appearance_tinting": {
"color": [0, 0, 0, 0.5] "color": [
"variable.face_color.r",
"variable.face_color.g",
"variable.face_color.b",
"variable.face_color.a"
]
} }
} }
} }
@@ -17,14 +17,19 @@
}, },
"minecraft:emitter_shape_point": {}, "minecraft:emitter_shape_point": {},
"minecraft:particle_lifetime_expression": { "minecraft:particle_lifetime_expression": {
"max_lifetime": 1 "max_lifetime": "variable.lifetime"
}, },
"minecraft:particle_appearance_billboard": { "minecraft:particle_appearance_billboard": {
"size": [0.5, 0.5], "size": [0.5, 0.5],
"facing_camera_mode": "emitter_transform_yz" "facing_camera_mode": "emitter_transform_yz"
}, },
"minecraft:particle_appearance_tinting": { "minecraft:particle_appearance_tinting": {
"color": [0, 0, 0, 0.5] "color": [
"variable.face_color.r",
"variable.face_color.g",
"variable.face_color.b",
"variable.face_color.a"
]
} }
} }
} }
@@ -4,7 +4,7 @@
"description": { "description": {
"identifier": "structool:outline", "identifier": "structool:outline",
"basic_render_parameters": { "basic_render_parameters": {
"material": "particles_blend", "material": "particles_alpha",
"texture": "textures/particle/particles" "texture": "textures/particle/particles"
} }
}, },
@@ -30,7 +30,12 @@
} }
}, },
"minecraft:particle_appearance_tinting": { "minecraft:particle_appearance_tinting": {
"color": [0, 0.66667, 0, 1] "color": [
"variable.dot_color.r",
"variable.dot_color.g",
"variable.dot_color.b",
"variable.dot_color.a"
]
} }
} }
} }