diff --git a/src/main/java/io/wispforest/lavender/book/StructureComponent.java b/src/main/java/io/wispforest/lavender/book/StructureComponent.java index 3fe656f..7ed6b41 100644 --- a/src/main/java/io/wispforest/lavender/book/StructureComponent.java +++ b/src/main/java/io/wispforest/lavender/book/StructureComponent.java @@ -58,7 +58,7 @@ public void draw(OwoUIDrawContext context, int mouseX, int mouseY, float partial var entityBuffers = client.getBufferBuilders().getEntityVertexConsumers(); float scale = Math.min(this.width, this.height); - scale /= Math.max(structure.xSize, Math.max(structure.ySize, structure.zSize)); + scale /= Math.max(this.structure.xSize(), Math.max(this.structure.ySize(), this.structure.zSize())); scale /= 1.625f; var matrices = context.getMatrices(); @@ -69,7 +69,7 @@ public void draw(OwoUIDrawContext context, int mouseX, int mouseY, float partial matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(this.displayAngle)); matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(this.rotation)); - matrices.translate(this.structure.xSize / -2f, this.structure.ySize / -2f, this.structure.zSize / -2f); + matrices.translate(this.structure.xSize() / -2.0f, this.structure.ySize() / -2.0f, this.structure.zSize() / -2.0f); RenderSystem.runAsFancy(() -> { structure.forEachPredicate((blockPos, predicate) -> { @@ -95,7 +95,7 @@ public void draw(OwoUIDrawContext context, int mouseX, int mouseY, float partial }); if (this.placeable) { - if (StructureOverlayRenderer.isShowingOverlay(this.structure.id)) { + if (StructureOverlayRenderer.isShowingOverlay(this.structure.id())) { context.drawText(client.textRenderer, Text.translatable("text.lavender.structure_component.active_overlay_hint"), this.x + this.width - 5 - client.textRenderer.getWidth("⚓"), this.y + this.height - 9 - 5, 0, false); this.tooltip(Text.translatable("text.lavender.structure_component.hide_hint")); } else { @@ -109,11 +109,11 @@ public boolean onMouseDown(double mouseX, double mouseY, int button) { var result = super.onMouseDown(mouseX, mouseY, button); if (!this.placeable || button != GLFW.GLFW_MOUSE_BUTTON_LEFT || !Screen.hasShiftDown()) return result; - if (StructureOverlayRenderer.isShowingOverlay(this.structure.id)) { - StructureOverlayRenderer.removeAllOverlays(this.structure.id); + if (StructureOverlayRenderer.isShowingOverlay(this.structure.id())) { + StructureOverlayRenderer.removeAllOverlays(this.structure.id()); } else { - StructureOverlayRenderer.addPendingOverlay(this.structure.id); - StructureOverlayRenderer.restrictVisibleLayer(this.structure.id, this.visibleLayer); + StructureOverlayRenderer.addPendingOverlay(this.structure.id()); + StructureOverlayRenderer.restrictVisibleLayer(this.structure.id(), this.visibleLayer); MinecraftClient.getInstance().setScreen(null); } @@ -138,7 +138,7 @@ public boolean canFocus(FocusSource source) { } public StructureComponent visibleLayer(int visibleLayer) { - StructureOverlayRenderer.restrictVisibleLayer(this.structure.id, visibleLayer); + StructureOverlayRenderer.restrictVisibleLayer(this.structure.id(), visibleLayer); this.visibleLayer = visibleLayer; return this; diff --git a/src/main/java/io/wispforest/lavender/client/StructureOverlayRenderer.java b/src/main/java/io/wispforest/lavender/client/StructureOverlayRenderer.java index 043b195..62c37f1 100644 --- a/src/main/java/io/wispforest/lavender/client/StructureOverlayRenderer.java +++ b/src/main/java/io/wispforest/lavender/client/StructureOverlayRenderer.java @@ -283,8 +283,8 @@ private static Vec3i getPendingOffset(StructureTemplate structure) { return switch (PENDING_OVERLAY.rotation) { case NONE -> new Vec3i(-structure.anchor().getX(), -structure.anchor().getY(), -structure.anchor().getZ()); case CLOCKWISE_90 -> new Vec3i(-structure.anchor().getZ(), -structure.anchor().getY(), -structure.anchor().getX()); - case CLOCKWISE_180 -> new Vec3i(-structure.xSize + structure.anchor.getX() + 1, -structure.anchor().getY(), -structure.zSize + structure.anchor.getZ() + 1); - case COUNTERCLOCKWISE_90 -> new Vec3i(-structure.zSize + structure.anchor.getZ() + 1, -structure.anchor().getY(), -structure.xSize + structure.anchor.getX() + 1); + case CLOCKWISE_180 -> new Vec3i(-structure.xSize() + structure.anchor().getX() + 1, -structure.anchor().getY(), -structure.zSize() + structure.anchor().getZ() + 1); + case COUNTERCLOCKWISE_90 -> new Vec3i(-structure.zSize() + structure.anchor().getZ() + 1, -structure.anchor().getY(), -structure.xSize() + structure.anchor().getX() + 1); }; // @formatter:on } diff --git a/src/main/java/io/wispforest/lavender/md/features/StructureFeature.java b/src/main/java/io/wispforest/lavender/md/features/StructureFeature.java index ce01482..b2a9e06 100644 --- a/src/main/java/io/wispforest/lavender/md/features/StructureFeature.java +++ b/src/main/java/io/wispforest/lavender/md/features/StructureFeature.java @@ -108,15 +108,15 @@ public StructureNode(StructureTemplate structure, int angle, boolean placeable) protected void visitStart(MarkdownCompiler compiler) { var structureComponent = StructureFeature.this.bookComponentSource.builtinTemplate( ParentComponent.class, - this.structure.ySize > 1 ? "structure-preview-with-layers" : "structure-preview", - Map.of("structure", this.structure.id.toString(), "angle", String.valueOf(this.angle)) + this.structure.ySize() > 1 ? "structure-preview-with-layers" : "structure-preview", + Map.of("structure", this.structure.id().toString(), "angle", String.valueOf(this.angle)) ); var structurePreview = structureComponent.childById(StructureComponent.class, "structure").placeable(this.placeable); var layerSlider = structureComponent.childById(SlimSliderComponent.class, "layer-slider"); if (layerSlider != null) { - layerSlider.max(0).min(this.structure.ySize).tooltipSupplier(layer -> { + layerSlider.max(0).min(this.structure.ySize()).tooltipSupplier(layer -> { return layer > 0 ? Text.translatable("text.lavender.structure_component.layer_tooltip", layer.intValue()) : Text.translatable("text.lavender.structure_component.all_layers_tooltip"); @@ -124,7 +124,7 @@ protected void visitStart(MarkdownCompiler compiler) { structurePreview.visibleLayer((int) layer - 1); }); - layerSlider.value(StructureOverlayRenderer.getLayerRestriction(this.structure.id) + 1); + layerSlider.value(StructureOverlayRenderer.getLayerRestriction(this.structure.id()) + 1); } ((OwoUICompiler) compiler).visitComponent(structureComponent); diff --git a/src/main/java/io/wispforest/lavender/structure/BlockStatePredicate.java b/src/main/java/io/wispforest/lavender/structure/BlockStatePredicate.java index c31ef0b..60cb18c 100644 --- a/src/main/java/io/wispforest/lavender/structure/BlockStatePredicate.java +++ b/src/main/java/io/wispforest/lavender/structure/BlockStatePredicate.java @@ -17,9 +17,11 @@ public interface BlockStatePredicate { * a full state match */ BlockStatePredicate NULL_PREDICATE = new BlockStatePredicate() { + private static final BlockState[] PREVIEW_STATES = {Blocks.AIR.getDefaultState()}; + @Override - public BlockState preview() { - return Blocks.AIR.getDefaultState(); + public BlockState[] previewBlockstates() { + return PREVIEW_STATES; } @Override @@ -38,9 +40,11 @@ public boolean isOf(MatchCategory type) { * match on any air block */ BlockStatePredicate AIR_PREDICATE = new BlockStatePredicate() { + private static final BlockState[] PREVIEW_STATES = {Blocks.AIR.getDefaultState()}; + @Override - public BlockState preview() { - return Blocks.AIR.getDefaultState(); + public BlockState[] previewBlockstates() { + return PREVIEW_STATES; } @Override @@ -69,7 +73,15 @@ default boolean matches(BlockState state) { * is called every frame the preview is rendered, returning a different sample * depending on system time (e.g. to cycle to a block tag) is valid behavior */ - BlockState preview(); + default BlockState preview() { + BlockState[] states = this.previewBlockstates(); + return states[(int) (System.currentTimeMillis() / 1000 % states.length)]; + } + + /** + * @return An array of all possible preview block states. + */ + BlockState[] previewBlockstates(); /** * @return Whether this predicate falls into the given matching category, generally diff --git a/src/main/java/io/wispforest/lavender/structure/StructureTemplate.java b/src/main/java/io/wispforest/lavender/structure/StructureTemplate.java index 44d4412..8bfdd1c 100644 --- a/src/main/java/io/wispforest/lavender/structure/StructureTemplate.java +++ b/src/main/java/io/wispforest/lavender/structure/StructureTemplate.java @@ -1,10 +1,12 @@ package io.wispforest.lavender.structure; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.mojang.brigadier.exceptions.CommandSyntaxException; import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; +import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.block.entity.BlockEntity; @@ -13,6 +15,8 @@ import net.minecraft.fluid.FluidState; import net.minecraft.fluid.Fluids; import net.minecraft.registry.Registries; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.tag.TagKey; import net.minecraft.state.property.Property; import net.minecraft.util.BlockRotation; import net.minecraft.util.Identifier; @@ -25,18 +29,29 @@ import net.minecraft.world.biome.ColorResolver; import net.minecraft.world.chunk.light.LightingProvider; import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Optional; import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.StreamSupport; -public class StructureTemplate { +public class StructureTemplate implements Iterable> { + + private static final char AIR_BLOCKSTATE_KEY = '_'; + private static final char NULL_BLOCKSTATE_KEY = ' '; + private static final char ANCHOR_BLOCKSTATE_KEY = '#'; private final BlockStatePredicate[][][] predicates; private final EnumMap predicateCountByType; - public final int xSize, ySize, zSize; public final Vec3i anchor; public final Identifier id; @@ -69,6 +84,30 @@ public int predicatesOfType(BlockStatePredicate.MatchCategory type) { return this.predicateCountByType.get(type).intValue(); } + public Identifier id() { + return this.id; + } + + public BlockStatePredicate[][][] predicates() { + return this.predicates; + } + + public EnumMap predicateCountByType() { + return this.predicateCountByType; + } + + public int xSize() { + return this.xSize; + } + + public int ySize() { + return this.ySize; + } + + public int zSize() { + return this.zSize; + } + /** * @return The anchor position of this template, * to be used when placing in the world @@ -107,6 +146,25 @@ public void forEachPredicate(BiConsumer action, B } } + @Override + public Iterator> iterator() { + return iterator(BlockRotation.NONE); + } + + @Override + public void forEach(Consumer> action) { + var mutablePair = new MutablePair(); + forEachPredicate((pos, predicate) -> { + mutablePair.setLeft(pos); + mutablePair.setRight(predicate); + action.accept(mutablePair); + }); + } + + public Iterator> iterator(BlockRotation rotation) { + return new StructureTemplateIterator(this, rotation); + } + // --- validation --- /** @@ -135,7 +193,7 @@ public int countValidStates(World world, BlockPos anchor) { /** * Shorthand of {@link #countValidStates(World, BlockPos, BlockRotation, BlockStatePredicate.MatchCategory)} - * which uses {@link io.wispforest.lavender.structure.BlockStatePredicate.MatchCategory#NON_NULL} + * which uses {@link BlockStatePredicate.MatchCategory#NON_NULL} */ public int countValidStates(World world, BlockPos anchor, BlockRotation rotation) { return countValidStates(world, anchor, rotation, BlockStatePredicate.MatchCategory.NON_NULL); @@ -163,51 +221,7 @@ public int countValidStates(World world, BlockPos anchor, BlockRotation rotation // --- utility --- public BlockRenderView asBlockRenderView() { - var world = MinecraftClient.getInstance().world; - return new BlockRenderView() { - @Override - public float getBrightness(Direction direction, boolean shaded) { - return 1f; - } - - @Override - public LightingProvider getLightingProvider() { - return world.getLightingProvider(); - } - - @Override - public int getColor(BlockPos pos, ColorResolver colorResolver) { - return colorResolver.getColor(world.getBiome(pos).value(), pos.getX(), pos.getZ()); - } - - @Nullable - @Override - public BlockEntity getBlockEntity(BlockPos pos) { - return null; - } - - @Override - public BlockState getBlockState(BlockPos pos) { - if (pos.getX() < 0 || pos.getX() >= StructureTemplate.this.xSize || pos.getY() < 0 || pos.getY() >= StructureTemplate.this.ySize || pos.getZ() < 0 || pos.getZ() >= StructureTemplate.this.zSize) - return Blocks.AIR.getDefaultState(); - return StructureTemplate.this.predicates[pos.getX()][pos.getY()][pos.getZ()].preview(); - } - - @Override - public FluidState getFluidState(BlockPos pos) { - return Fluids.EMPTY.getDefaultState(); - } - - @Override - public int getHeight() { - return world.getHeight(); - } - - @Override - public int getBottomY() { - return world.getBottomY(); - } - }; + return new StructureTemplateRenderView(Objects.requireNonNull(MinecraftClient.getInstance().world), this); } public static BlockRotation inverse(BlockRotation rotation) { @@ -221,100 +235,11 @@ public static BlockRotation inverse(BlockRotation rotation) { // --- parsing --- - @SuppressWarnings({"rawtypes", "unchecked"}) public static StructureTemplate parse(Identifier resourceId, JsonObject json) { - var keyObject = JsonHelper.getObject(json, "keys"); - var keys = new Char2ObjectOpenHashMap(); Vec3i anchor = null; - for (var entry : keyObject.entrySet()) { - char key; - if (entry.getKey().length() == 1) { - key = entry.getKey().charAt(0); - if (key == '#') { - throw new JsonParseException("Key '#' is reserved for 'anchor' declarations"); - } - - } else if (entry.getKey().equals("anchor")) { - key = '#'; - } else { - continue; - } - - try { - var result = BlockArgumentParser.blockOrTag(Registries.BLOCK.getReadOnlyWrapper(), entry.getValue().getAsString(), false); - if (result.left().isPresent()) { - var predicate = result.left().get(); - - keys.put(key, new BlockStatePredicate() { - @Override - public BlockState preview() { - return predicate.blockState(); - } - - @Override - public Result test(BlockState state) { - if (state.getBlock() != predicate.blockState().getBlock()) return Result.NO_MATCH; - - for (var propAndValue : predicate.properties().entrySet()) { - if (!state.get(propAndValue.getKey()).equals(propAndValue.getValue())) { - return Result.BLOCK_MATCH; - } - } - - return Result.STATE_MATCH; - } - }); - } else { - var predicate = result.right().get(); - - var previewStates = new ArrayList(); - predicate.tag().forEach(registryEntry -> { - var block = registryEntry.value(); - var state = block.getDefaultState(); - - for (var propAndValue : predicate.vagueProperties().entrySet()) { - Property prop = block.getStateManager().getProperty(propAndValue.getKey()); - if (prop == null) return; - - Optional value = prop.parse(propAndValue.getValue()); - if (value.isEmpty()) return; - - state = state.with(prop, value.get()); - } - - previewStates.add(state); - }); - - keys.put(key, new BlockStatePredicate() { - @Override - public BlockState preview() { - if (previewStates.isEmpty()) return Blocks.AIR.getDefaultState(); - return previewStates.get((int) (System.currentTimeMillis() / 1000 % previewStates.size())); - } - - @Override - public Result test(BlockState state) { - if (!state.isIn(predicate.tag())) return Result.NO_MATCH; - - for (var propAndValue : predicate.vagueProperties().entrySet()) { - var prop = state.getBlock().getStateManager().getProperty(propAndValue.getKey()); - if (prop == null) return Result.BLOCK_MATCH; - - var expected = prop.parse(propAndValue.getValue()); - if (expected.isEmpty()) return Result.BLOCK_MATCH; - - if (!state.get(prop).equals(expected.get())) return Result.BLOCK_MATCH; - } - - return Result.STATE_MATCH; - } - }); - } - } catch (CommandSyntaxException e) { - throw new JsonParseException("Failed to parse block state predicate", e); - } - } + var keyObject = JsonHelper.getObject(json, "keys"); + var keys = StructureTemplate.buildStructureKeysMap(keyObject); var layersArray = JsonHelper.getArray(json, "layers"); int xSize = 0, ySize = layersArray.size(), zSize = 0; @@ -361,16 +286,16 @@ public Result test(BlockState state) { if (keys.containsKey(key)) { predicate = keys.get(key); - if (key == '#') { + if (key == ANCHOR_BLOCKSTATE_KEY) { if (anchor != null) { throw new JsonParseException("Anchor key '#' cannot be used twice within the same structure"); + } else { + anchor = new Vec3i(x, y, z); } - - anchor = new Vec3i(x, y, z); } - } else if (key == ' ') { + } else if (key == NULL_BLOCKSTATE_KEY) { predicate = BlockStatePredicate.NULL_PREDICATE; - } else if (key == '_') { + } else if (key == AIR_BLOCKSTATE_KEY) { predicate = BlockStatePredicate.AIR_PREDICATE; } else { throw new JsonParseException("Unknown key '" + key + "'"); @@ -383,4 +308,273 @@ public Result test(BlockState state) { return new StructureTemplate(resourceId, result, xSize, ySize, zSize, anchor); } + + private static Char2ObjectOpenHashMap buildStructureKeysMap(JsonObject keyObject) { + var keys = new Char2ObjectOpenHashMap(); + for (var entry : keyObject.entrySet()) { + char key = blockstateKeyForEntry(entry); + + if (keys.containsKey(key)) { + throw new JsonParseException("Keys can only appear once. Key '%s' appears twice.".formatted(key)); + } + + if (entry.getValue().isJsonArray()) { + JsonArray blockStringsArray = entry.getValue().getAsJsonArray(); + var blockStatePredicates = StreamSupport.stream(blockStringsArray.spliterator(), false) + .map(blockString -> StructureTemplate.parseStringToBlockStatePredicate(blockString.getAsString())) + .toArray(BlockStatePredicate[]::new); + keys.put(key, new OrBlockStatePredicate(blockStatePredicates)); + } else if (entry.getValue().isJsonPrimitive()) { + keys.put(key, StructureTemplate.parseStringToBlockStatePredicate(entry.getValue().getAsString())); + } else { + throw new JsonParseException("The values for the map of key-to-blocks must either be a string or an array of strings."); + } + } + + return keys; + } + + private static char blockstateKeyForEntry(final Map.Entry entry) { + char key; + if (entry.getKey().length() == 1) { + key = entry.getKey().charAt(0); + if (key == ANCHOR_BLOCKSTATE_KEY) { + throw new JsonParseException("Key '#' is reserved for 'anchor' declarations. Rename the key to 'anchor' and use '#' in the structure definition."); + } else if (key == AIR_BLOCKSTATE_KEY) { + throw new JsonParseException("Key '_' is a reserved key for marking a block that must be AIR."); + } else if (key == NULL_BLOCKSTATE_KEY) { + throw new JsonParseException("Key ' ' is a reserved key for marking a block that can be anything."); + } + } else if ("anchor".equals(entry.getKey())) { + key = ANCHOR_BLOCKSTATE_KEY; + } else { + throw new JsonParseException("Keys should only be a single character or should be 'anchor'."); + } + return key; + } + + private static BlockStatePredicate parseStringToBlockStatePredicate(String blockOrTag) { + try { + var result = BlockArgumentParser.blockOrTag(Registries.BLOCK.getReadOnlyWrapper(), blockOrTag, false); + return result.map( + blockResult -> new SingleBlockStatePredicate(blockResult.blockState(), blockResult.properties()), + tagResult -> new TagBlockStatePredicate((RegistryEntryList.Named) tagResult.tag(), tagResult.vagueProperties()) + ); + } catch (CommandSyntaxException e) { + throw new JsonParseException("Failed to parse block state predicate", e); + } + } + + public static class OrBlockStatePredicate implements BlockStatePredicate { + private final BlockStatePredicate[] predicates; + private final BlockState[] previewStates; + + public OrBlockStatePredicate(BlockStatePredicate[] predicates) { + this.predicates = predicates; + this.previewStates = Arrays.stream(predicates) + .flatMap((predicate) -> Arrays.stream(predicate.previewBlockstates())) + .toArray(BlockState[]::new); + } + + @Override + public BlockState[] previewBlockstates() { + return this.previewStates; + } + + @Override + public Result test(BlockState state) { + boolean hasBlockMatch = false; + for (var predicate : this.predicates) { + var result = predicate.test(state); + if (result == Result.STATE_MATCH) + return Result.STATE_MATCH; + else if (result == Result.BLOCK_MATCH) + hasBlockMatch = true; + } + + return hasBlockMatch ? Result.BLOCK_MATCH : Result.NO_MATCH; + } + } + + public static class SingleBlockStatePredicate implements BlockStatePredicate { + private final BlockState state; + private final BlockState[] states; + private final Map, Comparable> properties; + + public SingleBlockStatePredicate(BlockState state, Map, Comparable> properties) { + this.state = state; + this.states = new BlockState[]{state}; + this.properties = properties; + } + + @Override + public BlockState[] previewBlockstates() { + return this.states; + } + + @Override + public Result test(BlockState state) { + if (state.getBlock() != this.state.getBlock()) return Result.NO_MATCH; + + for (var propAndValue : this.properties.entrySet()) { + if (!state.get(propAndValue.getKey()).equals(propAndValue.getValue())) { + return Result.BLOCK_MATCH; + } + } + + return Result.STATE_MATCH; + } + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static class TagBlockStatePredicate implements BlockStatePredicate { + private final TagKey tag; + private final Map vagueProperties; + private final BlockState[] previewStates; + + public TagBlockStatePredicate(RegistryEntryList.Named tagEntries, Map properties) { + this.vagueProperties = properties; + this.tag = tagEntries.getTag(); + this.previewStates = tagEntries.stream().map(entry -> { + var block = entry.value(); + var state = block.getDefaultState(); + + for (var propAndValue : this.vagueProperties.entrySet()) { + Property prop = block.getStateManager().getProperty(propAndValue.getKey()); + if (prop == null) continue; + + Optional value = prop.parse(propAndValue.getValue()); + if (value.isEmpty()) continue; + + state = state.with(prop, value.get()); + } + + return state; + }).toArray(BlockState[]::new); + } + + @Override + public BlockState[] previewBlockstates() { + return this.previewStates; + } + + @Override + public Result test(BlockState state) { + if (!state.isIn(this.tag)) + return Result.NO_MATCH; + + for (var propAndValue : this.vagueProperties.entrySet()) { + var prop = state.getBlock().getStateManager().getProperty(propAndValue.getKey()); + if (prop == null) + return Result.BLOCK_MATCH; + + var expected = prop.parse(propAndValue.getValue()); + if (expected.isEmpty()) + return Result.BLOCK_MATCH; + + if (!state.get(prop).equals(expected.get())) + return Result.BLOCK_MATCH; + } + + return Result.STATE_MATCH; + } + } + + private record StructureTemplateRenderView(World world, StructureTemplate template) implements BlockRenderView { + @Override + public float getBrightness(Direction direction, boolean shaded) { + return 1.0f; + } + + @Override + public LightingProvider getLightingProvider() { + return this.world.getLightingProvider(); + } + + @Override + public int getColor(BlockPos pos, ColorResolver colorResolver) { + return colorResolver.getColor(this.world.getBiome(pos).value(), pos.getX(), pos.getZ()); + } + + @Override + public BlockEntity getBlockEntity(BlockPos pos) { + return null; + } + + @Override + public BlockState getBlockState(BlockPos pos) { + if (pos.getX() < 0 || pos.getX() >= this.template.xSize || + pos.getY() < 0 || pos.getY() >= this.template.ySize || + pos.getZ() < 0 || pos.getZ() >= this.template.zSize) + return Blocks.AIR.getDefaultState(); + return this.template.predicates()[pos.getX()][pos.getY()][pos.getZ()].preview(); + } + + @Override + public FluidState getFluidState(BlockPos pos) { + return Fluids.EMPTY.getDefaultState(); + } + + @Override + public int getHeight() { + return this.world.getHeight(); + } + + @Override + public int getBottomY() { + return this.world.getBottomY(); + } + } + + private static final class StructureTemplateIterator implements Iterator> { + + private final StructureTemplate template; + + private final BlockPos.Mutable currentPos = new BlockPos.Mutable(); + + private final MutablePair currentElement = new MutablePair<>(); + + private final BlockRotation rotation; + + private int posX = 0, posY = 0, posZ = 0; + + private StructureTemplateIterator(StructureTemplate template, BlockRotation rotation) { + this.template = template; + this.rotation = rotation; + } + + @Override + public boolean hasNext() { + return this.posX < this.template.xSize() - 1 && this.posY < this.template.ySize() - 1 && this.posZ < this.template.zSize() - 1; + } + + @Override + public Pair next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + switch (this.rotation) { + case CLOCKWISE_90 -> this.currentPos.set(this.template.zSize() - this.posZ - 1, this.posY, this.posX); + case COUNTERCLOCKWISE_90 -> this.currentPos.set(this.posZ, this.posY, this.template.xSize() - this.posX - 1); + case CLOCKWISE_180 -> + this.currentPos.set(this.template.xSize() - this.posX - 1, this.posY, this.template.zSize() - this.posZ - 1); + default -> this.currentPos.set(this.posX, this.posY, this.posZ); + } + + this.currentElement.setRight(this.template.predicates()[this.posX][this.posY][this.posZ]); + this.currentElement.setLeft(this.currentPos); + + // Advance to next position + if (++this.posZ >= this.template.zSize()) { + this.posZ = 0; + if (++this.posY >= this.template.ySize()) { + this.posY = 0; + ++this.posX; + } + } + + return this.currentElement; + } + } }