diff --git a/scripts/data.js b/scripts/data.js index 5c8eb22..e5f45c9 100644 --- a/scripts/data.js +++ b/scripts/data.js @@ -1,22 +1,32 @@ -export const bannedEasyPlaceBlocks = [ - 'air', 'bed', +export const bannedBlocks = [ + 'air', 'bed', 'piston_arm_collision', 'sticky_piston_arm_collision', "skeleton_skull", 'standing_banner', 'wall_banner', 'wooden_door', 'spruce_door', 'birch_door', 'jungle_door', 'acacia_door', 'dark_oak_door', 'mangrove_door', 'cherry_door', 'pale_oak_door', 'bamboo_door', 'iron_door', 'crimson_door', 'warped_door', 'copper_door', 'exposed_copper_door', 'weathered_copper_door', 'oxidized_copper_door', 'waxed_copper_door', 'waxed_exposed_copper_door', 'waxed_weathered_copper_door', 'waxed_oxidized_copper_door', - 'piston_arm_collision', 'sticky_piston_arm_collision', "skeleton_skull" + 'seagrass', 'kelp' ]; +export const bannedDimensionBlocks = { + 'overworld': [], + 'nether': ['water'], + 'end': [] +}; + +export const whitelistedBlockStates = { + 'water': { liquid_depth: 0 }, + 'lava': { liquid_depth: 0 } +}; + export const bannedToValidBlockMap = { 'lit_furnace': 'furnace', 'lit_smoker': 'smoker', 'lit_blast_furnace': 'blast_furnace', 'lit_redstone_ore': 'redstone_ore', - 'unlit_redstone_torch': 'redstone_torch', - 'bubble_column': 'water', - // 'seagrass'? 'tall_seagrass'? kelp? + 'lit_redstone_lamp': 'redstone_lamp', + 'unlit_redstone_torch': 'redstone_torch' }; -export const defaultStates = { +export const resetToBlockStates = { growth: 0, age: 0, height: 0, @@ -25,15 +35,49 @@ export const defaultStates = { redstone_signal: 0, cluster_count: 0, respawn_anchor_charge: 0, - turtle_egg_count: "one_egg", - cauldron_liquid: "water" + turtle_egg_count: 0, + cluster_count: 0 }; -export const allowedBlockStates = { - water: { depth_level: 0 }, - lava: { depth_level: 0 } +export const chainedStatePlacements = { + cauldron: { + cauldron_liquid: { + water: ['water_bucket'], + lava: ['lava_bucket'] + } + }, + sea_pickle: { + cluster_count: { + 1: ['sea_pickle'], + 2: ['sea_pickle', 'sea_pickle'], + 3: ['sea_pickle', 'sea_pickle', 'sea_pickle'] + } + }, + turtle_egg: { + turtle_egg_count: { + two_eggs: ['turtle_egg'], + three_eggs: ['turtle_egg', 'turtle_egg'], + four_eggs: ['turtle_egg', 'turtle_egg', 'turtle_egg'] + } + }, + respawn_anchor: { + respawn_anchor_charge: { + 1: ['glowstone'], + 2: ['glowstone', 'glowstone'], + 3: ['glowstone', 'glowstone', 'glowstone'], + 4: ['glowstone', 'glowstone', 'glowstone', 'glowstone'] + } + } }; -export const bannedDimensionBlocks = { - 'nether': ['water', 'bubble_column'] -}; \ No newline at end of file +export const blockIdToItemStackMap = { + 'water': 'water_bucket', + 'lava': 'lava_bucket', + 'fire': 'fire_charge', + 'soul_fire': 'fire_charge', +}; + +export const specialItemPlacementConversions = { + 'water_bucket': 'bucket', + 'lava_bucket': 'bucket' +} \ No newline at end of file diff --git a/scripts/rules/easyPlace.js b/scripts/rules/easyPlace.js index 94185fe..0bfe8dd 100644 --- a/scripts/rules/easyPlace.js +++ b/scripts/rules/easyPlace.js @@ -1,8 +1,9 @@ import { Rule } from '../lib/canopy/CanopyExtension'; import { extension } from '../config'; -import { EntityComponentTypes, GameMode, system, world } from '@minecraft/server'; +import { BlockPermutation, EntityComponentTypes, GameMode, ItemStack, system, world } from '@minecraft/server'; import { structureCollection } from '../classes/StructureCollection'; -import { bannedEasyPlaceBlocks } from '../data'; +import { bannedBlocks, bannedToValidBlockMap, whitelistedBlockStates, resetToBlockStates, bannedDimensionBlocks, specialItemPlacementConversions, + blockIdToItemStackMap } from '../data'; const easyPlace = new Rule({ identifier: 'easyPlace', @@ -16,9 +17,8 @@ function onPlayerPlaceBlock(event) { const { player, block } = event; if (!player || !block) return; const structureBlock = fetchStructureBlock(block.location); - if (!structureBlock || isBannedBlock(structureBlock)) + if (!structureBlock) return; - const states = structureBlock.getAllStates(); tryPlaceBlock(event, player, block, structureBlock); } @@ -30,36 +30,76 @@ function fetchStructureBlock(location) { return structure.getBlock(structure.toStructureCoords(location)); } -function isBannedBlock(structureBlock) { - return bannedEasyPlaceBlocks.some(bannedId => structureBlock.type.id.replace('minecraft:', '') === bannedId); -} - function tryPlaceBlock(event, player, block, structureBlock) { + if (isBannedBlock(player, structureBlock)) return; + structureBlock = tryConvertBannedToValidBlock(structureBlock); if (player.getGameMode() === GameMode.creative) { placeBlock(block, structureBlock); } else if (player.getGameMode() === GameMode.survival) { + structureBlock = tryConvertToDefaultState(structureBlock); tryPlaceBlockSurvival(event, player, block, structureBlock); } } +function isBannedBlock(player, structureBlock) { + const blockId = structureBlock.type.id.replace('minecraft:', ''); + if (bannedBlocks.includes(blockId)) + return true; + if (bannedDimensionBlocks[player.dimension.id.replace('minecraft:', '')]?.includes(blockId)) + return true; + const allowedStates = whitelistedBlockStates[blockId]; + if (allowedStates) { + for (const [stateKey, stateValue] of Object.entries(allowedStates)) { + if (structureBlock.getState(stateKey) !== stateValue) + return true; + } + } + return false; +} + +function tryConvertBannedToValidBlock(structureBlock) { + const blockId = structureBlock.type.id.replace('minecraft:', ''); + if (Object.keys(bannedToValidBlockMap).includes(blockId)) + return BlockPermutation.resolve(bannedToValidBlockMap[blockId], structureBlock.getAllStates()); + return structureBlock; +} + +function tryConvertToDefaultState(structureBlock) { + const newStates = {}; + for (const [stateKey, stateValue] of Object.entries(structureBlock.getAllStates())) { + if (resetToBlockStates[stateKey] !== void 0 && stateValue !== resetToBlockStates[stateKey]) + newStates[stateKey] = resetToBlockStates[stateKey]; + else + newStates[stateKey] = stateValue; + } + return BlockPermutation.resolve(structureBlock.type.id, newStates); +} + function tryPlaceBlockSurvival(event, player, block, structureBlock) { - const itemSlotToUse = fetchMatchingItemSlot(player, 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; placeBlock(block, structureBlock, itemSlotToUse); } } -function fetchMatchingItemSlot(player, structureBlock) { - const itemToMatch = structureBlock.getItemStack(); - if (!itemToMatch) +function getPlaceableItemStack(structureBlock) { + const blockId = structureBlock.type.id.replace('minecraft:', ''); + const newItemId = blockIdToItemStackMap[blockId]; + return newItemId ? new ItemStack(newItemId) : structureBlock.getItemStack(); +} + +function fetchMatchingItemSlot(player, itemToMatchId) { + if (!itemToMatchId) return void 0; const inventory = player.getComponent(EntityComponentTypes.Inventory)?.container; if (!inventory) return void 0; for (let index = 0; index < inventory.size; index++) { const itemSlot = inventory.getSlot(index); - if (itemSlot.hasItem() && itemSlot?.typeId === itemToMatch.typeId) + if (itemSlot.hasItem() && itemSlot?.typeId === itemToMatchId) return itemSlot; } } @@ -67,11 +107,23 @@ function fetchMatchingItemSlot(player, structureBlock) { function placeBlock(block, structureBlock, itemSlot) { system.run(() => { if (itemSlot) { - if (itemSlot.amount === 1) - itemSlot.setItem(void 0); - else - itemSlot.amount--; + consumeItem(itemSlot); } block.setPermutation(structureBlock); }); -} \ No newline at end of file +} + +function consumeItem(itemSlot) { + if (specialItemPlacementConversions[itemSlot.typeId.replace('minecraft:', '')]) { + consumeSpecial(itemSlot); + } else { + if (itemSlot.amount === 1) + itemSlot.setItem(void 0); + else + itemSlot.amount--; + } +} + +function consumeSpecial(itemSlot) { + itemSlot.setItem(new ItemStack(specialItemPlacementConversions[itemSlot.typeId.replace('minecraft:', '')])); +}