From 830b54e55a7de3b96462f8e320f8ffe7a6545c0d Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Tue, 11 Mar 2025 02:13:16 -0700 Subject: [PATCH] Material counter and cleanup --- scripts/classes/MaterialCounter.js | 73 ++++++++++++++++++++++++ scripts/classes/Outliner.js | 3 +- scripts/classes/Structure.js | 79 ++++++++++++++++++++++---- scripts/classes/StructureCollection.js | 14 +---- scripts/commands/struct.js | 72 ++++++++++++++++++++--- 5 files changed, 211 insertions(+), 30 deletions(-) create mode 100644 scripts/classes/MaterialCounter.js diff --git a/scripts/classes/MaterialCounter.js b/scripts/classes/MaterialCounter.js new file mode 100644 index 0000000..3c7974c --- /dev/null +++ b/scripts/classes/MaterialCounter.js @@ -0,0 +1,73 @@ +import { structureCollection } from '../classes/StructureCollection'; + +class MaterialCounter { + static getAll(name) { + const structure = structureCollection.get(name); + const materials = {}; + for (const block of structure.getBlocks()) { + const typeId = block?.getItemStack()?.typeId.replace('minecraft:', ''); + if (!typeId) continue; + if (!materials[typeId]) { + materials[typeId] = 0; + } + materials[typeId]++; + } + return materials; + } + + static getLayer(name, layer) { + const structure = structureCollection.get(name); + const materials = {}; + for (const block of structure.getLayerBlocks(layer)) { + const typeId = block?.getItemStack()?.typeId.replace('minecraft:', ''); + if (!typeId) continue; + if (!materials[typeId]) { + materials[typeId] = 0; + } + materials[typeId]++; + } + return materials; + } + + static getPrintable(name) { + const structure = structureCollection.get(name); + const materials = {}; + for (const block of structure.getBlocks()) { + const itemStack = block?.getItemStack(); + const typeId = itemStack?.typeId.replace('minecraft:', ''); + if (!typeId) continue; + if (!materials[typeId]) { + materials[typeId] = { count: 0, maxStack: itemStack.maxAmount }; + } + materials[typeId].count++; + } + let message = []; + for (const blockType in materials) { + let count = materials[blockType].count; + let countStr = ''; + const maxStack = materials[blockType].maxStack; + const fullShulker = 27 * maxStack; + if (count >= fullShulker) { + countStr = `${Math.floor(count / fullShulker)} sb`; + } + if (count > fullShulker) { + countStr += ' + '; + } + count %= fullShulker; + if (count >= maxStack) { + countStr += `${Math.floor(count / maxStack)} stack`; + } + if (count > maxStack) { + countStr += ' + '; + } + count %= maxStack; + if (count > 0) { + countStr += count; + } + message.push(` ${blockType}: ${countStr}`); + } + return message.sort().join('\n'); + } +} + +export { MaterialCounter }; \ No newline at end of file diff --git a/scripts/classes/Outliner.js b/scripts/classes/Outliner.js index 18d2a61..f3a37a2 100644 --- a/scripts/classes/Outliner.js +++ b/scripts/classes/Outliner.js @@ -1,4 +1,4 @@ -import { MolangVariableMap, system, world } from "@minecraft/server"; +import { system, world } from "@minecraft/server"; import { Vector } from "../lib/Vector"; const drawFrequency = 8; @@ -17,6 +17,7 @@ export class Outliner { this.dimension = world.getDimension(dimension); this.min = new Vector(min.x, min.y, min.z); this.max = new Vector(max.x, max.y, max.z); + this.startDraw(); } startDraw() { diff --git a/scripts/classes/Structure.js b/scripts/classes/Structure.js index ba35f29..c743e51 100644 --- a/scripts/classes/Structure.js +++ b/scripts/classes/Structure.js @@ -8,12 +8,16 @@ export class Structure { dimensionId: MinecraftDimensionTypes.overworld, location: { x: 0, y: 0, z: 0 }, rotation: 0, - mirror: false + mirror: false, + currentLayer: 0 }; constructor(structureName) { this.name = structureName; this.#structure = world.structureManager.get(structureName); + if (!this.#structure) { + throw new Error(`[StrucTool] Structure '${structureName}' not found.`); + } this.#options = this.loadOptions(); } @@ -29,16 +33,48 @@ export class Structure { updateOptions() { world.setDynamicProperty(`structOptions:${this.name}`, JSON.stringify(this.#options)); } - - getName() { - return this.name; - } - + getStructure() { return this.#structure; } - getOutlineLimits() { + getLocation() { + return this.#options.location; + } + + getHeight() { + return this.#structure.size.y; + } + + getLayer() { + return this.#options.currentLayer; + } + + *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 }); + } + } + } + } + + *getLayerBlocks(y) { + 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 }); + } + } + } + + getBlock(location) { + return this.#structure.getBlockPermutation(location); + } + + getBounds() { if (!this.#options.isPlaced) throw new Error(`[StrucTool] Structure '${this.name}' is not placed.`); return { @@ -55,6 +91,22 @@ export class Structure { } } + getLayeredBounds(useUnder = false) { + if (!this.#options.isPlaced) + throw new Error(`[StrucTool] Structure '${this.name}' is not placed.`); + if (useUnder) { + return { + min: { x: this.#options.location.x, y: this.#options.location.y, z: this.#options.location.z }, + max: { x: this.#options.location.x + this.#structure.size.x, y: this.#options.location.y + this.#options.currentLayer, z: this.#options.location.z + this.#structure.size.z } + } + } else { + return { + min: { x: this.#options.location.x, y: this.#options.location.y + this.#options.currentLayer - 1,z: this.#options.location.z }, + max: { x: this.#options.location.x + this.#structure.size.x, y: this.#options.location.y + this.#options.currentLayer, z: this.#options.location.z + this.#structure.size.z } + } + } + } + place(dimensionId, location) { this.#options = { isPlaced: true, @@ -62,8 +114,7 @@ export class Structure { location: { x: Math.floor(location.x), y: Math.floor(location.y), z: Math.floor(location.z) }, }; this.updateOptions(); - this.outliner = new Outliner(dimensionId, this.getOutlineLimits().min, this.getOutlineLimits().max); - this.outliner.startDraw(); + this.outliner = new Outliner(dimensionId, this.getBounds().min, this.getBounds().max); } remove() { @@ -72,6 +123,14 @@ export class Structure { this.#options.isPlaced = false; this.updateOptions(); this.outliner.stopDraw(); - delete this.outliner; + } + + setLayer(layer) { + if (layer < 1 || layer > this.#structure.size.y) + throw new Error(`[StrucTool] Structure '${this.name}' does not have layer ${layer}.`); + this.#options.currentLayer = layer; + this.updateOptions(); + this.outliner.stopDraw(); + this.outliner = new Outliner(this.#options.dimensionId, this.getLayeredBounds().min, this.getLayeredBounds().max); } } \ No newline at end of file diff --git a/scripts/classes/StructureCollection.js b/scripts/classes/StructureCollection.js index cbd578a..c393b19 100644 --- a/scripts/classes/StructureCollection.js +++ b/scripts/classes/StructureCollection.js @@ -1,4 +1,3 @@ -import { world } from '@minecraft/server'; import { Structure } from './Structure'; class StructureCollection { @@ -9,8 +8,9 @@ class StructureCollection { } add(name) { - const struct = world.structureManager.get(name); - this.#structures[name] = new Structure(name, struct); + const structure = new Structure(name); + this.#structures[name] = structure; + return structure; } get(name) { @@ -26,14 +26,6 @@ class StructureCollection { struct.remove(); delete this.#structures[name]; } - - place(name, dimensionId, location) { - const struct = this.get(name); - if (!struct) { - throw new Error(`Structure ${name} not found.`); - } - struct.place(dimensionId, location); - } } export const structureCollection = new StructureCollection(); \ No newline at end of file diff --git a/scripts/commands/struct.js b/scripts/commands/struct.js index 3ce064b..d4cf73e 100644 --- a/scripts/commands/struct.js +++ b/scripts/commands/struct.js @@ -1,6 +1,8 @@ import { Command } from '../lib/canopy/CanopyExtension'; import { extension } from '../config'; import { structureCollection } from '../classes/StructureCollection'; +import { system } from '@minecraft/server'; +import { MaterialCounter } from '../classes/MaterialCounter'; const structCmd = new Command({ name: 'struct', @@ -8,35 +10,89 @@ const structCmd = new Command({ usage: 'struct', callback: structCommand, args: [ + { type: 'string', name: 'name' }, { type: 'string', name: 'option' }, - { type: 'string', name: 'name' } + { type: 'string|number', name: 'arg3' } ] }); extension.addCommand(structCmd); function structCommand(sender, args) { - const { option, name, structure } = args; + const { option, name, arg3 } = args; switch (option) { case 'add': - addStructure(sender, name, structure); + addStructure(sender, name); break; case 'remove': removeStructure(sender, name); break; case 'place': - structureCollection.place(name, sender.dimension.id, sender.location); + 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, filename) { - structureCollection.add(name, filename); - sender.sendMessage({ text: `Added structure '${name}'` }); +function addStructure(sender, name) { + structureCollection.add(name); + sender.sendMessage({ text: `§7Added structure '${name}'` }); } function removeStructure(sender, name) { - structureCollection.remove(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); + } catch (e) { + sender.sendMessage({ text: `§cStructure '${name}' not found.` }); + return; + } + } + 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 location = structure.getLocation(); + sender.sendMessage({ text: `§7Structure '${name}' at [${location.x} ${location.y} ${location.z}]` }); + sender.sendMessage({ text: `§7Current Layer: ${structure.getLayer()}` }); + sender.sendMessage({ text: `§7Materials: ${MaterialCounter.getPrintable(name)}` }); } \ No newline at end of file