diff --git a/Construct[BP]/scripts/classes/Enums/InstanceButtons.js b/Construct[BP]/scripts/classes/Enums/InstanceButtons.js index a066923..d72087e 100644 --- a/Construct[BP]/scripts/classes/Enums/InstanceButtons.js +++ b/Construct[BP]/scripts/classes/Enums/InstanceButtons.js @@ -10,5 +10,6 @@ export const InstanceButtons = Object.freeze({ PreviousLayer: 'Decrease Layer', Move: 'Move Here', Statistics: 'Statistics', - Settings: 'Settings' + Settings: 'Settings', + Materials: 'Material List' }); \ No newline at end of file diff --git a/Construct[BP]/scripts/classes/Instance/InstanceForm.js b/Construct[BP]/scripts/classes/Instance/InstanceForm.js index e55d7b4..e5a3f85 100644 --- a/Construct[BP]/scripts/classes/Instance/InstanceForm.js +++ b/Construct[BP]/scripts/classes/Instance/InstanceForm.js @@ -12,8 +12,9 @@ export class InstanceForm { InstanceButtons.NextLayer, InstanceButtons.PreviousLayer, InstanceButtons.Move, - InstanceButtons.Statistics, InstanceButtons.Settings, + InstanceButtons.Statistics, + InstanceButtons.Materials, InstanceButtons.Rename, InstanceButtons.Disable ], @@ -90,15 +91,18 @@ export class InstanceForm { this.instance.decreaseLayer(); new InstanceForm(this.player, this.instanceName); break; - case InstanceButtons.Settings: - this.settingsForm(); - break; case InstanceButtons.Move: this.instance.move(this.player.dimension.id, this.player.location); break; + case InstanceButtons.Settings: + this.settingsForm(); + break; case InstanceButtons.Statistics: this.statisticsForm(); break; + case InstanceButtons.Materials: + this.materialsForm(); + break; case InstanceButtons.MainMenu: new MenuForm(this.player, { jumpToInstance: false }); break; @@ -160,7 +164,15 @@ export class InstanceForm { this.instance.setVerifierDistance(5); else this.instance.setVerifierDistance(0); - this.instance.setLayer(parseInt(response.formValues[3])); + this.instance.setLayer(parseInt(response.formValues[2])); + }); + } + + materialsForm(onlyShowMissingMaterials = false) { + forceShow(this.player, InstanceFormBuilder.buildMaterialList(this.instance, onlyShowMissingMaterials, this.player)).then((response) => { + if (response.canceled) return; + if (response.selection === 0) + this.materialsForm(!onlyShowMissingMaterials); }); } } \ No newline at end of file diff --git a/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js b/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js index 7659577..2fd0fe0 100644 --- a/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js +++ b/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js @@ -2,7 +2,7 @@ import { ActionFormData, ModalFormData } from '@minecraft/server-ui'; import { MenuFormBuilder } from '../MenuFormBuilder'; import { StructureVerifier } from '../Verifier/StructureVerifier'; import { StructureStatistics } from '../Structure/StructureStatistics'; -import { TicksPerSecond } from '@minecraft/server'; +import { EntityComponentTypes, TicksPerSecond } from '@minecraft/server'; export class InstanceFormBuilder { static structureVerifier; @@ -46,9 +46,39 @@ export class InstanceFormBuilder { return new ModalFormData() .title(MenuFormBuilder.menuTitle) .toggle('Block Validation', { defaultValue: instance.options.verifier.isEnabled, tooltip: 'Shows missing and incorrect block overlay.' }) - .toggle('Distance-Based Block Validation', { defaultValue: instance.verifier.getTrackPlayerDistance() !== 0, tooltip: `If enabled, the verifier will only check within ${instance.verifier.getTrackPlayerDistance()} blocks of the player. This should stay enabled unless your structure is very small.` }) - .label('Use the slider to select the layer. Use 0 for all layers.') - .slider("Layer", 0, instance.getMaxLayer(), { defaultValue: instance.getLayer(), valueStep: 1 }) + .toggle('Distance-Based Block Validation', { defaultValue: instance.verifier.getTrackPlayerDistance() !== 0, tooltip: `If enabled, the verifier will only check within ${instance.verifier.getTrackPlayerDistance()} blocks of the player. This should stay enabled unless your structure is very small or in layer mode.` }) + .slider("Layer", 0, instance.getMaxLayer(), { defaultValue: instance.getLayer(), valueStep: 1, tooltip: 'Changes the active layer. Use 0 for all layers.' }) .submitButton('§2Apply'); } + + static buildMaterialList(instance, onlyMissing = false, player = false) { + const materials = instance.getActiveMaterials(); + const form = new ActionFormData() + .title(MenuFormBuilder.menuTitle) + const bodyText = { rawtext: [] }; + let buttonText = void 0; + if (onlyMissing) { + const inventoryContainer = player?.getComponent(EntityComponentTypes.Inventory)?.container; + if (!inventoryContainer) { + form.body('§cNo player inventory found.'); + return form; + } + bodyText.rawtext.push({ text: `§cMaterials Missing From Inventory:` }); + if (instance.hasLayerSelected()) + bodyText.rawtext.push({ text: ` §7(layer ${instance.getLayer()})` }); + bodyText.rawtext.push({ text: `§f\n\n` }); + bodyText.rawtext.push(materials.formatString(materials.getMaterialsDifference(inventoryContainer))); + buttonText = "Show All Materials"; + } else { + bodyText.rawtext.push({ text: `§aAll Materials:` }); + if (instance.hasLayerSelected()) + bodyText.rawtext.push({ text: ` §7(layer ${instance.getLayer()})` }); + bodyText.rawtext.push({ text: `§f\n\n` }); + bodyText.rawtext.push(materials.formatString()); + buttonText = "Show Missing Materials"; + } + form.body(bodyText); + form.button(buttonText); + return form; + } } \ No newline at end of file diff --git a/Construct[BP]/scripts/classes/Materials/MaterialsForm.js b/Construct[BP]/scripts/classes/Materials/MaterialGrabberForm.js similarity index 82% rename from Construct[BP]/scripts/classes/Materials/MaterialsForm.js rename to Construct[BP]/scripts/classes/Materials/MaterialGrabberForm.js index 6245af6..45c05fa 100644 --- a/Construct[BP]/scripts/classes/Materials/MaterialsForm.js +++ b/Construct[BP]/scripts/classes/Materials/MaterialGrabberForm.js @@ -1,9 +1,9 @@ -import { MaterialsFormBuilder } from './MaterialsFormBuilder'; +import { MaterialGrabberFormBuilder } from './MaterialGrabberFormBuilder'; import { forceShow } from '../../utils'; import { Builders } from '../Builder/Builders'; import { structureCollection } from '../Structure/StructureCollection'; -export class MaterialsForm { +export class MaterialGrabberForm { constructor(player) { this.player = player; this.show(); @@ -11,7 +11,7 @@ export class MaterialsForm { show() { try { - return forceShow(this.player, MaterialsFormBuilder.buildInstanceSelector(this.player)).then((response) => { + return forceShow(this.player, MaterialGrabberFormBuilder.buildInstanceSelector(this.player)).then((response) => { if (response.canceled) return; const selectedInstanceName = structureCollection.getInstanceNames()[response.selection]; diff --git a/Construct[BP]/scripts/classes/Materials/MaterialsFormBuilder.js b/Construct[BP]/scripts/classes/Materials/MaterialGrabberFormBuilder.js similarity index 95% rename from Construct[BP]/scripts/classes/Materials/MaterialsFormBuilder.js rename to Construct[BP]/scripts/classes/Materials/MaterialGrabberFormBuilder.js index c5efc8f..b5a0fdd 100644 --- a/Construct[BP]/scripts/classes/Materials/MaterialsFormBuilder.js +++ b/Construct[BP]/scripts/classes/Materials/MaterialGrabberFormBuilder.js @@ -3,7 +3,7 @@ import { MenuFormBuilder } from "../MenuFormBuilder"; import { structureCollection } from "../Structure/StructureCollection"; import { Builders } from "../Builder/Builders"; -export class MaterialsFormBuilder { +export class MaterialGrabberFormBuilder { static menuTitle = MenuFormBuilder.menuTitle + ' Material Grabber'; static buildInstanceSelector(player) { diff --git a/Construct[BP]/scripts/classes/Materials/StructureMaterials.js b/Construct[BP]/scripts/classes/Materials/StructureMaterials.js index 6b85e94..81d3582 100644 --- a/Construct[BP]/scripts/classes/Materials/StructureMaterials.js +++ b/Construct[BP]/scripts/classes/Materials/StructureMaterials.js @@ -1,3 +1,5 @@ +import { ItemStack } from "@minecraft/server"; + class StructureMaterials { instance; materials; @@ -74,28 +76,54 @@ class StructureMaterials { delete this.materials[key]; } - toString() { - let message = []; - for (const blockType in this.materials) { - let count = this.materials[blockType].count; + formatString(otherMaterials = void 0) { + const materials = otherMaterials || this.materials; + let message = { rawtext: [] }; + const sortedTypes = Object.keys(materials).sort( + (a, b) => materials[b].count - materials[a].count + ); + for (const blockType of sortedTypes) { + let count = materials[blockType].count; let countStr = ''; - const stackSize = this.materials[blockType].stackSize; + const stackSize = materials[blockType].stackSize; const fullShulker = 27 * stackSize; if (count >= fullShulker) - countStr = `${Math.floor(count / fullShulker)} \uE200`; - if (count > fullShulker) + countStr = `${Math.floor(count / fullShulker)}\uE200`; + if (count > fullShulker && count % fullShulker > 0) countStr += ' + '; count %= fullShulker; - if (count >= stackSize) - countStr += `${Math.floor(count / stackSize)} stack`; - if (count > stackSize) + if (count >= stackSize) { + const numStacks = Math.floor(count / stackSize); + countStr += `${numStacks} stack`; + if (numStacks > 1) + countStr += 's'; + } + if (count > stackSize && count % stackSize > 0) countStr += ' + '; count %= stackSize; if (count > 0) countStr += count; - message.push(` ${blockType}: ${countStr}`); + message.rawtext.push({ text: '§3' }); + message.rawtext.push({ translate: new ItemStack(blockType).localizationKey }) + message.rawtext.push({ text: `§f: ${countStr}\n` }); } - return message.sort().join('\n'); + return message; + } + + getMaterialsDifference(container) { + const missingMaterials = JSON.parse(JSON.stringify(this.materials)); + for (let slotIndex = 0; slotIndex < container.size; slotIndex++) { + const slot = container.getSlot(slotIndex); + if (slot.hasItem()) { + const itemType = slot.getItem().typeId; + if (!missingMaterials[itemType]) + continue; + missingMaterials[itemType].count -= slot.amount; + if (missingMaterials[itemType].count <= 0) + delete missingMaterials[itemType]; + } + } + return missingMaterials; } } diff --git a/Construct[BP]/scripts/options/materialGrabber.js b/Construct[BP]/scripts/options/materialGrabber.js index b7ab95e..43a457a 100644 --- a/Construct[BP]/scripts/options/materialGrabber.js +++ b/Construct[BP]/scripts/options/materialGrabber.js @@ -1,6 +1,6 @@ import { BuilderOption } from '../classes/Builder/BuilderOption'; import { EntityComponentTypes, ItemStack, Player, world, system } from '@minecraft/server'; -import { MaterialsForm } from '../classes/Materials/MaterialsForm'; +import { MaterialGrabberForm } from '../classes/Materials/MaterialGrabberForm'; import { Builders } from '../classes/Builder/Builders'; import { structureCollection } from '../classes/Structure/StructureCollection'; @@ -19,7 +19,7 @@ function onItemUse(event) { if (!isActionItem(event.itemStack) || !builderOption.isEnabled(event.source.id)) return; event.cancel = true; - system.run(() => new MaterialsForm(event.source)); + system.run(() => new MaterialGrabberForm(event.source)); } function onPlayerInteract(event) {