diff --git a/Construct[BP]/items/easy_place.json b/Construct[BP]/items/easy_place.json new file mode 100644 index 0000000..e4ed343 --- /dev/null +++ b/Construct[BP]/items/easy_place.json @@ -0,0 +1,22 @@ +{ + "format_version": "1.20.30", + "minecraft:item": { + "description": { + "identifier": "construct:easy_place", + "category": "Items" + }, + "components": { + "minecraft:max_stack_size": 1, + "minecraft:icon": { + "texture": "construct:easy_place" + }, + "minecraft:display_name": { + "value": "Easy Place" + }, + "minecraft:wearable": { + "dispensable": true, + "slot": "slot.weapon.offhand" + } + } + } +} \ No newline at end of file diff --git a/Construct[BP]/items/material_grabber.json b/Construct[BP]/items/material_grabber.json new file mode 100644 index 0000000..f3d45fd --- /dev/null +++ b/Construct[BP]/items/material_grabber.json @@ -0,0 +1,22 @@ +{ + "format_version": "1.20.30", + "minecraft:item": { + "description": { + "identifier": "construct:material_grabber", + "category": "Items" + }, + "components": { + "minecraft:max_stack_size": 1, + "minecraft:icon": { + "texture": "construct:material_grabber" + }, + "minecraft:display_name": { + "value": "Material Grabber" + }, + "minecraft:wearable": { + "dispensable": true, + "slot": "slot.weapon.offhand" + } + } + } +} \ No newline at end of file diff --git a/Construct[BP]/items/menu.json b/Construct[BP]/items/menu.json new file mode 100644 index 0000000..7994039 --- /dev/null +++ b/Construct[BP]/items/menu.json @@ -0,0 +1,22 @@ +{ + "format_version": "1.20.30", + "minecraft:item": { + "description": { + "identifier": "construct:menu", + "category": "Items" + }, + "components": { + "minecraft:max_stack_size": 1, + "minecraft:icon": { + "texture": "construct:menu" + }, + "minecraft:display_name": { + "value": "Construct" + }, + "minecraft:wearable": { + "dispensable": true, + "slot": "slot.weapon.offhand" + } + } + } +} \ No newline at end of file diff --git a/Construct[BP]/scripts/classes/Builder/BuilderOption.js b/Construct[BP]/scripts/classes/Builder/BuilderOption.js index cdd981c..a4fb937 100644 --- a/Construct[BP]/scripts/classes/Builder/BuilderOption.js +++ b/Construct[BP]/scripts/classes/Builder/BuilderOption.js @@ -25,12 +25,12 @@ export class BuilderOption { } setValue(playerId, value) { - if (value) - this.#onEnable(playerId); - else - this.#onDisable(playerId); if (this.isEnabled(playerId) !== value) { this.save(playerId, value); + if (value) + this.#onEnable(playerId); + else + this.#onDisable(playerId); return value; } this.save(playerId, value); diff --git a/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js b/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js index 386056e..d54c6f9 100644 --- a/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js +++ b/Construct[BP]/scripts/classes/Instance/InstanceFormBuilder.js @@ -3,7 +3,6 @@ import { MenuFormBuilder } from '../MenuFormBuilder'; import { StructureVerifier } from '../Verifier/StructureVerifier'; import { StructureStatistics } from '../Structure/StructureStatistics'; import { EntityComponentTypes, TicksPerSecond } from '@minecraft/server'; -import { BlockVerificationLevel } from '../Enums/BlockVerificationLevel'; export class InstanceFormBuilder { static structureVerifier; diff --git a/Construct[BP]/scripts/commands/construct.js b/Construct[BP]/scripts/commands/construct.js index 219b373..0755464 100644 --- a/Construct[BP]/scripts/commands/construct.js +++ b/Construct[BP]/scripts/commands/construct.js @@ -1,16 +1,16 @@ import { Command } from '../lib/canopy/CanopyExtension'; import { extension } from '../config'; -import { world, system } from '@minecraft/server'; +import { world, system, EntityComponentTypes, ItemStack } from '@minecraft/server'; import { MenuForm } from '../classes/MenuForm'; import { structureCollection } from '../classes/Structure/StructureCollection' -const ACTION_ITEM = 'minecraft:paper'; +const ACTION_ITEM = 'construct:menu'; const menuCmd = new Command({ name: 'construct', - description: { text: 'Opens the Construct Menu. Using a paper will also open the menu.' }, + description: { text: 'Gives you the Construct item. Use it to open the Construct menu.' }, usage: 'construct', - callback: (sender) => openMenu(sender) + callback: (sender) => sender.getComponent(EntityComponentTypes.Inventory)?.container?.addItem(new ItemStack(ACTION_ITEM)) }); extension.addCommand(menuCmd); diff --git a/Construct[BP]/scripts/options/easyPlace.js b/Construct[BP]/scripts/options/easyPlace.js index 7ad3ae8..63facdd 100644 --- a/Construct[BP]/scripts/options/easyPlace.js +++ b/Construct[BP]/scripts/options/easyPlace.js @@ -1,39 +1,62 @@ import { BuilderOption } from '../classes/Builder/BuilderOption'; -import { BlockPermutation, EntityComponentTypes, GameMode, ItemStack, system, world } from '@minecraft/server'; +import { BlockPermutation, EntityComponentTypes, EquipmentSlot, GameMode, ItemStack, system, world } from '@minecraft/server'; import { structureCollection } from '../classes/Structure/StructureCollection'; import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, specialItemPlacementConversions, blockIdToItemStackMap } from '../data'; import { fetchMatchingItemSlot } from '../utils'; - -const ACTION_SLOT = 27; +import { Builders } from '../classes/Builder/Builders'; const builderOption = new BuilderOption({ identifier: 'easyPlace', displayName: 'Easy Place', description: 'Always place the correct structure block.', - howToUse: "Name a paper 'Easy Place', then put it in the inventory slot above your first hotbar slot to always place the correct blocks in a structure." + howToUse: "Hold the Easy Place item in your offhand to always place the correct blocks in a structure.", + onEnableCallback: (playerId) => giveActionItem(playerId), + onDisableCallback: (playerId) => removeActionItem(playerId) }); +function giveActionItem(playerId) { + const player = world.getEntity(playerId); + const container = player.getComponent(EntityComponentTypes.Inventory)?.container; + const itemStack = new ItemStack('construct:easy_place'); + if (!container.contains(itemStack)) + container.addItem(itemStack); +} + +function removeActionItem(playerId) { + const builder = Builders.get(playerId); + if (builder.isOptionEnabled('fastEasyPlace')) + return; + const player = world.getEntity(playerId); + const container = player.getComponent(EntityComponentTypes.Inventory)?.container; + for (let i = 0; i < container.size; i++) { + const itemStack = container.getItem(i); + if (itemStack?.typeId === 'construct:easy_place') + container.setItem(i, void 0); + } + const equipment = player.getComponent(EntityComponentTypes.Equippable); + const offhandItemStack = equipment?.getEquipment(EquipmentSlot.Offhand); + if (offhandItemStack?.typeId === 'construct:easy_place') { + equipment.setEquipment(EquipmentSlot.Offhand, void 0); + } +} + world.beforeEvents.playerPlaceBlock.subscribe(onPlayerPlaceBlock); function onPlayerPlaceBlock(event) { const { player, block } = event; - if (!player || !block || !builderOption.isEnabled(player.id) || !hasActionItemInCorrectSlot(player)) return; + if (!player || !block || !builderOption.isEnabled(player.id) || !isHoldingActionItem(player)) return; const structureBlock = structureCollection.fetchStructureBlock(block.dimension.id, block.location); if (!structureBlock) return; tryPlaceBlock(event, player, block, structureBlock); } -function hasActionItemInCorrectSlot(player) { - const inventory = player.getComponent(EntityComponentTypes.Inventory)?.container; - if (!inventory) +function isHoldingActionItem(player) { + const offhandItemStack = player.getComponent(EntityComponentTypes.Equippable).getEquipment(EquipmentSlot.Offhand); + if (!offhandItemStack) return false; - const actionSlot = inventory.getSlot(ACTION_SLOT); - return actionSlot.hasItem() - && actionSlot.typeId === 'minecraft:paper' - && actionSlot.nameTag?.toLowerCase().includes('easy') - && actionSlot.nameTag?.toLowerCase().includes('place'); + return offhandItemStack.typeId === 'construct:easy_place'; } function tryPlaceBlock(event, player, block, structureBlock) { @@ -95,7 +118,6 @@ function tryConvertToDefaultState(structureBlock) { function tryPlaceBlockSurvival(event, player, block, structureBlock) { const placeableItemStack = getPlaceableItemStack(structureBlock); - // console.warn(`Looking for item to place ${structureBlock?.type.id} (${placeableItemStack?.typeId})...`); const itemSlotToUse = fetchMatchingItemSlot(player, placeableItemStack?.typeId); if (itemSlotToUse) { event.cancel = true; diff --git a/Construct[BP]/scripts/options/fastEasyPlace.js b/Construct[BP]/scripts/options/fastEasyPlace.js index fda754b..b025e31 100644 --- a/Construct[BP]/scripts/options/fastEasyPlace.js +++ b/Construct[BP]/scripts/options/fastEasyPlace.js @@ -1,8 +1,9 @@ import { BuilderOption } from '../classes/Builder/BuilderOption'; -import { BlockPermutation, EntityComponentTypes, EquipmentSlot, GameMode, ItemStack, system, world } from '@minecraft/server'; +import { BlockPermutation, EntityComponentTypes, EquipmentSlot, GameMode, InputMode, ItemStack, system, world } from '@minecraft/server'; import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, specialItemPlacementConversions, blockIdToItemStackMap } from '../data'; import { Raycaster } from '../classes/Raycaster'; +import { Builders } from '../classes/Builder/Builders'; const PROCESS_INTERVAL = 2; @@ -11,19 +12,55 @@ const builderOption = new BuilderOption({ identifier: 'fastEasyPlace', displayName: 'Fast Easy Place', description: 'Place correct structure blocks just by looking at them.', - howToUse: "Hold a paper named 'Easy Place' in your hand and look at blocks in a structure to place them." + howToUse: "Hold the Easy Place item in your main hand and look at blocks in a structure to place them.", + onEnableCallback: (playerId) => giveActionItem(playerId), + onDisableCallback: (playerId) => removeActionItem(playerId) }); +function giveActionItem(playerId) { + const player = world.getEntity(playerId); + const container = player.getComponent(EntityComponentTypes.Inventory)?.container; + const itemStack = new ItemStack('construct:easy_place'); + if (!container.contains(itemStack)) + container.addItem(itemStack); +} + +function removeActionItem(playerId) { + const builder = Builders.get(playerId); + if (builder.isOptionEnabled('easyPlace')) + return; + const player = world.getEntity(playerId); + const container = player.getComponent(EntityComponentTypes.Inventory)?.container; + for (let i = 0; i < container.size; i++) { + const itemStack = container.getItem(i); + if (itemStack?.typeId === 'construct:easy_place') + container.setItem(i, void 0); + } + const equipment = player.getComponent(EntityComponentTypes.Equippable); + const offhandItemStack = equipment?.getEquipment(EquipmentSlot.Offhand); + if (offhandItemStack?.typeId === 'construct:easy_place') { + equipment.setEquipment(EquipmentSlot.Offhand, void 0); + } +} + system.runInterval(onTick, PROCESS_INTERVAL); function onTick() { for (const player of world.getAllPlayers()) { - if (!player || !builderOption.isEnabled(player.id)) - continue; - processEasyPlace(player); + if (player && builderOption.isEnabled(player.id) && player.inputInfo.lastInputModeUsed === InputMode.Touch) + processEasyPlace(player); } } +world.beforeEvents.itemUse.subscribe(event => { + const player = event.source; + if (player && builderOption.isEnabled(event.source?.id) && player.inputInfo.lastInputModeUsed !== InputMode.Touch) { + system.run(() => { + processEasyPlace(event.source); + }); + } +}); + function processEasyPlace(player) { if (!player || !isHoldingActionItem(player)) return; const structureBlock = Raycaster.getTargetedStructureBlock(player, { isFirst: true }); @@ -37,9 +74,7 @@ function isHoldingActionItem(player) { const mainhandItemStack = player.getComponent(EntityComponentTypes.Equippable).getEquipment(EquipmentSlot.Mainhand); if (!mainhandItemStack) return false; - return mainhandItemStack.typeId === 'minecraft:paper' - && mainhandItemStack.nameTag?.toLowerCase().includes('easy') - && mainhandItemStack.nameTag?.toLowerCase().includes('place'); + return mainhandItemStack.typeId === 'construct:easy_place'; } function tryPlaceBlock(player, worldBlock, structureBlock) { diff --git a/Construct[BP]/scripts/options/materialGrabber.js b/Construct[BP]/scripts/options/materialGrabber.js index 0f69733..6080cea 100644 --- a/Construct[BP]/scripts/options/materialGrabber.js +++ b/Construct[BP]/scripts/options/materialGrabber.js @@ -1,5 +1,5 @@ import { BuilderOption } from '../classes/Builder/BuilderOption'; -import { EntityComponentTypes, ItemStack, Player, world, system } from '@minecraft/server'; +import { EntityComponentTypes, ItemStack, Player, world, system, EquipmentSlot } from '@minecraft/server'; import { MaterialGrabberForm } from '../classes/Materials/MaterialGrabberForm'; import { Builders } from '../classes/Builder/Builders'; import { structureCollection } from '../classes/Structure/StructureCollection'; @@ -8,28 +8,66 @@ const builderOption = new BuilderOption({ identifier: 'materialGrabber', displayName: 'Material Grabber', description: 'Pulls structure items from inventories.', - howToUse: "Interact with inventories using an item named 'Material Grabber' to pull structure items from them." + howToUse: "Interact with inventories using an item named 'Material Grabber' to pull structure items from them.", + onEnableCallback: (playerId) => giveActionItem(playerId), + onDisableCallback: (playerId) => removeActionItem(playerId) }); +function giveActionItem(playerId) { + const player = world.getEntity(playerId); + const container = player.getComponent(EntityComponentTypes.Inventory)?.container; + const itemStack = new ItemStack('construct:material_grabber'); + if (!container.contains(itemStack)) + container.addItem(itemStack); +} + +function removeActionItem(playerId) { + const player = world.getEntity(playerId); + const container = player.getComponent(EntityComponentTypes.Inventory)?.container; + for (let i = 0; i < container.size; i++) { + const itemStack = container.getItem(i); + if (itemStack?.typeId === 'construct:material_grabber') + container.setItem(i, void 0); + } + const equipment = player.getComponent(EntityComponentTypes.Equippable); + const offhandItemStack = equipment?.getEquipment(EquipmentSlot.Offhand); + if (offhandItemStack?.typeId === 'construct:material_grabber') { + equipment.setEquipment(EquipmentSlot.Offhand, void 0); + } +} + world.beforeEvents.itemUse.subscribe(onItemUse); world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract); world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract); function onItemUse(event) { - if (!isActionItem(event.itemStack) || !builderOption.isEnabled(event.source.id)) + if (!isActionItem(event.itemStack) || !builderOption.isEnabled(event.source?.id)) return; - event.cancel = true; - system.run(() => new MaterialGrabberForm(event.source)); + openInstanceSelectionForm(event.source, event); } function onPlayerInteract(event) { - if (!isActionItem(event.itemStack) || !builderOption.isEnabled(event.player.id)) + if (!isActionItem(event.itemStack) || !builderOption.isEnabled(event.player?.id)) return; const player = event.player; const target = event.block || event.target; + const focusedInstanceName = Builders.get(player.id).materialInstanceName; + if (!focusedInstanceName) { + openInstanceSelectionForm(player, event); + } else { + tryGrabMaterials(player, target, focusedInstanceName, event); + } +} + +function openInstanceSelectionForm(player, event) { + event.cancel = true; + system.run(() => new MaterialGrabberForm(player)); +} + +function tryGrabMaterials(player, target, focusedInstanceName, event) { if (target instanceof Player) return; - const materials = getActiveMaterials(player); + const materials = getActiveMaterials(focusedInstanceName); if (!materials) return; const targetContainer = target.getComponent(EntityComponentTypes.Inventory)?.container; @@ -40,18 +78,12 @@ function onPlayerInteract(event) { } function isActionItem(itemStack) { - return itemStack?.nameTag?.toLowerCase().includes('material') - && itemStack?.nameTag?.toLowerCase().includes('grabber'); + return itemStack?.typeId === 'construct:material_grabber'; } -function getActiveMaterials(player) { - const focusedInstanceName = Builders.get(player.id).materialInstanceName; - if (!focusedInstanceName) - return void 0; - const structure = structureCollection.get(focusedInstanceName); - if (!structure) - return void 0; - return structure.getActiveMaterials(); +function getActiveMaterials(focusedInstanceName) { + const instance = structureCollection.get(focusedInstanceName); + return instance?.getActiveMaterials(); } function transferMaterialsToPlayer(player, targetContainer, materials) { diff --git a/Construct[RP]/textures/item_texture.json b/Construct[RP]/textures/item_texture.json new file mode 100644 index 0000000..621032d --- /dev/null +++ b/Construct[RP]/textures/item_texture.json @@ -0,0 +1,14 @@ +{ + "resource_pack_name": "Construct Resource Pack", + "texture_data": { + "construct:menu": { + "textures": "textures/items/menu" + }, + "construct:easy_place": { + "textures": "textures/items/easy_place" + }, + "construct:material_grabber": { + "textures": "textures/items/material_grabber" + } + } +} \ No newline at end of file diff --git a/Construct[RP]/textures/items/easy_place.png b/Construct[RP]/textures/items/easy_place.png new file mode 100644 index 0000000..4d560e1 Binary files /dev/null and b/Construct[RP]/textures/items/easy_place.png differ diff --git a/Construct[RP]/textures/items/material_grabber.png b/Construct[RP]/textures/items/material_grabber.png new file mode 100644 index 0000000..8367a51 Binary files /dev/null and b/Construct[RP]/textures/items/material_grabber.png differ diff --git a/Construct[RP]/textures/items/menu.png b/Construct[RP]/textures/items/menu.png new file mode 100644 index 0000000..748a1c6 Binary files /dev/null and b/Construct[RP]/textures/items/menu.png differ