From 0b4392e4c3607f04758ea91f5106e0c108134859 Mon Sep 17 00:00:00 2001 From: vrejhead <113960896+vrejhead@users.noreply.github.com> Date: Sat, 13 Jul 2024 14:42:14 -0700 Subject: [PATCH 01/64] scuffed commit, more red than lines changed --- .../gregtech/api/pattern/BlockPattern.java | 182 +++++++++++++----- .../gregtech/api/pattern/BlockWorldState.java | 17 -- .../api/pattern/FactoryBlockPattern.java | 159 ++++++++++----- .../gregtech/api/pattern/GreggyBlockPos.java | 97 ++++++++++ .../gregtech/api/pattern/PatternAisle.java | 49 +++++ .../api/pattern/TraceabilityPredicate.java | 20 +- 6 files changed, 396 insertions(+), 128 deletions(-) create mode 100644 src/main/java/gregtech/api/pattern/GreggyBlockPos.java create mode 100644 src/main/java/gregtech/api/pattern/PatternAisle.java diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java index 8479368a3db..15b648a41d0 100644 --- a/src/main/java/gregtech/api/pattern/BlockPattern.java +++ b/src/main/java/gregtech/api/pattern/BlockPattern.java @@ -9,6 +9,8 @@ import gregtech.api.util.BlockInfo; import gregtech.api.util.RelativeDirection; +import it.unimi.dsi.fastutil.chars.Char2ObjectMap; + import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; @@ -24,81 +26,66 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import org.apache.commons.lang3.ArrayUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.lang.reflect.Array; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; public class BlockPattern { static EnumFacing[] FACINGS = { EnumFacing.SOUTH, EnumFacing.NORTH, EnumFacing.WEST, EnumFacing.EAST, EnumFacing.UP, EnumFacing.DOWN }; - public final int[][] aisleRepetitions; public final RelativeDirection[] structureDir; - protected final TraceabilityPredicate[][][] blockMatches; // [z][y][x] - protected final int fingerLength; // z size - protected final int thumbLength; // y size - protected final int palmLength; // x size + protected final int[] dimensions; + protected final int[] startOffset; + protected final PatternAisle[] aisles; + protected final Char2ObjectMap predicates; protected final BlockWorldState worldState = new BlockWorldState(); protected final PatternMatchContext matchContext = new PatternMatchContext(); protected final Map globalCount; protected final Map layerCount; public Long2ObjectMap cache = new Long2ObjectOpenHashMap<>(); - // x, y, z, minZ, maxZ - private int[] centerOffset = null; /** * The repetitions per aisle along the axis of repetition */ public int[] formedRepetitionCount; - public BlockPattern(@NotNull TraceabilityPredicate[][][] predicatesIn, @NotNull RelativeDirection[] structureDir, - @NotNull int[][] aisleRepetitions) { - this.blockMatches = predicatesIn; - this.globalCount = new HashMap<>(); - this.layerCount = new HashMap<>(); - this.fingerLength = predicatesIn.length; - this.structureDir = structureDir; - this.aisleRepetitions = aisleRepetitions; - this.formedRepetitionCount = new int[aisleRepetitions.length]; - - if (this.fingerLength > 0) { - this.thumbLength = predicatesIn[0].length; + // how many not nulls to keep someone from not passing in null? + public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] dimensions, @NotNull RelativeDirection @NotNull [] directions, + int @Nullable [] startOffset, @NotNull Char2ObjectMap predicates, char centerChar) { + this.aisles = aisles; + this.dimensions = dimensions; + this.structureDir = directions; + this.predicates = predicates; + this.startOffset = startOffset; - if (this.thumbLength > 0) { - this.palmLength = predicatesIn[0][0].length; - } else { - this.palmLength = 0; - } - } else { - this.thumbLength = 0; - this.palmLength = 0; - } - - initializeCenterOffsets(); + if (this.startOffset == null) legacyStartOffset(centerChar); } - private void initializeCenterOffsets() { - loop: - for (int x = 0; x < this.palmLength; x++) { - for (int y = 0; y < this.thumbLength; y++) { - for (int z = 0, minZ = 0, maxZ = 0; z < - this.fingerLength; minZ += aisleRepetitions[z][0], maxZ += aisleRepetitions[z][1], z++) { - TraceabilityPredicate predicate = this.blockMatches[z][y][x]; - if (predicate.isCenter) { - centerOffset = new int[] { x, y, z, minZ, maxZ }; - break loop; - } - } + /** + * For legacy compat only, + * @param center The center char to look for + */ + private void legacyStartOffset(char center) { + // could also use aisles.length but this is cooler + for (int aisleI = 0; aisleI < dimensions[2]; aisleI++) { + int[] result = aisles[aisleI].firstInstanceOf(center); + if (result != null) { + startOffset[0] = aisleI; + startOffset[1] = result[0]; + startOffset[2] = result[2]; + return; } } - if (centerOffset == null) { - throw new IllegalArgumentException("Didn't find center predicate"); - } + + throw new IllegalArgumentException("Didn't find center predicate"); } public PatternError getError() { @@ -165,8 +152,7 @@ private PatternMatchContext checkPatternAt(World world, BlockPos centerPos, Enum for (int b = 0, y = -centerOffset[1]; b < this.thumbLength; b++, y++) { for (int a = 0, x = -centerOffset[0]; a < this.palmLength; a++, x++) { TraceabilityPredicate predicate = this.blockMatches[c][b][a]; - BlockPos pos = RelativeDirection.setActualRelativeOffset(x, y, z, frontFacing, upwardsFacing, - isFlipped, structureDir) + BlockPos pos = setActualRelativeOffset(x, y, z, frontFacing, upwardsFacing, isFlipped) .add(centerPos.getX(), centerPos.getY(), centerPos.getZ()); worldState.update(world, pos, matchContext, globalCount, layerCount, predicate); TileEntity tileEntity = worldState.getTileEntity(); @@ -251,10 +237,9 @@ public void autoBuild(EntityPlayer player, MultiblockControllerBase controllerBa for (int b = 0, y = -centerOffset[1]; b < this.thumbLength; b++, y++) { for (int a = 0, x = -centerOffset[0]; a < this.palmLength; a++, x++) { TraceabilityPredicate predicate = this.blockMatches[c][b][a]; - BlockPos pos = RelativeDirection.setActualRelativeOffset(x, y, z, facing, - controllerBase.getUpwardsFacing(), - controllerBase.isFlipped(), structureDir) - .add(centerPos.getX(), centerPos.getY(), centerPos.getZ()); + BlockPos pos = setActualRelativeOffset(x, y, z, facing, controllerBase.getUpwardsFacing(), + controllerBase.isFlipped()) + .add(centerPos.getX(), centerPos.getY(), centerPos.getZ()); worldState.update(world, pos, matchContext, globalCount, layerCount, predicate); if (!world.getBlockState(pos).getMaterial().isReplaceable()) { blocks.put(pos, world.getBlockState(pos)); @@ -573,8 +558,7 @@ public BlockInfo[][][] getPreview(int[] repetition) { } } BlockInfo info = infos == null || infos.length == 0 ? BlockInfo.EMPTY : infos[0]; - BlockPos pos = RelativeDirection.setActualRelativeOffset(z, y, x, EnumFacing.NORTH, - EnumFacing.UP, false, structureDir); + BlockPos pos = setActualRelativeOffset(z, y, x, EnumFacing.NORTH, EnumFacing.UP, false); // TODO if (info.getTileEntity() instanceof MetaTileEntityHolder) { MetaTileEntityHolder holder = new MetaTileEntityHolder(); @@ -627,4 +611,96 @@ public BlockInfo[][][] getPreview(int[] repetition) { }); return result; } + + private BlockPos offsetFrom(BlockPos start, int aisleOffset, int stringOffset, int charOffset, EnumFacing frontFacing, + EnumFacing upFacing, boolean flip) { + GreggyBlockPos pos = new GreggyBlockPos(start); + pos.offset(structureDir[0].getRelativeFacing(frontFacing, upFacing, flip), aisleOffset); + pos.offset(structureDir[1].getRelativeFacing(frontFacing, upFacing, flip), stringOffset); + pos.offset(structureDir[2].getRelativeFacing(frontFacing, upFacing, flip), charOffset); + return pos.immutable(); + } + + private BlockPos setActualRelativeOffset(int x, int y, int z, EnumFacing facing, EnumFacing upwardsFacing, + boolean isFlipped) { + int[] c0 = new int[] { x, y, z }, c1 = new int[3]; + if (facing == EnumFacing.UP || facing == EnumFacing.DOWN) { + EnumFacing of = facing == EnumFacing.DOWN ? upwardsFacing : upwardsFacing.getOpposite(); + for (int i = 0; i < 3; i++) { + switch (structureDir[i].getActualFacing(of)) { + case UP -> c1[1] = c0[i]; + case DOWN -> c1[1] = -c0[i]; + case WEST -> c1[0] = -c0[i]; + case EAST -> c1[0] = c0[i]; + case NORTH -> c1[2] = -c0[i]; + case SOUTH -> c1[2] = c0[i]; + } + } + int xOffset = upwardsFacing.getXOffset(); + int zOffset = upwardsFacing.getZOffset(); + int tmp; + if (xOffset == 0) { + tmp = c1[2]; + c1[2] = zOffset > 0 ? c1[1] : -c1[1]; + c1[1] = zOffset > 0 ? -tmp : tmp; + } else { + tmp = c1[0]; + c1[0] = xOffset > 0 ? c1[1] : -c1[1]; + c1[1] = xOffset > 0 ? -tmp : tmp; + } + if (isFlipped) { + if (upwardsFacing == EnumFacing.NORTH || upwardsFacing == EnumFacing.SOUTH) { + c1[0] = -c1[0]; // flip X-axis + } else { + c1[2] = -c1[2]; // flip Z-axis + } + } + } else { + for (int i = 0; i < 3; i++) { + switch (structureDir[i].getActualFacing(facing)) { + case UP -> c1[1] = c0[i]; + case DOWN -> c1[1] = -c0[i]; + case WEST -> c1[0] = -c0[i]; + case EAST -> c1[0] = c0[i]; + case NORTH -> c1[2] = -c0[i]; + case SOUTH -> c1[2] = c0[i]; + } + } + if (upwardsFacing == EnumFacing.WEST || upwardsFacing == EnumFacing.EAST) { + int xOffset = upwardsFacing == EnumFacing.WEST ? facing.rotateY().getXOffset() : + facing.rotateY().getOpposite().getXOffset(); + int zOffset = upwardsFacing == EnumFacing.WEST ? facing.rotateY().getZOffset() : + facing.rotateY().getOpposite().getZOffset(); + int tmp; + if (xOffset == 0) { + tmp = c1[2]; + c1[2] = zOffset > 0 ? -c1[1] : c1[1]; + c1[1] = zOffset > 0 ? tmp : -tmp; + } else { + tmp = c1[0]; + c1[0] = xOffset > 0 ? -c1[1] : c1[1]; + c1[1] = xOffset > 0 ? tmp : -tmp; + } + } else if (upwardsFacing == EnumFacing.SOUTH) { + c1[1] = -c1[1]; + if (facing.getXOffset() == 0) { + c1[0] = -c1[0]; + } else { + c1[2] = -c1[2]; + } + } + if (isFlipped) { + if (upwardsFacing == EnumFacing.NORTH || upwardsFacing == EnumFacing.SOUTH) { + if (facing == EnumFacing.NORTH || facing == EnumFacing.SOUTH) { + c1[0] = -c1[0]; // flip X-axis + } else { + c1[2] = -c1[2]; // flip Z-axis + } + } else { + c1[1] = -c1[1]; // flip Y-axis + } + } + } + return new BlockPos(c1[0], c1[1], c1[2]); + } } diff --git a/src/main/java/gregtech/api/pattern/BlockWorldState.java b/src/main/java/gregtech/api/pattern/BlockWorldState.java index 74dfa1c4526..48473ba7151 100644 --- a/src/main/java/gregtech/api/pattern/BlockWorldState.java +++ b/src/main/java/gregtech/api/pattern/BlockWorldState.java @@ -19,14 +19,10 @@ public class BlockWorldState { protected TileEntity tileEntity; protected boolean tileEntityInitialized; protected PatternMatchContext matchContext; - protected Map globalCount; - protected Map layerCount; protected TraceabilityPredicate predicate; protected PatternError error; public void update(World worldIn, BlockPos posIn, PatternMatchContext matchContext, - Map globalCount, - Map layerCount, TraceabilityPredicate predicate) { this.world = worldIn; this.pos = posIn; @@ -34,9 +30,6 @@ public void update(World worldIn, BlockPos posIn, PatternMatchContext matchConte this.tileEntity = null; this.tileEntityInitialized = false; this.matchContext = matchContext; - this.globalCount = globalCount; - this.layerCount = layerCount; - this.predicate = predicate; this.error = null; } @@ -77,16 +70,6 @@ public BlockPos getPos() { return this.pos.toImmutable(); } - public IBlockState getOffsetState(EnumFacing face) { - if (pos instanceof MutableBlockPos) { - ((MutableBlockPos) pos).move(face); - IBlockState blockState = world.getBlockState(pos); - ((MutableBlockPos) pos).move(face.getOpposite()); - return blockState; - } - return world.getBlockState(this.pos.offset(face)); - } - public World getWorld() { return world; } diff --git a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java index 7b2e272f991..a8f28978e28 100644 --- a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java +++ b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java @@ -3,8 +3,11 @@ import gregtech.api.util.RelativeDirection; import com.google.common.base.Joiner; +import it.unimi.dsi.fastutil.chars.Char2ObjectMap; +import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import java.lang.reflect.Array; import java.util.ArrayList; @@ -15,16 +18,54 @@ import static gregtech.api.util.RelativeDirection.*; +/** + * A builder class for {@link BlockPattern}
+ * When the multiblock is placed, its facings are concrete. Then, the {@link RelativeDirection}s passed into + * {@link FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)} are ways in which the + * pattern progresses. It can be thought like this, where startPos() is either defined via {@link FactoryBlockPattern#setStartOffset(int...)} + * , or automatically detected(for legacy compat only, you should use {@link FactoryBlockPattern#setStartOffset(int...)} always for new code): + *
+ * {@code
+ * for(int aisleI in 0..aisles):
+ *     for(int stringI in 0..strings):
+ *         for(int charI in 0..chars):
+ *             pos = startPos()
+ *             pos.move(aisleI in aisleDir)
+ *             pos.move(stringI in stringDir)
+ *             pos.move(charI in charDir)
+ *             predicate = aisles[aisleI].stringAt(stringI).charAt(charI)
+ * }
+ * 
+ */ public class FactoryBlockPattern { private static final Joiner COMMA_JOIN = Joiner.on(","); - private final List depth = new ArrayList<>(); - private final List aisleRepetitions = new ArrayList<>(); - private final Map symbolMap = new HashMap<>(); - private int aisleHeight; - private int rowWidth; + + /** + * In the form of [ num char per string, num string per aisle, num aisles ] + */ + private final int[] dimensions = { -1, -1, -1 }; + /** + * In the form of [ aisleOffset, stringOffset, charOffset ] where the offsets are the opposite {@link RelativeDirection} of structure directions + */ + private int[] startOffset; + private char centerChar; + + private final List aisles = new ArrayList<>(); + + /** + * Map going from chars to the predicates + */ + private final Char2ObjectMap symbolMap = new Char2ObjectOpenHashMap<>(); + + /** + * In the form of [ charDir, stringDir, aisleDir ] + */ private final RelativeDirection[] structureDir = new RelativeDirection[3]; + /** + * @see FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection) + */ private FactoryBlockPattern(RelativeDirection charDir, RelativeDirection stringDir, RelativeDirection aisleDir) { structureDir[0] = charDir; structureDir[1] = stringDir; @@ -52,40 +93,43 @@ private FactoryBlockPattern(RelativeDirection charDir, RelativeDirection stringD /** * Adds a repeatable aisle to this pattern. + * @param aisle The aisle to add + * @see FactoryBlockPattern#setRepeatable(int, int) */ - public FactoryBlockPattern aisleRepeatable(int minRepeat, int maxRepeat, String... aisle) { - if (!ArrayUtils.isEmpty(aisle) && !StringUtils.isEmpty(aisle[0])) { - if (this.depth.isEmpty()) { - this.aisleHeight = aisle.length; - this.rowWidth = aisle[0].length(); - } + public FactoryBlockPattern aisleRepeatable(int minRepeat, int maxRepeat, @NotNull String... aisle) { + if (ArrayUtils.isEmpty(aisle) || StringUtils.isEmpty(aisle[0])) + throw new IllegalArgumentException("Empty pattern for aisle"); - if (aisle.length != this.aisleHeight) { - throw new IllegalArgumentException("Expected aisle with height of " + this.aisleHeight + - ", but was given one with a height of " + aisle.length + ")"); - } else { - for (String s : aisle) { - if (s.length() != this.rowWidth) { - throw new IllegalArgumentException( - "Not all rows in the given aisle are the correct width (expected " + this.rowWidth + - ", found one with " + s.length() + ")"); - } + // set the dimensions if the user hasn't already + if (dimensions[0] == -1) { + dimensions[0] = aisle[0].length(); + } + if (dimensions[1] == -1) { + dimensions[1] = aisle.length; + } - for (char c0 : s.toCharArray()) { - if (!this.symbolMap.containsKey(c0)) { - this.symbolMap.put(c0, null); - } - } + if (aisle.length != dimensions[1]) { + throw new IllegalArgumentException("Expected aisle with height of " + dimensions[1] + + ", but was given one with a height of " + aisle.length + ")"); + } else { + for (String s : aisle) { + if (s.length() != dimensions[0]) { + throw new IllegalArgumentException( + "Not all rows in the given aisle are the correct width (expected " + dimensions[0] + + ", found one with " + s.length() + ")"); } - this.depth.add(aisle); - if (minRepeat > maxRepeat) - throw new IllegalArgumentException("Lower bound of repeat counting must smaller than upper bound!"); - aisleRepetitions.add(new int[] { minRepeat, maxRepeat }); - return this; + for (char c : s.toCharArray()) { + if (!this.symbolMap.containsKey(c)) { + this.symbolMap.put(c, null); + } + } } - } else { - throw new IllegalArgumentException("Empty pattern for aisle"); + + aisles.add(new PatternAisle(minRepeat, maxRepeat, aisle)); + if (minRepeat > maxRepeat) + throw new IllegalArgumentException("Lower bound of repeat counting must smaller than upper bound!"); + return this; } } @@ -98,32 +142,58 @@ public FactoryBlockPattern aisle(String... aisle) { /** * Set last aisle repeatable + * @param minRepeat Minimum amount of repeats, inclusive + * @param maxRepeat Maximum amount of repeats, inclusive */ public FactoryBlockPattern setRepeatable(int minRepeat, int maxRepeat) { if (minRepeat > maxRepeat) throw new IllegalArgumentException("Lower bound of repeat counting must smaller than upper bound!"); - aisleRepetitions.set(aisleRepetitions.size() - 1, new int[] { minRepeat, maxRepeat }); + aisles.get(aisles.size() - 1).setRepeats(minRepeat, maxRepeat); return this; } /** * Set last aisle repeatable + * @param repeatCount The amount to repeat */ public FactoryBlockPattern setRepeatable(int repeatCount) { return setRepeatable(repeatCount, repeatCount); } + public FactoryBlockPattern setStartOffset(int aisleOffset, int stringOffset, int charOffset) { + this.startOffset[0] = aisleOffset; + this.startOffset[1] = stringOffset; + this.startOffset[2] = charOffset; + return this; + } + + /** + * Starts the builder, this is equivlent to calling {@link FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)} with RIGHT, UP, BACK + * @see FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection) + */ public static FactoryBlockPattern start() { return new FactoryBlockPattern(RIGHT, UP, BACK); } + /** + * Starts the builder, each pair of {@link RelativeDirection} must be used at exactly once! + * @param charDir The direction chars progress in, each successive char in a string progresses by this direction + * @param stringDir The direction strings progress in, each successive string in an aisle progresses by this direction + * @param aisleDir The direction aisles progress in, each successive {@link FactoryBlockPattern#aisle(String...)} progresses in this direction + */ public static FactoryBlockPattern start(RelativeDirection charDir, RelativeDirection stringDir, RelativeDirection aisleDir) { return new FactoryBlockPattern(charDir, stringDir, aisleDir); } + /** + * Puts a symbol onto the predicate map + * @param symbol The symbol, will override previous identical ones + * @param blockMatcher The predicate to put + */ public FactoryBlockPattern where(char symbol, TraceabilityPredicate blockMatcher) { this.symbolMap.put(symbol, new TraceabilityPredicate(blockMatcher).sort()); + if (blockMatcher.isCenter) centerChar = symbol; return this; } @@ -136,24 +206,9 @@ public FactoryBlockPattern where(String symbol, TraceabilityPredicate blockMatch } public BlockPattern build() { - return new BlockPattern(makePredicateArray(), structureDir, - aisleRepetitions.toArray(new int[aisleRepetitions.size()][])); - } - - private TraceabilityPredicate[][][] makePredicateArray() { - this.checkMissingPredicates(); - TraceabilityPredicate[][][] predicate = (TraceabilityPredicate[][][]) Array - .newInstance(TraceabilityPredicate.class, this.depth.size(), this.aisleHeight, this.rowWidth); - - for (int i = 0; i < this.depth.size(); ++i) { - for (int j = 0; j < this.aisleHeight; ++j) { - for (int k = 0; k < this.rowWidth; ++k) { - predicate[i][j][k] = this.symbolMap.get(this.depth.get(i)[j].charAt(k)); - } - } - } - - return predicate; + checkMissingPredicates(); + this.dimensions[2] = aisles.size(); + return new BlockPattern(aisles.toArray(PatternAisle[]::new), dimensions, structureDir, startOffset, symbolMap, centerChar); } private void checkMissingPredicates() { diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java new file mode 100644 index 00000000000..d1023d561f2 --- /dev/null +++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java @@ -0,0 +1,97 @@ +package gregtech.api.pattern; + +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; + +/** + * A possibly saner(and mutable) alternative to BlockPos, where getters and setters use indices and axis instead of + * separate names to avoid stupid code + */ +public class GreggyBlockPos { + + protected final int[] pos; + + public GreggyBlockPos() { + this(0, 0, 0); + } + + public GreggyBlockPos(int x, int y, int z) { + pos = new int[] { x, y, z }; + } + + public GreggyBlockPos(BlockPos base) { + pos = new int[] { base.getX(), base.getY(), base.getZ() }; + } + + /** + * Sets a coordinate in the given axis + * + * @param axis The axis to set + * @param value The value of said coordinate + */ + public void set(EnumFacing.Axis axis, int value) { + pos[axis.ordinal()] = value; + } + + /** + * Sets all 3 coordinates in the given axis order + * + * @param a1 The first axis, p1 will be set from this + * @param a2 The second axis, p2 will be set from this + * @param p1 Value for a1 + * @param p2 Value for a2 + * @param p3 The axis is inferred from the other 2 axis(all 3 are unique, so if a1 is X and a2 is Z, then p3 is set + * in Y) + */ + public void setAxisRelative(EnumFacing.Axis a1, EnumFacing.Axis a2, int p1, int p2, int p3) { + set(a1, p1); + set(a2, p2); + // 3 cases since order doesn't matter(a3 is the axis we are trying to find): + // a3 = X: Y.ordinal() + Z.ordinal() = 3: a3.ordinal() is 0 + // a3 = Y: X.ordinal() + Z.ordinal() = 2: a3.ordinal() is 1 + // a3 = Z: X.ordinal() + Y.ordinal() = 1: a3.ordinal() is 2 + // in all 3 cases, 3 - a1.ordinal() - a2.ordinal() correctly finds a3 + pos[3 - a1.ordinal() - a2.ordinal()] = p3; + } + + /** + * Offsets in the given {@link EnumFacing} amount times {@link BlockPos#offset(EnumFacing)} + */ + public void offset(EnumFacing facing, int amount) { + pos[0] += facing.getXOffset() * amount; + pos[1] += facing.getYOffset() * amount; + pos[2] += facing.getZOffset() * amount; + } + + /** + * Equivalent to calling {@link GreggyBlockPos#offset(EnumFacing, int)} with amount set to 1 + */ + public void offset(EnumFacing facing) { + offset(facing, 1); + } + + /** + * @return A new immutable instance of {@link BlockPos} + */ + public BlockPos immutable() { + return new BlockPos(pos[0], pos[1], pos[2]); + } + + /** + * Gets a coordinate associated with the index, X = 0, Y = 1, Z = 2 + */ + public int get(int index) { + return pos[index]; + } + + /** + * Gets a coordinate associated with the axis + */ + public int get(EnumFacing.Axis axis) { + return pos[axis.ordinal()]; + } + + public GreggyBlockPos copy() { + return new GreggyBlockPos(pos[0], pos[1], pos[2]); + } +} diff --git a/src/main/java/gregtech/api/pattern/PatternAisle.java b/src/main/java/gregtech/api/pattern/PatternAisle.java new file mode 100644 index 00000000000..9226ee951ca --- /dev/null +++ b/src/main/java/gregtech/api/pattern/PatternAisle.java @@ -0,0 +1,49 @@ +package gregtech.api.pattern; + +public class PatternAisle { + // not final because of setRepeatable and i need to have compat + // actualRepeats stores the information for multiblock checks, while minRepeats and maxRepeats are rules + protected int minRepeats, maxRepeats, actualRepeats; + protected final String[] pattern; + public PatternAisle(int minRepeats, int maxRepeats, String[] pattern) { + this.minRepeats = minRepeats; + this.maxRepeats = maxRepeats; + this.pattern = pattern; + } + + public PatternAisle(int repeats, String[] pattern) { + this.minRepeats = this.maxRepeats = repeats; + this.pattern = pattern; + } + + public void setRepeats(int minRepeats, int maxRepeats) { + this.minRepeats = minRepeats; + this.maxRepeats = maxRepeats; + } + + public void setRepeats(int repeats) { + this.minRepeats = this.maxRepeats = repeats; + } + + public void setActualRepeats(int actualRepeats) { + this.actualRepeats = actualRepeats; + } + + public int getActualRepeats() { + return this.actualRepeats; + } + + /** + * Gets the first instance of the char in the pattern + * @param c The char to find + * @return An int array in the form of [ index into String[], index into String#charAt ], or null if it was not found + */ + public int[] firstInstanceOf(char c) { + for (int strI = 0; strI < pattern.length; strI++) { + for (int chrI = 0; chrI < pattern[0].length(); chrI++) { + if (pattern[strI].charAt(chrI) == c) return new int[] { strI, chrI }; + } + } + return null; + } +} diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java index da9483998be..cdfee3c7195 100644 --- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java +++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java @@ -7,6 +7,8 @@ import gregtech.api.metatileentity.multiblock.MultiblockControllerBase; import gregtech.api.util.BlockInfo; +import it.unimi.dsi.fastutil.objects.Object2IntMap; + import net.minecraft.block.state.IBlockState; import net.minecraft.client.resources.I18n; import net.minecraft.init.Blocks; @@ -24,9 +26,9 @@ public class TraceabilityPredicate { // Allow any block. - public static TraceabilityPredicate ANY = new TraceabilityPredicate((state) -> true); + public static final TraceabilityPredicate ANY = new TraceabilityPredicate((state) -> true); // Allow the air block. - public static TraceabilityPredicate AIR = new TraceabilityPredicate( + public static final TraceabilityPredicate AIR = new TraceabilityPredicate( blockWorldState -> blockWorldState.getBlockState().getBlock().isAir(blockWorldState.getBlockState(), blockWorldState.getWorld(), blockWorldState.getPos())); // Allow all heating coils, and require them to have the same type. @@ -304,7 +306,7 @@ public boolean testLimited(BlockWorldState blockWorldState) { return testGlobal(blockWorldState) && testLayer(blockWorldState); } - public boolean testGlobal(BlockWorldState blockWorldState) { + public SinglePredicateError testGlobal(BlockWorldState blockWorldState) { if (minGlobalCount == -1 && maxGlobalCount == -1) return true; Integer count = blockWorldState.globalCount.get(this); boolean base = predicate.test(blockWorldState); @@ -315,9 +317,15 @@ public boolean testGlobal(BlockWorldState blockWorldState) { return false; } - public boolean testLayer(BlockWorldState blockWorldState) { - if (minLayerCount == -1 && maxLayerCount == -1) return true; - Integer count = blockWorldState.layerCount.get(this); + /** + * + * @param blockWorldState + * @return + */ + public SinglePredicateError testLayer(Object2IntMap cache) { + if (minLayerCount == -1 && maxLayerCount == -1) return null; + + int count = cache.get(this); boolean base = predicate.test(blockWorldState); count = (count == null ? 0 : count) + (base ? 1 : 0); blockWorldState.layerCount.put(this, count); From 5de619be01ee82f03bee191b95dee1a5ce5cd571 Mon Sep 17 00:00:00 2001 From: vrejhead <113960896+vrejhead@users.noreply.github.com> Date: Sun, 14 Jul 2024 13:47:00 -0700 Subject: [PATCH 02/64] IT COMPILES!!!! debugging time --- .../multiblock/MultiblockControllerBase.java | 17 +- .../gregtech/api/pattern/BlockPattern.java | 979 +++++++++--------- .../gregtech/api/pattern/BlockWorldState.java | 67 +- .../api/pattern/FactoryBlockPattern.java | 23 +- .../gregtech/api/pattern/GreggyBlockPos.java | 30 +- .../gregtech/api/pattern/PatternAisle.java | 10 + .../gregtech/api/pattern/PatternError.java | 30 +- .../api/pattern/PatternStringError.java | 4 + .../gregtech/api/pattern/StructureInfo.java | 22 + .../api/pattern/TraceabilityPredicate.java | 117 ++- .../MultiblockInfoRecipeWrapper.java | 15 +- 11 files changed, 718 insertions(+), 596 deletions(-) create mode 100644 src/main/java/gregtech/api/pattern/StructureInfo.java diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java index ff3848b1557..fb097c8d9b0 100644 --- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java +++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java @@ -551,14 +551,15 @@ public boolean allowsFlip() { } public List getMatchingShapes() { - if (this.structurePattern == null) { - this.reinitializeStructurePattern(); - if (this.structurePattern == null) { - return Collections.emptyList(); - } - } - int[][] aisleRepetitions = this.structurePattern.aisleRepetitions; - return repetitionDFS(new ArrayList<>(), aisleRepetitions, new Stack<>()); + return Collections.emptyList(); +// if (this.structurePattern == null) { +// this.reinitializeStructurePattern(); +// if (this.structurePattern == null) { +// return Collections.emptyList(); +// } +// } +// int[][] aisleRepetitions = this.structurePattern.aisleRepetitions; +// return repetitionDFS(new ArrayList<>(), aisleRepetitions, new Stack<>()); } private List repetitionDFS(List pages, int[][] aisleRepetitions, diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java index 15b648a41d0..7583eb8fee4 100644 --- a/src/main/java/gregtech/api/pattern/BlockPattern.java +++ b/src/main/java/gregtech/api/pattern/BlockPattern.java @@ -11,6 +11,10 @@ import it.unimi.dsi.fastutil.chars.Char2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; + +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; + import net.minecraft.block.state.IBlockState; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; @@ -40,15 +44,28 @@ public class BlockPattern { static EnumFacing[] FACINGS = { EnumFacing.SOUTH, EnumFacing.NORTH, EnumFacing.WEST, EnumFacing.EAST, EnumFacing.UP, EnumFacing.DOWN }; + + /** + * In the form of [ charDir, stringDir, aisleDir ] + */ public final RelativeDirection[] structureDir; + + /** + * In the form of [ num aisles, num string per aisle, num char per string ] + */ protected final int[] dimensions; + + /** + * In the form of [ aisleOffset, stringOffset, charOffset ] where the offsets are the opposite {@link RelativeDirection} of structure directions + */ protected final int[] startOffset; protected final PatternAisle[] aisles; protected final Char2ObjectMap predicates; - protected final BlockWorldState worldState = new BlockWorldState(); + protected final StructureInfo info; + protected final BlockWorldState worldState; protected final PatternMatchContext matchContext = new PatternMatchContext(); - protected final Map globalCount; - protected final Map layerCount; + protected final Object2IntMap globalCount = new Object2IntOpenHashMap<>(); + protected final Object2IntMap layerCount = new Object2IntOpenHashMap<>(); public Long2ObjectMap cache = new Long2ObjectOpenHashMap<>(); @@ -67,6 +84,9 @@ public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] di this.startOffset = startOffset; if (this.startOffset == null) legacyStartOffset(centerChar); + + this.info = new StructureInfo(new PatternMatchContext(), null); + this.worldState = new BlockWorldState(info); } /** @@ -89,7 +109,7 @@ private void legacyStartOffset(char center) { } public PatternError getError() { - return worldState.error; + return info.getError(); } public PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, EnumFacing frontFacing, @@ -116,509 +136,518 @@ public PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, E } // First try normal pattern, and if it fails, try flipped (if allowed). - PatternMatchContext pmc = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, false); + boolean valid = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, false); + if (valid) return matchContext; + if (allowsFlip) { - if (pmc != null) { - return pmc; - } - pmc = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, true); + valid = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, true); } - if (pmc == null) clearCache(); // we don't want a random cache of a partially formed multi - return pmc; + if (!valid) clearCache(); // we don't want a random cache of a partially formed multi + return null; } public void clearCache() { cache.clear(); } - private PatternMatchContext checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing, + private boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped) { - boolean findFirstAisle = false; - int minZ = -centerOffset[4]; - this.matchContext.reset(); this.globalCount.clear(); this.layerCount.clear(); cache.clear(); - // Checking aisles - for (int c = 0, z = minZ++, r; c < this.fingerLength; c++) { - // Checking repeatable slices - int validRepetitions = 0; - loop: - for (r = 0; (findFirstAisle ? r < aisleRepetitions[c][1] : z <= -centerOffset[3]); r++) { - // Checking single slice - this.layerCount.clear(); - - for (int b = 0, y = -centerOffset[1]; b < this.thumbLength; b++, y++) { - for (int a = 0, x = -centerOffset[0]; a < this.palmLength; a++, x++) { - TraceabilityPredicate predicate = this.blockMatches[c][b][a]; - BlockPos pos = setActualRelativeOffset(x, y, z, frontFacing, upwardsFacing, isFlipped) - .add(centerPos.getX(), centerPos.getY(), centerPos.getZ()); - worldState.update(world, pos, matchContext, globalCount, layerCount, predicate); - TileEntity tileEntity = worldState.getTileEntity(); - if (predicate != TraceabilityPredicate.ANY) { - if (tileEntity instanceof IGregTechTileEntity) { - if (((IGregTechTileEntity) tileEntity).isValid()) { - cache.put(pos.toLong(), - new BlockInfo(worldState.getBlockState(), tileEntity, predicate)); - } else { - cache.put(pos.toLong(), new BlockInfo(worldState.getBlockState(), null, predicate)); - } - } else { - cache.put(pos.toLong(), - new BlockInfo(worldState.getBlockState(), tileEntity, predicate)); - } - } - if (!predicate.test(worldState)) { - if (findFirstAisle) { - if (r < aisleRepetitions[c][0]) {// retreat to see if the first aisle can start later - r = c = 0; - z = minZ++; - matchContext.reset(); - findFirstAisle = false; - } - } else { - z++;// continue searching for the first aisle - } - continue loop; - } - } - } - findFirstAisle = true; - z++; - - // Check layer-local matcher predicate - for (Map.Entry entry : layerCount.entrySet()) { - if (entry.getValue() < entry.getKey().minLayerCount) { - worldState.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 3)); - return null; - } - } - validRepetitions++; - } - // Repetitions out of range - if (r < aisleRepetitions[c][0]) { - if (!worldState.hasError()) { - worldState.setError(new PatternError()); - } - return null; - } - // finished checking the aisle, so store the repetitions - formedRepetitionCount[c] = validRepetitions; - } + worldState.world = world; + + int aisleOffset = -1; + GreggyBlockPos controllerPos = new GreggyBlockPos(centerPos); - // Check count matches amount - for (Map.Entry entry : globalCount.entrySet()) { - if (entry.getValue() < entry.getKey().minGlobalCount) { - worldState.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 1)); - return null; + for (int aisleI = 0; aisleI < aisles.length; aisleI++) { + PatternAisle aisle = aisles[aisleI]; + + for (int repeats = aisle.minRepeats; repeats <= aisle.maxRepeats; repeats++) { + boolean aisleResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI, aisleOffset + repeats, isFlipped); + + // since aisle repetitions goes up, if this amount of repetitions are invalid, and all smaller repetitions + // have already been checked, then all larger repetitions will also be invalid(since they must also contain + // the problematic section), so the pattern cannot be correct + if (!aisleResult) return false; + + // if this isn't the last aisle, then check the next aisle after to see if this repetition matches with that + // if not, then this repetition is invalid. This only checks the first aisle even if it has a min repeat > 1, but yeah + if (aisleI != aisles.length - 1) { + boolean nextResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI + 1, aisleOffset + repeats + 1, isFlipped); + + // if the next aisle is also valid, then move on + if (nextResult) { + aisleOffset += repeats; + break; + } + } } } - worldState.setError(null); + info.setError(null); matchContext.setNeededFlip(isFlipped); - return matchContext; + return true; } - public void autoBuild(EntityPlayer player, MultiblockControllerBase controllerBase) { - World world = player.world; - BlockWorldState worldState = new BlockWorldState(); - int minZ = -centerOffset[4]; - EnumFacing facing = controllerBase.getFrontFacing().getOpposite(); - BlockPos centerPos = controllerBase.getPos(); - Map cacheInfos = new HashMap<>(); - Map cacheGlobal = new HashMap<>(); - Map blocks = new HashMap<>(); - blocks.put(controllerBase.getPos(), controllerBase); - for (int c = 0, z = minZ++, r; c < this.fingerLength; c++) { - for (r = 0; r < aisleRepetitions[c][0]; r++) { - Map cacheLayer = new HashMap<>(); - for (int b = 0, y = -centerOffset[1]; b < this.thumbLength; b++, y++) { - for (int a = 0, x = -centerOffset[0]; a < this.palmLength; a++, x++) { - TraceabilityPredicate predicate = this.blockMatches[c][b][a]; - BlockPos pos = setActualRelativeOffset(x, y, z, facing, controllerBase.getUpwardsFacing(), - controllerBase.isFlipped()) - .add(centerPos.getX(), centerPos.getY(), centerPos.getZ()); - worldState.update(world, pos, matchContext, globalCount, layerCount, predicate); - if (!world.getBlockState(pos).getMaterial().isReplaceable()) { - blocks.put(pos, world.getBlockState(pos)); - for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { - limit.testLimited(worldState); - } - } else { - boolean find = false; - BlockInfo[] infos = new BlockInfo[0]; - for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { - if (limit.minLayerCount > 0) { - if (!cacheLayer.containsKey(limit)) { - cacheLayer.put(limit, 1); - } else - if (cacheLayer.get(limit) < limit.minLayerCount && (limit.maxLayerCount == -1 || - cacheLayer.get(limit) < limit.maxLayerCount)) { - cacheLayer.put(limit, cacheLayer.get(limit) + 1); - } else { - continue; - } - } else { - continue; - } - if (!cacheInfos.containsKey(limit)) { - cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); - } - infos = cacheInfos.get(limit); - find = true; - break; - } - if (!find) { - for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { - if (limit.minGlobalCount > 0) { - if (!cacheGlobal.containsKey(limit)) { - cacheGlobal.put(limit, 1); - } else if (cacheGlobal.get(limit) < limit.minGlobalCount && - (limit.maxGlobalCount == -1 || - cacheGlobal.get(limit) < limit.maxGlobalCount)) { - cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); - } else { - continue; - } - } else { - continue; - } - if (!cacheInfos.containsKey(limit)) { - cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); - } - infos = cacheInfos.get(limit); - find = true; - break; - } - } - if (!find) { // no limited - for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { - if (limit.maxLayerCount != -1 && - cacheLayer.getOrDefault(limit, Integer.MAX_VALUE) == limit.maxLayerCount) - continue; - if (limit.maxGlobalCount != -1 && - cacheGlobal.getOrDefault(limit, Integer.MAX_VALUE) == limit.maxGlobalCount) - continue; - if (!cacheInfos.containsKey(limit)) { - cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); - } - if (cacheLayer.containsKey(limit)) { - cacheLayer.put(limit, cacheLayer.get(limit) + 1); - } else { - cacheLayer.put(limit, 1); - } - if (cacheGlobal.containsKey(limit)) { - cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); - } else { - cacheGlobal.put(limit, 1); - } - infos = ArrayUtils.addAll(infos, cacheInfos.get(limit)); - } - for (TraceabilityPredicate.SimplePredicate common : predicate.common) { - if (!cacheInfos.containsKey(common)) { - cacheInfos.put(common, - common.candidates == null ? null : common.candidates.get()); - } - infos = ArrayUtils.addAll(infos, cacheInfos.get(common)); - } - } - - List candidates = Arrays.stream(infos) - .filter(info -> info.getBlockState().getBlock() != Blocks.AIR).map(info -> { - IBlockState blockState = info.getBlockState(); - MetaTileEntity metaTileEntity = info - .getTileEntity() instanceof IGregTechTileEntity ? - ((IGregTechTileEntity) info.getTileEntity()) - .getMetaTileEntity() : - null; - if (metaTileEntity != null) { - return metaTileEntity.getStackForm(); - } else { - return new ItemStack(Item.getItemFromBlock(blockState.getBlock()), 1, - blockState.getBlock().damageDropped(blockState)); - } - }).collect(Collectors.toList()); - if (candidates.isEmpty()) continue; - // check inventory - ItemStack found = null; - if (!player.isCreative()) { - for (ItemStack itemStack : player.inventory.mainInventory) { - if (candidates.stream().anyMatch(candidate -> candidate.isItemEqual(itemStack)) && - !itemStack.isEmpty() && itemStack.getItem() instanceof ItemBlock) { - found = itemStack.copy(); - itemStack.setCount(itemStack.getCount() - 1); - break; - } - } - if (found == null) continue; - } else { - for (int i = candidates.size() - 1; i >= 0; i--) { - found = candidates.get(i).copy(); - if (!found.isEmpty() && found.getItem() instanceof ItemBlock) { - break; - } - found = null; - } - if (found == null) continue; - } - ItemBlock itemBlock = (ItemBlock) found.getItem(); - IBlockState state = itemBlock.getBlock() - .getStateFromMeta(itemBlock.getMetadata(found.getMetadata())); - blocks.put(pos, state); - world.setBlockState(pos, state); - TileEntity holder = world.getTileEntity(pos); - if (holder instanceof IGregTechTileEntity igtte) { - MTERegistry registry = GregTechAPI.mteManager - .getRegistry(found.getItem().getRegistryName().getNamespace()); - MetaTileEntity sampleMetaTileEntity = registry.getObjectById(found.getItemDamage()); - if (sampleMetaTileEntity != null) { - MetaTileEntity metaTileEntity = igtte.setMetaTileEntity(sampleMetaTileEntity); - metaTileEntity.onPlacement(player); - blocks.put(pos, metaTileEntity); - if (found.getTagCompound() != null) { - metaTileEntity.initFromItemStackData(found.getTagCompound()); - } - } - } - } - } - } - z++; + /** + * Checks a specific aisle for validity + * @param controllerPos The position of the controller + * @param frontFacing The front facing of the controller + * @param upFacing The up facing of the controller + * @param aisleIndex The index of the aisle, this is where the pattern is gotten from, treats repeatable aisles as only 1 + * @param aisleOffset The offset of the aisle, how much offset in aisleDir to check the blocks in world, for example, if the first aisle is repeated 2 times, aisleIndex is 1 while this is 2 + * @param flip Whether to flip or not + * @return True if the check passed, otherwise the {@link StructureInfo} would have been updated with an error + */ + public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing, int aisleIndex, int aisleOffset, boolean flip) { + // absolute facings from the relative facings + EnumFacing absoluteAisle = structureDir[0].getRelativeFacing(frontFacing, upFacing, flip); + EnumFacing absoluteString = structureDir[1].getRelativeFacing(frontFacing, upFacing, flip); + EnumFacing absoluteChar = structureDir[2].getRelativeFacing(frontFacing, upFacing, flip); + + // where the aisle would start in world + GreggyBlockPos aisleStart = startPos(controllerPos, frontFacing, upFacing, flip) + .offset(absoluteAisle, aisleOffset); + // where the current string would start in world + GreggyBlockPos stringStart = aisleStart.copy(); + // where the char being checked is + GreggyBlockPos charPos = aisleStart.copy(); + PatternAisle aisle = aisles[aisleIndex]; + + layerCount.clear(); + + for (int stringI = 0; stringI < dimensions[1]; stringI++) { + for (int charI = 0; charI < dimensions[2]; charI++) { + worldState.setPos(charPos); + TraceabilityPredicate predicate = predicates.get(aisle.charAt(stringI, charI)); + boolean result = predicate.test(worldState, info, globalCount, layerCount); + if (!result) return false; + + charPos.offset(absoluteChar); } - } - EnumFacing[] facings = ArrayUtils.addAll(new EnumFacing[] { controllerBase.getFrontFacing() }, FACINGS); // follow - // controller - // first - blocks.forEach((pos, block) -> { // adjust facing - if (block instanceof MetaTileEntity) { - MetaTileEntity metaTileEntity = (MetaTileEntity) block; - boolean find = false; - for (EnumFacing enumFacing : facings) { - if (metaTileEntity.isValidFrontFacing(enumFacing)) { - if (!blocks.containsKey(pos.offset(enumFacing))) { - metaTileEntity.setFrontFacing(enumFacing); - find = true; - break; - } - } - } - if (!find) { - for (EnumFacing enumFacing : FACINGS) { - if (world.isAirBlock(pos.offset(enumFacing)) && metaTileEntity.isValidFrontFacing(enumFacing)) { - metaTileEntity.setFrontFacing(enumFacing); - break; - } - } + + // offset the string start once after every string + stringStart.offset(absoluteString); + charPos.from(stringStart); + + // layer minimum checks + for (Object2IntMap.Entry entry : layerCount.object2IntEntrySet()) { + if (entry.getIntValue() < entry.getKey().minLayerCount) { + info.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 3)); + return false; } } - }); + } + return true; + } + + public void autoBuild(EntityPlayer player, MultiblockControllerBase controllerBase) { +// World world = player.world; +// BlockWorldState worldState = new BlockWorldState(); +// int minZ = -centerOffset[4]; +// EnumFacing facing = controllerBase.getFrontFacing().getOpposite(); +// BlockPos centerPos = controllerBase.getPos(); +// Map cacheInfos = new HashMap<>(); +// Map cacheGlobal = new HashMap<>(); +// Map blocks = new HashMap<>(); +// blocks.put(controllerBase.getPos(), controllerBase); +// for (int c = 0, z = minZ++, r; c < this.fingerLength; c++) { +// for (r = 0; r < aisleRepetitions[c][0]; r++) { +// Map cacheLayer = new HashMap<>(); +// for (int b = 0, y = -centerOffset[1]; b < this.thumbLength; b++, y++) { +// for (int a = 0, x = -centerOffset[0]; a < this.palmLength; a++, x++) { +// TraceabilityPredicate predicate = this.blockMatches[c][b][a]; +// BlockPos pos = setActualRelativeOffset(x, y, z, facing, controllerBase.getUpwardsFacing(), +// controllerBase.isFlipped()) +// .add(centerPos.getX(), centerPos.getY(), centerPos.getZ()); +// worldState.update(world, pos, matchContext, globalCount, layerCount, predicate); +// if (!world.getBlockState(pos).getMaterial().isReplaceable()) { +// blocks.put(pos, world.getBlockState(pos)); +// for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { +// limit.testLimited(worldState); +// } +// } else { +// boolean find = false; +// BlockInfo[] infos = new BlockInfo[0]; +// for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { +// if (limit.minLayerCount > 0) { +// if (!cacheLayer.containsKey(limit)) { +// cacheLayer.put(limit, 1); +// } else +// if (cacheLayer.get(limit) < limit.minLayerCount && (limit.maxLayerCount == -1 || +// cacheLayer.get(limit) < limit.maxLayerCount)) { +// cacheLayer.put(limit, cacheLayer.get(limit) + 1); +// } else { +// continue; +// } +// } else { +// continue; +// } +// if (!cacheInfos.containsKey(limit)) { +// cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); +// } +// infos = cacheInfos.get(limit); +// find = true; +// break; +// } +// if (!find) { +// for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { +// if (limit.minGlobalCount > 0) { +// if (!cacheGlobal.containsKey(limit)) { +// cacheGlobal.put(limit, 1); +// } else if (cacheGlobal.get(limit) < limit.minGlobalCount && +// (limit.maxGlobalCount == -1 || +// cacheGlobal.get(limit) < limit.maxGlobalCount)) { +// cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); +// } else { +// continue; +// } +// } else { +// continue; +// } +// if (!cacheInfos.containsKey(limit)) { +// cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); +// } +// infos = cacheInfos.get(limit); +// find = true; +// break; +// } +// } +// if (!find) { // no limited +// for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { +// if (limit.maxLayerCount != -1 && +// cacheLayer.getOrDefault(limit, Integer.MAX_VALUE) == limit.maxLayerCount) +// continue; +// if (limit.maxGlobalCount != -1 && +// cacheGlobal.getOrDefault(limit, Integer.MAX_VALUE) == limit.maxGlobalCount) +// continue; +// if (!cacheInfos.containsKey(limit)) { +// cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); +// } +// if (cacheLayer.containsKey(limit)) { +// cacheLayer.put(limit, cacheLayer.get(limit) + 1); +// } else { +// cacheLayer.put(limit, 1); +// } +// if (cacheGlobal.containsKey(limit)) { +// cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); +// } else { +// cacheGlobal.put(limit, 1); +// } +// infos = ArrayUtils.addAll(infos, cacheInfos.get(limit)); +// } +// for (TraceabilityPredicate.SimplePredicate common : predicate.common) { +// if (!cacheInfos.containsKey(common)) { +// cacheInfos.put(common, +// common.candidates == null ? null : common.candidates.get()); +// } +// infos = ArrayUtils.addAll(infos, cacheInfos.get(common)); +// } +// } +// +// List candidates = Arrays.stream(infos) +// .filter(info -> info.getBlockState().getBlock() != Blocks.AIR).map(info -> { +// IBlockState blockState = info.getBlockState(); +// MetaTileEntity metaTileEntity = info +// .getTileEntity() instanceof IGregTechTileEntity ? +// ((IGregTechTileEntity) info.getTileEntity()) +// .getMetaTileEntity() : +// null; +// if (metaTileEntity != null) { +// return metaTileEntity.getStackForm(); +// } else { +// return new ItemStack(Item.getItemFromBlock(blockState.getBlock()), 1, +// blockState.getBlock().damageDropped(blockState)); +// } +// }).collect(Collectors.toList()); +// if (candidates.isEmpty()) continue; +// // check inventory +// ItemStack found = null; +// if (!player.isCreative()) { +// for (ItemStack itemStack : player.inventory.mainInventory) { +// if (candidates.stream().anyMatch(candidate -> candidate.isItemEqual(itemStack)) && +// !itemStack.isEmpty() && itemStack.getItem() instanceof ItemBlock) { +// found = itemStack.copy(); +// itemStack.setCount(itemStack.getCount() - 1); +// break; +// } +// } +// if (found == null) continue; +// } else { +// for (int i = candidates.size() - 1; i >= 0; i--) { +// found = candidates.get(i).copy(); +// if (!found.isEmpty() && found.getItem() instanceof ItemBlock) { +// break; +// } +// found = null; +// } +// if (found == null) continue; +// } +// ItemBlock itemBlock = (ItemBlock) found.getItem(); +// IBlockState state = itemBlock.getBlock() +// .getStateFromMeta(itemBlock.getMetadata(found.getMetadata())); +// blocks.put(pos, state); +// world.setBlockState(pos, state); +// TileEntity holder = world.getTileEntity(pos); +// if (holder instanceof IGregTechTileEntity igtte) { +// MTERegistry registry = GregTechAPI.mteManager +// .getRegistry(found.getItem().getRegistryName().getNamespace()); +// MetaTileEntity sampleMetaTileEntity = registry.getObjectById(found.getItemDamage()); +// if (sampleMetaTileEntity != null) { +// MetaTileEntity metaTileEntity = igtte.setMetaTileEntity(sampleMetaTileEntity); +// metaTileEntity.onPlacement(); +// blocks.put(pos, metaTileEntity); +// if (found.getTagCompound() != null) { +// metaTileEntity.initFromItemStackData(found.getTagCompound()); +// } +// } +// } +// } +// } +// } +// z++; +// } +// } +// EnumFacing[] facings = ArrayUtils.addAll(new EnumFacing[] { controllerBase.getFrontFacing() }, FACINGS); // follow +// // controller +// // first +// blocks.forEach((pos, block) -> { // adjust facing +// if (block instanceof MetaTileEntity) { +// MetaTileEntity metaTileEntity = (MetaTileEntity) block; +// boolean find = false; +// for (EnumFacing enumFacing : facings) { +// if (metaTileEntity.isValidFrontFacing(enumFacing)) { +// if (!blocks.containsKey(pos.offset(enumFacing))) { +// metaTileEntity.setFrontFacing(enumFacing); +// find = true; +// break; +// } +// } +// } +// if (!find) { +// for (EnumFacing enumFacing : FACINGS) { +// if (world.isAirBlock(pos.offset(enumFacing)) && metaTileEntity.isValidFrontFacing(enumFacing)) { +// metaTileEntity.setFrontFacing(enumFacing); +// break; +// } +// } +// } +// } +// }); } public BlockInfo[][][] getPreview(int[] repetition) { - Map cacheInfos = new HashMap<>(); - Map cacheGlobal = new HashMap<>(); - Map blocks = new HashMap<>(); - int minX = Integer.MAX_VALUE; - int minY = Integer.MAX_VALUE; - int minZ = Integer.MAX_VALUE; - int maxX = Integer.MIN_VALUE; - int maxY = Integer.MIN_VALUE; - int maxZ = Integer.MIN_VALUE; - for (int l = 0, x = 0; l < this.fingerLength; l++) { - for (int r = 0; r < repetition[l]; r++) { - // Checking single slice - Map cacheLayer = new HashMap<>(); - for (int y = 0; y < this.thumbLength; y++) { - for (int z = 0; z < this.palmLength; z++) { - TraceabilityPredicate predicate = this.blockMatches[l][y][z]; - boolean find = false; - BlockInfo[] infos = null; - for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { // check layer and - // previewCount - if (limit.minLayerCount > 0) { - if (!cacheLayer.containsKey(limit)) { - cacheLayer.put(limit, 1); - } else if (cacheLayer.get(limit) < limit.minLayerCount) { - cacheLayer.put(limit, cacheLayer.get(limit) + 1); - } else { - continue; - } - if (cacheGlobal.getOrDefault(limit, 0) < limit.previewCount) { - if (!cacheGlobal.containsKey(limit)) { - cacheGlobal.put(limit, 1); - } else if (cacheGlobal.get(limit) < limit.previewCount) { - cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); - } else { - continue; - } - } - } else { - continue; - } - if (!cacheInfos.containsKey(limit)) { - cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); - } - infos = cacheInfos.get(limit); - find = true; - break; - } - if (!find) { // check global and previewCount - for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { - if (limit.minGlobalCount == -1 && limit.previewCount == -1) continue; - if (cacheGlobal.getOrDefault(limit, 0) < limit.previewCount) { - if (!cacheGlobal.containsKey(limit)) { - cacheGlobal.put(limit, 1); - } else if (cacheGlobal.get(limit) < limit.previewCount) { - cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); - } else { - continue; - } - } else if (limit.minGlobalCount > 0) { - if (!cacheGlobal.containsKey(limit)) { - cacheGlobal.put(limit, 1); - } else if (cacheGlobal.get(limit) < limit.minGlobalCount) { - cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); - } else { - continue; - } - } else { - continue; - } - if (!cacheInfos.containsKey(limit)) { - cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); - } - infos = cacheInfos.get(limit); - find = true; - break; - } - } - if (!find) { // check common with previewCount - for (TraceabilityPredicate.SimplePredicate common : predicate.common) { - if (common.previewCount > 0) { - if (!cacheGlobal.containsKey(common)) { - cacheGlobal.put(common, 1); - } else if (cacheGlobal.get(common) < common.previewCount) { - cacheGlobal.put(common, cacheGlobal.get(common) + 1); - } else { - continue; - } - } else { - continue; - } - if (!cacheInfos.containsKey(common)) { - cacheInfos.put(common, common.candidates == null ? null : common.candidates.get()); - } - infos = cacheInfos.get(common); - find = true; - break; - } - } - if (!find) { // check without previewCount - for (TraceabilityPredicate.SimplePredicate common : predicate.common) { - if (common.previewCount == -1) { - if (!cacheInfos.containsKey(common)) { - cacheInfos.put(common, - common.candidates == null ? null : common.candidates.get()); - } - infos = cacheInfos.get(common); - find = true; - break; - } - } - } - if (!find) { // check max - for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { - if (limit.previewCount != -1) { - continue; - } else if (limit.maxGlobalCount != -1 || limit.maxLayerCount != -1) { - if (cacheGlobal.getOrDefault(limit, 0) < limit.maxGlobalCount) { - if (!cacheGlobal.containsKey(limit)) { - cacheGlobal.put(limit, 1); - } else { - cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); - } - } else if (cacheLayer.getOrDefault(limit, 0) < limit.maxLayerCount) { - if (!cacheLayer.containsKey(limit)) { - cacheLayer.put(limit, 1); - } else { - cacheLayer.put(limit, cacheLayer.get(limit) + 1); - } - } else { - continue; - } - } - - if (!cacheInfos.containsKey(limit)) { - cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); - } - infos = cacheInfos.get(limit); - break; - } - } - BlockInfo info = infos == null || infos.length == 0 ? BlockInfo.EMPTY : infos[0]; - BlockPos pos = setActualRelativeOffset(z, y, x, EnumFacing.NORTH, EnumFacing.UP, false); - // TODO - if (info.getTileEntity() instanceof MetaTileEntityHolder) { - MetaTileEntityHolder holder = new MetaTileEntityHolder(); - holder.setMetaTileEntity(((MetaTileEntityHolder) info.getTileEntity()).getMetaTileEntity()); - holder.getMetaTileEntity().onPlacement(); - info = new BlockInfo(holder.getMetaTileEntity().getBlock().getDefaultState(), holder); - } - blocks.put(pos, info); - minX = Math.min(pos.getX(), minX); - minY = Math.min(pos.getY(), minY); - minZ = Math.min(pos.getZ(), minZ); - maxX = Math.max(pos.getX(), maxX); - maxY = Math.max(pos.getY(), maxY); - maxZ = Math.max(pos.getZ(), maxZ); - } - } - x++; - } - } - BlockInfo[][][] result = (BlockInfo[][][]) Array.newInstance(BlockInfo.class, maxX - minX + 1, maxY - minY + 1, - maxZ - minZ + 1); - int finalMinX = minX; - int finalMinY = minY; - int finalMinZ = minZ; - blocks.forEach((pos, info) -> { - if (info.getTileEntity() instanceof MetaTileEntityHolder) { - MetaTileEntity metaTileEntity = ((MetaTileEntityHolder) info.getTileEntity()).getMetaTileEntity(); - boolean find = false; - for (EnumFacing enumFacing : FACINGS) { - if (metaTileEntity.isValidFrontFacing(enumFacing)) { - if (!blocks.containsKey(pos.offset(enumFacing))) { - metaTileEntity.setFrontFacing(enumFacing); - find = true; - break; - } - } - } - if (!find) { - for (EnumFacing enumFacing : FACINGS) { - BlockInfo blockInfo = blocks.get(pos.offset(enumFacing)); - if (blockInfo != null && blockInfo.getBlockState().getBlock() == Blocks.AIR && - metaTileEntity.isValidFrontFacing(enumFacing)) { - metaTileEntity.setFrontFacing(enumFacing); - break; - } - } - } - } - result[pos.getX() - finalMinX][pos.getY() - finalMinY][pos.getZ() - finalMinZ] = info; - }); - return result; + return null; +// Map cacheInfos = new HashMap<>(); +// Map cacheGlobal = new HashMap<>(); +// Map blocks = new HashMap<>(); +// int minX = Integer.MAX_VALUE; +// int minY = Integer.MAX_VALUE; +// int minZ = Integer.MAX_VALUE; +// int maxX = Integer.MIN_VALUE; +// int maxY = Integer.MIN_VALUE; +// int maxZ = Integer.MIN_VALUE; +// for (int l = 0, x = 0; l < this.fingerLength; l++) { +// for (int r = 0; r < repetition[l]; r++) { +// // Checking single slice +// Map cacheLayer = new HashMap<>(); +// for (int y = 0; y < this.thumbLength; y++) { +// for (int z = 0; z < this.palmLength; z++) { +// TraceabilityPredicate predicate = this.blockMatches[l][y][z]; +// boolean find = false; +// BlockInfo[] infos = null; +// for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { // check layer and +// // previewCount +// if (limit.minLayerCount > 0) { +// if (!cacheLayer.containsKey(limit)) { +// cacheLayer.put(limit, 1); +// } else if (cacheLayer.get(limit) < limit.minLayerCount) { +// cacheLayer.put(limit, cacheLayer.get(limit) + 1); +// } else { +// continue; +// } +// if (cacheGlobal.getOrDefault(limit, 0) < limit.previewCount) { +// if (!cacheGlobal.containsKey(limit)) { +// cacheGlobal.put(limit, 1); +// } else if (cacheGlobal.get(limit) < limit.previewCount) { +// cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); +// } else { +// continue; +// } +// } +// } else { +// continue; +// } +// if (!cacheInfos.containsKey(limit)) { +// cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); +// } +// infos = cacheInfos.get(limit); +// find = true; +// break; +// } +// if (!find) { // check global and previewCount +// for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { +// if (limit.minGlobalCount == -1 && limit.previewCount == -1) continue; +// if (cacheGlobal.getOrDefault(limit, 0) < limit.previewCount) { +// if (!cacheGlobal.containsKey(limit)) { +// cacheGlobal.put(limit, 1); +// } else if (cacheGlobal.get(limit) < limit.previewCount) { +// cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); +// } else { +// continue; +// } +// } else if (limit.minGlobalCount > 0) { +// if (!cacheGlobal.containsKey(limit)) { +// cacheGlobal.put(limit, 1); +// } else if (cacheGlobal.get(limit) < limit.minGlobalCount) { +// cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); +// } else { +// continue; +// } +// } else { +// continue; +// } +// if (!cacheInfos.containsKey(limit)) { +// cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); +// } +// infos = cacheInfos.get(limit); +// find = true; +// break; +// } +// } +// if (!find) { // check common with previewCount +// for (TraceabilityPredicate.SimplePredicate common : predicate.common) { +// if (common.previewCount > 0) { +// if (!cacheGlobal.containsKey(common)) { +// cacheGlobal.put(common, 1); +// } else if (cacheGlobal.get(common) < common.previewCount) { +// cacheGlobal.put(common, cacheGlobal.get(common) + 1); +// } else { +// continue; +// } +// } else { +// continue; +// } +// if (!cacheInfos.containsKey(common)) { +// cacheInfos.put(common, common.candidates == null ? null : common.candidates.get()); +// } +// infos = cacheInfos.get(common); +// find = true; +// break; +// } +// } +// if (!find) { // check without previewCount +// for (TraceabilityPredicate.SimplePredicate common : predicate.common) { +// if (common.previewCount == -1) { +// if (!cacheInfos.containsKey(common)) { +// cacheInfos.put(common, +// common.candidates == null ? null : common.candidates.get()); +// } +// infos = cacheInfos.get(common); +// find = true; +// break; +// } +// } +// } +// if (!find) { // check max +// for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { +// if (limit.previewCount != -1) { +// continue; +// } else if (limit.maxGlobalCount != -1 || limit.maxLayerCount != -1) { +// if (cacheGlobal.getOrDefault(limit, 0) < limit.maxGlobalCount) { +// if (!cacheGlobal.containsKey(limit)) { +// cacheGlobal.put(limit, 1); +// } else { +// cacheGlobal.put(limit, cacheGlobal.get(limit) + 1); +// } +// } else if (cacheLayer.getOrDefault(limit, 0) < limit.maxLayerCount) { +// if (!cacheLayer.containsKey(limit)) { +// cacheLayer.put(limit, 1); +// } else { +// cacheLayer.put(limit, cacheLayer.get(limit) + 1); +// } +// } else { +// continue; +// } +// } +// +// if (!cacheInfos.containsKey(limit)) { +// cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get()); +// } +// infos = cacheInfos.get(limit); +// break; +// } +// } +// BlockInfo info = infos == null || infos.length == 0 ? BlockInfo.EMPTY : infos[0]; +// BlockPos pos = setActualRelativeOffset(z, y, x, EnumFacing.NORTH, EnumFacing.UP, false); +// // TODO +// if (info.getTileEntity() instanceof MetaTileEntityHolder) { +// MetaTileEntityHolder holder = new MetaTileEntityHolder(); +// holder.setMetaTileEntity(((MetaTileEntityHolder) info.getTileEntity()).getMetaTileEntity()); +// holder.getMetaTileEntity().onPlacement(); +// info = new BlockInfo(holder.getMetaTileEntity().getBlock().getDefaultState(), holder); +// } +// blocks.put(pos, info); +// minX = Math.min(pos.getX(), minX); +// minY = Math.min(pos.getY(), minY); +// minZ = Math.min(pos.getZ(), minZ); +// maxX = Math.max(pos.getX(), maxX); +// maxY = Math.max(pos.getY(), maxY); +// maxZ = Math.max(pos.getZ(), maxZ); +// } +// } +// x++; +// } +// } +// BlockInfo[][][] result = (BlockInfo[][][]) Array.newInstance(BlockInfo.class, maxX - minX + 1, maxY - minY + 1, +// maxZ - minZ + 1); +// int finalMinX = minX; +// int finalMinY = minY; +// int finalMinZ = minZ; +// blocks.forEach((pos, info) -> { +// if (info.getTileEntity() instanceof MetaTileEntityHolder) { +// MetaTileEntity metaTileEntity = ((MetaTileEntityHolder) info.getTileEntity()).getMetaTileEntity(); +// boolean find = false; +// for (EnumFacing enumFacing : FACINGS) { +// if (metaTileEntity.isValidFrontFacing(enumFacing)) { +// if (!blocks.containsKey(pos.offset(enumFacing))) { +// metaTileEntity.setFrontFacing(enumFacing); +// find = true; +// break; +// } +// } +// } +// if (!find) { +// for (EnumFacing enumFacing : FACINGS) { +// BlockInfo blockInfo = blocks.get(pos.offset(enumFacing)); +// if (blockInfo != null && blockInfo.getBlockState().getBlock() == Blocks.AIR && +// metaTileEntity.isValidFrontFacing(enumFacing)) { +// metaTileEntity.setFrontFacing(enumFacing); +// break; +// } +// } +// } +// } +// result[pos.getX() - finalMinX][pos.getY() - finalMinY][pos.getZ() - finalMinZ] = info; +// }); +// return result; } - private BlockPos offsetFrom(BlockPos start, int aisleOffset, int stringOffset, int charOffset, EnumFacing frontFacing, - EnumFacing upFacing, boolean flip) { - GreggyBlockPos pos = new GreggyBlockPos(start); + private GreggyBlockPos offsetFrom(GreggyBlockPos start, int aisleOffset, int stringOffset, int charOffset, @NotNull EnumFacing frontFacing, + @NotNull EnumFacing upFacing, boolean flip) { + GreggyBlockPos pos = start.copy(); pos.offset(structureDir[0].getRelativeFacing(frontFacing, upFacing, flip), aisleOffset); pos.offset(structureDir[1].getRelativeFacing(frontFacing, upFacing, flip), stringOffset); pos.offset(structureDir[2].getRelativeFacing(frontFacing, upFacing, flip), charOffset); - return pos.immutable(); + return pos; + } + + private GreggyBlockPos startPos(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing, boolean flip) { + // negate since the offsets are the opposite direction of structureDir + return offsetFrom(controllerPos, -startOffset[0], -startOffset[1], -startOffset[2], frontFacing, upFacing, flip); } private BlockPos setActualRelativeOffset(int x, int y, int z, EnumFacing facing, EnumFacing upwardsFacing, diff --git a/src/main/java/gregtech/api/pattern/BlockWorldState.java b/src/main/java/gregtech/api/pattern/BlockWorldState.java index 48473ba7151..3c2dae0771a 100644 --- a/src/main/java/gregtech/api/pattern/BlockWorldState.java +++ b/src/main/java/gregtech/api/pattern/BlockWorldState.java @@ -1,51 +1,69 @@ package gregtech.api.pattern; +import gregtech.api.util.GTLog; + import net.minecraft.block.state.IBlockState; import net.minecraft.tileentity.TileEntity; -import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.BlockPos.MutableBlockPos; import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; -import java.util.Map; - +/** + * Class allowing access to a block at a certain pos for structure checks and contains structure information for legacy + */ public class BlockWorldState { - + protected static boolean warned = false; protected World world; protected BlockPos pos; protected IBlockState state; protected TileEntity tileEntity; protected boolean tileEntityInitialized; - protected PatternMatchContext matchContext; - protected TraceabilityPredicate predicate; - protected PatternError error; + protected final StructureInfo info; + + public BlockWorldState(StructureInfo info) { + this.info = info; + } - public void update(World worldIn, BlockPos posIn, PatternMatchContext matchContext, - TraceabilityPredicate predicate) { + public void update(World worldIn, GreggyBlockPos pos) { this.world = worldIn; - this.pos = posIn; + this.pos = pos.immutable(); + this.state = null; + this.tileEntity = null; + this.tileEntityInitialized = false; + } + + public void setPos(GreggyBlockPos pos) { + setPos(pos.immutable()); + } + + public void setPos(BlockPos pos) { + this.pos = pos.toImmutable(); this.state = null; this.tileEntity = null; this.tileEntityInitialized = false; - this.matchContext = matchContext; - this.error = null; } + @Deprecated public boolean hasError() { - return error != null; + warn("hasError()"); + return info.getError() != null; } + @Deprecated public void setError(PatternError error) { - this.error = error; - if (error != null) { - error.setWorldState(this); - } + warn("setError(PatternError)"); + info.setError(error); } + @Deprecated public PatternMatchContext getMatchContext() { - return matchContext; + warn("getMatchContext()"); + return info.getContext(); + } + + public StructureInfo getStructureInfo() { + return this.info; } public IBlockState getBlockState() { @@ -73,4 +91,15 @@ public BlockPos getPos() { public World getWorld() { return world; } + + public void setWorld(World world) { + this.world = world; + } + + protected void warn(String name) { + if (warned) return; + + GTLog.logger.warn("Calling " + name + " on BlockWorldState is deprecated! Use the method on StructureInfo, obtained via BlockWorldState#getStructureInfo() !"); + warned = true; + } } diff --git a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java index a8f28978e28..f92e9337ec0 100644 --- a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java +++ b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java @@ -9,11 +9,8 @@ import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; -import java.lang.reflect.Array; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Map.Entry; import static gregtech.api.util.RelativeDirection.*; @@ -22,8 +19,8 @@ * A builder class for {@link BlockPattern}
* When the multiblock is placed, its facings are concrete. Then, the {@link RelativeDirection}s passed into * {@link FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)} are ways in which the - * pattern progresses. It can be thought like this, where startPos() is either defined via {@link FactoryBlockPattern#setStartOffset(int...)} - * , or automatically detected(for legacy compat only, you should use {@link FactoryBlockPattern#setStartOffset(int...)} always for new code): + * pattern progresses. It can be thought like this, where startPos() is either defined via {@link FactoryBlockPattern#setStartOffset(int, int, int)} + * , or automatically detected(for legacy compat only, you should use {@link FactoryBlockPattern#setStartOffset(int, int, int)} always for new code): *
  * {@code
  * for(int aisleI in 0..aisles):
@@ -42,13 +39,13 @@ public class FactoryBlockPattern {
     private static final Joiner COMMA_JOIN = Joiner.on(",");
 
     /**
-     * In the form of [ num char per string, num string per aisle, num aisles ]
+     * In the form of [ num aisles, num string per aisle, num char per string ]
      */
     private final int[] dimensions = { -1, -1, -1 };
     /**
      * In the form of [ aisleOffset, stringOffset, charOffset ] where the offsets are the opposite {@link RelativeDirection} of structure directions
      */
-    private int[] startOffset;
+    private final int[] startOffset = new int[3];
     private char centerChar;
 
     private final List aisles = new ArrayList<>();
@@ -101,8 +98,8 @@ public FactoryBlockPattern aisleRepeatable(int minRepeat, int maxRepeat, @NotNul
             throw new IllegalArgumentException("Empty pattern for aisle");
 
         // set the dimensions if the user hasn't already
-        if (dimensions[0] == -1) {
-            dimensions[0] = aisle[0].length();
+        if (dimensions[2] == -1) {
+            dimensions[2] = aisle[0].length();
         }
         if (dimensions[1] == -1) {
             dimensions[1] = aisle.length;
@@ -113,9 +110,9 @@ public FactoryBlockPattern aisleRepeatable(int minRepeat, int maxRepeat, @NotNul
                     ", but was given one with a height of " + aisle.length + ")");
         } else {
             for (String s : aisle) {
-                if (s.length() != dimensions[0]) {
+                if (s.length() != dimensions[2]) {
                     throw new IllegalArgumentException(
-                            "Not all rows in the given aisle are the correct width (expected " + dimensions[0] +
+                            "Not all rows in the given aisle are the correct width (expected " + dimensions[2] +
                                     ", found one with " + s.length() + ")");
                 }
 
@@ -207,8 +204,8 @@ public FactoryBlockPattern where(String symbol, TraceabilityPredicate blockMatch
 
     public BlockPattern build() {
         checkMissingPredicates();
-        this.dimensions[2] = aisles.size();
-        return new BlockPattern(aisles.toArray(PatternAisle[]::new), dimensions, structureDir, startOffset, symbolMap, centerChar);
+        this.dimensions[0] = aisles.size();
+        return new BlockPattern(aisles.toArray(new PatternAisle[0]), dimensions, structureDir, startOffset, symbolMap, centerChar);
     }
 
     private void checkMissingPredicates() {
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index d1023d561f2..7b0826ed1a2 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -29,8 +29,9 @@ public GreggyBlockPos(BlockPos base) {
      * @param axis  The axis to set
      * @param value The value of said coordinate
      */
-    public void set(EnumFacing.Axis axis, int value) {
+    public GreggyBlockPos set(EnumFacing.Axis axis, int value) {
         pos[axis.ordinal()] = value;
+        return this;
     }
 
     /**
@@ -43,31 +44,38 @@ public void set(EnumFacing.Axis axis, int value) {
      * @param p3 The axis is inferred from the other 2 axis(all 3 are unique, so if a1 is X and a2 is Z, then p3 is set
      *           in Y)
      */
-    public void setAxisRelative(EnumFacing.Axis a1, EnumFacing.Axis a2, int p1, int p2, int p3) {
+    public GreggyBlockPos setAxisRelative(EnumFacing.Axis a1, EnumFacing.Axis a2, int p1, int p2, int p3) {
         set(a1, p1);
         set(a2, p2);
-        // 3 cases since order doesn't matter(a3 is the axis we are trying to find):
-        // a3 = X: Y.ordinal() + Z.ordinal() = 3: a3.ordinal() is 0
-        // a3 = Y: X.ordinal() + Z.ordinal() = 2: a3.ordinal() is 1
-        // a3 = Z: X.ordinal() + Y.ordinal() = 1: a3.ordinal() is 2
-        // in all 3 cases, 3 - a1.ordinal() - a2.ordinal() correctly finds a3
+        // the 3 ordinals add up to 3, so to find the third axis just subtract the other 2 from 3
         pos[3 - a1.ordinal() - a2.ordinal()] = p3;
+        return this;
     }
 
     /**
      * Offsets in the given {@link EnumFacing} amount times {@link BlockPos#offset(EnumFacing)}
      */
-    public void offset(EnumFacing facing, int amount) {
+    public GreggyBlockPos offset(EnumFacing facing, int amount) {
         pos[0] += facing.getXOffset() * amount;
         pos[1] += facing.getYOffset() * amount;
         pos[2] += facing.getZOffset() * amount;
+        return this;
+    }
+
+    /**
+     * Sets this pos's position to be the same as the other one
+     * @param other The other pos to get position from
+     */
+    public GreggyBlockPos from(GreggyBlockPos other) {
+        System.arraycopy(other.pos, 0, this.pos, 0, 3);
+        return this;
     }
 
     /**
      * Equivalent to calling {@link GreggyBlockPos#offset(EnumFacing, int)} with amount set to 1
      */
-    public void offset(EnumFacing facing) {
-        offset(facing, 1);
+    public GreggyBlockPos offset(EnumFacing facing) {
+        return offset(facing, 1);
     }
 
     /**
@@ -92,6 +100,6 @@ public int get(EnumFacing.Axis axis) {
     }
 
     public GreggyBlockPos copy() {
-        return new GreggyBlockPos(pos[0], pos[1], pos[2]);
+        return new GreggyBlockPos().from(this);
     }
 }
diff --git a/src/main/java/gregtech/api/pattern/PatternAisle.java b/src/main/java/gregtech/api/pattern/PatternAisle.java
index 9226ee951ca..06a8f38d633 100644
--- a/src/main/java/gregtech/api/pattern/PatternAisle.java
+++ b/src/main/java/gregtech/api/pattern/PatternAisle.java
@@ -46,4 +46,14 @@ public int[] firstInstanceOf(char c) {
         }
         return null;
     }
+
+    /**
+     * Gets the char at the specified position.
+     * @param stringI The string index to get from
+     * @param charI The char index to get
+     * @return The char
+     */
+    public char charAt(int stringI, int charI) {
+        return pattern[stringI].charAt(charI);
+    }
 }
diff --git a/src/main/java/gregtech/api/pattern/PatternError.java b/src/main/java/gregtech/api/pattern/PatternError.java
index 62c014fa426..3aa7fb8290e 100644
--- a/src/main/java/gregtech/api/pattern/PatternError.java
+++ b/src/main/java/gregtech/api/pattern/PatternError.java
@@ -7,40 +7,36 @@
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
 
+import org.jetbrains.annotations.Nullable;
+
 import java.util.ArrayList;
 import java.util.List;
 
 public class PatternError {
 
-    protected BlockWorldState worldState;
+    protected BlockPos pos;
+    protected List> candidates;
 
-    public void setWorldState(BlockWorldState worldState) {
-        this.worldState = worldState;
+    public PatternError(BlockPos pos, List> candidates) {
+        this.pos = pos;
+        this.candidates = candidates;
     }
 
-    public World getWorld() {
-        return worldState.getWorld();
+    public PatternError(BlockPos pos, TraceabilityPredicate failingPredicate) {
+        this(pos, failingPredicate.getCandidates());
     }
 
+    @Nullable
     public BlockPos getPos() {
-        return worldState.getPos();
+        return pos;
     }
 
     public List> getCandidates() {
-        TraceabilityPredicate predicate = worldState.predicate;
-        List> candidates = new ArrayList<>();
-        for (TraceabilityPredicate.SimplePredicate common : predicate.common) {
-            candidates.add(common.getCandidates());
-        }
-        for (TraceabilityPredicate.SimplePredicate limited : predicate.limited) {
-            candidates.add(limited.getCandidates());
-        }
-        return candidates;
+        return this.candidates;
     }
 
     @SideOnly(Side.CLIENT)
     public String getErrorInfo() {
-        List> candidates = getCandidates();
         StringBuilder builder = new StringBuilder();
         for (List candidate : candidates) {
             if (!candidate.isEmpty()) {
@@ -49,6 +45,6 @@ public String getErrorInfo() {
             }
         }
         builder.append("...");
-        return I18n.format("gregtech.multiblock.pattern.error", builder.toString(), worldState.pos);
+        return I18n.format("gregtech.multiblock.pattern.error", builder.toString(), pos);
     }
 }
diff --git a/src/main/java/gregtech/api/pattern/PatternStringError.java b/src/main/java/gregtech/api/pattern/PatternStringError.java
index fc62bfecb27..e15f9c1fd41 100644
--- a/src/main/java/gregtech/api/pattern/PatternStringError.java
+++ b/src/main/java/gregtech/api/pattern/PatternStringError.java
@@ -1,12 +1,16 @@
 package gregtech.api.pattern;
 
 import net.minecraft.client.resources.I18n;
+import net.minecraft.util.math.BlockPos;
+
+import java.util.Collections;
 
 public class PatternStringError extends PatternError {
 
     public final String translateKey;
 
     public PatternStringError(String translateKey) {
+        super(new BlockPos(0, 0, 0), Collections.emptyList());
         this.translateKey = translateKey;
     }
 
diff --git a/src/main/java/gregtech/api/pattern/StructureInfo.java b/src/main/java/gregtech/api/pattern/StructureInfo.java
new file mode 100644
index 00000000000..8d806e1c3ef
--- /dev/null
+++ b/src/main/java/gregtech/api/pattern/StructureInfo.java
@@ -0,0 +1,22 @@
+package gregtech.api.pattern;
+
+public class StructureInfo {
+    protected final PatternMatchContext context;
+    protected PatternError error;
+    public StructureInfo(PatternMatchContext context, PatternError error) {
+        this.context = context;
+        this.error = error;
+    }
+
+    public PatternError getError() {
+        return error;
+    }
+
+    public PatternMatchContext getContext() {
+        return context;
+    }
+
+    public void setError(PatternError error) {
+        this.error = error;
+    }
+}
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index cdfee3c7195..aee7041aaaa 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -19,6 +19,7 @@
 import net.minecraftforge.fml.relauncher.SideOnly;
 
 import java.util.*;
+import java.util.function.BiPredicate;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
@@ -32,16 +33,16 @@ public class TraceabilityPredicate {
             blockWorldState -> blockWorldState.getBlockState().getBlock().isAir(blockWorldState.getBlockState(),
                     blockWorldState.getWorld(), blockWorldState.getPos()));
     // Allow all heating coils, and require them to have the same type.
-    public static Supplier HEATING_COILS = () -> new TraceabilityPredicate(blockWorldState -> {
+    public static Supplier HEATING_COILS = () -> new TraceabilityPredicate((blockWorldState, info) -> {
         IBlockState blockState = blockWorldState.getBlockState();
         if (GregTechAPI.HEATING_COILS.containsKey(blockState)) {
             IHeatingCoilBlockStats stats = GregTechAPI.HEATING_COILS.get(blockState);
-            Object currentCoil = blockWorldState.getMatchContext().getOrPut("CoilType", stats);
+            Object currentCoil = info.getContext().getOrPut("CoilType", stats);
             if (!currentCoil.equals(stats)) {
-                blockWorldState.setError(new PatternStringError("gregtech.multiblock.pattern.error.coils"));
+                info.setError(new PatternStringError("gregtech.multiblock.pattern.error.coils"));
                 return false;
             }
-            blockWorldState.getMatchContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos());
+            info.getContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos());
             return true;
         }
         return false;
@@ -68,6 +69,7 @@ public TraceabilityPredicate(TraceabilityPredicate predicate) {
         isSingle = predicate.isSingle;
     }
 
+    @Deprecated
     public TraceabilityPredicate(Predicate predicate, Supplier candidates) {
         common.add(new SimplePredicate(predicate, candidates));
     }
@@ -76,8 +78,13 @@ public TraceabilityPredicate(Predicate predicate) {
         this(predicate, null);
     }
 
-    public boolean isHasAir() {
-        return hasAir;
+    @Deprecated
+    public TraceabilityPredicate(BiPredicate predicate, Supplier candidates) {
+        common.add(new SimplePredicate(predicate, candidates));
+    }
+
+    public TraceabilityPredicate(BiPredicate predicate) {
+        this(predicate, null);
     }
 
     public boolean isSingle() {
@@ -124,6 +131,21 @@ public TraceabilityPredicate addTooltips(String... tips) {
         return this;
     }
 
+    /**
+     * Gets the candidates for this predicate.
+     * @return A list containing lists which group together candidates
+     */
+    public List> getCandidates() {
+        List> candidates = new ArrayList<>();
+        for (TraceabilityPredicate.SimplePredicate common : common) {
+            candidates.add(common.getCandidates());
+        }
+        for (TraceabilityPredicate.SimplePredicate limited : limited) {
+            candidates.add(limited.getCandidates());
+        }
+        return candidates;
+    }
+
     /**
      * Note: This method does not translate dynamically!! Parameters can not be updated once set.
      */
@@ -216,14 +238,13 @@ public TraceabilityPredicate setPreviewCount(int count) {
         return this;
     }
 
-    public boolean test(BlockWorldState blockWorldState) {
-        boolean flag = false;
+    public boolean test(BlockWorldState blockWorldState, StructureInfo info, Object2IntMap globalCache, Object2IntMap layerCache) {
         for (SimplePredicate predicate : limited) {
-            if (predicate.testLimited(blockWorldState)) {
-                flag = true;
+            if (predicate.testLimited(blockWorldState, info, globalCache, layerCache)) {
+                return true;
             }
         }
-        return flag || common.stream().anyMatch(predicate -> predicate.test(blockWorldState));
+        return common.stream().anyMatch(predicate -> predicate.test(blockWorldState, info));
     }
 
     public TraceabilityPredicate or(TraceabilityPredicate other) {
@@ -246,7 +267,7 @@ public static class SimplePredicate {
 
         public final Supplier candidates;
 
-        public final Predicate predicate;
+        public final BiPredicate predicate;
 
         @SideOnly(Side.CLIENT)
         private List toolTips;
@@ -258,7 +279,14 @@ public static class SimplePredicate {
 
         public int previewCount = -1;
 
+        @Deprecated
         public SimplePredicate(Predicate predicate, Supplier candidates) {
+            // legacy compat
+            this.predicate = (state, info) -> predicate.test(state);
+            this.candidates = candidates;
+        }
+
+        public SimplePredicate(BiPredicate predicate, Supplier candidates) {
             this.predicate = predicate;
             this.candidates = candidates;
         }
@@ -298,39 +326,35 @@ public List getToolTips(TraceabilityPredicate predicates) {
             return result;
         }
 
-        public boolean test(BlockWorldState blockWorldState) {
-            return predicate.test(blockWorldState);
+        public boolean test(BlockWorldState state, StructureInfo info) {
+            return predicate.test(state, info);
         }
 
-        public boolean testLimited(BlockWorldState blockWorldState) {
-            return testGlobal(blockWorldState) && testLayer(blockWorldState);
+        public boolean testLimited(BlockWorldState blockWorldState, StructureInfo info, Object2IntMap globalCache, Object2IntMap layerCache) {
+            return testGlobal(blockWorldState, info, globalCache) && testLayer(blockWorldState, info, layerCache);
         }
 
-        public SinglePredicateError testGlobal(BlockWorldState blockWorldState) {
+        public boolean testGlobal(BlockWorldState blockWorldState, StructureInfo info,Object2IntMap cache) {
             if (minGlobalCount == -1 && maxGlobalCount == -1) return true;
-            Integer count = blockWorldState.globalCount.get(this);
-            boolean base = predicate.test(blockWorldState);
-            count = (count == null ? 0 : count) + (base ? 1 : 0);
-            blockWorldState.globalCount.put(this, count);
+
+            boolean base = predicate.test(blockWorldState, info);
+            int count = cache.getInt(this);
+            count += base ? 1 : 0;
+            cache.put(this, count);
             if (maxGlobalCount == -1 || count <= maxGlobalCount) return base;
-            blockWorldState.setError(new SinglePredicateError(this, 0));
+            info.setError(new SinglePredicateError(this, 0));
             return false;
         }
 
-        /**
-         *
-         * @param blockWorldState
-         * @return
-         */
-        public SinglePredicateError testLayer(Object2IntMap cache) {
-            if (minLayerCount == -1 && maxLayerCount == -1) return null;
-
-            int count = cache.get(this);
-            boolean base = predicate.test(blockWorldState);
-            count = (count == null ? 0 : count) + (base ? 1 : 0);
-            blockWorldState.layerCount.put(this, count);
+        public boolean testLayer(BlockWorldState blockWorldState, StructureInfo info,Object2IntMap cache) {
+            if (minLayerCount == -1 && maxLayerCount == -1) return true;
+
+            boolean base = predicate.test(blockWorldState, info);
+            int count = cache.getInt(this);
+            count += base ? 1 : 0;
+            cache.put(this, count);
             if (maxLayerCount == -1 || count <= maxLayerCount) return base;
-            blockWorldState.setError(new SinglePredicateError(this, 2));
+            info.setError(new SinglePredicateError(this, 2));
             return false;
         }
 
@@ -352,27 +376,24 @@ public List getCandidates() {
 
     public static class SinglePredicateError extends PatternError {
 
-        public final SimplePredicate predicate;
-        public final int type;
+        public final int type, number;
 
-        public SinglePredicateError(SimplePredicate predicate, int type) {
-            this.predicate = predicate;
+        public SinglePredicateError(SimplePredicate failingPredicate, int type) {
+            super(null, Collections.singletonList(failingPredicate.getCandidates()));
             this.type = type;
-        }
 
-        @Override
-        public List> getCandidates() {
-            return Collections.singletonList(predicate.getCandidates());
+            int number = -1;
+            if (type == 0) number = failingPredicate.maxGlobalCount;
+            if (type == 1) number = failingPredicate.minGlobalCount;
+            if (type == 2) number = failingPredicate.maxLayerCount;
+            if (type == 3) number = failingPredicate.minLayerCount;
+
+            this.number = number;
         }
 
         @SideOnly(Side.CLIENT)
         @Override
         public String getErrorInfo() {
-            int number = -1;
-            if (type == 0) number = predicate.maxGlobalCount;
-            if (type == 1) number = predicate.minGlobalCount;
-            if (type == 2) number = predicate.maxLayerCount;
-            if (type == 3) number = predicate.minLayerCount;
             return I18n.format("gregtech.multiblock.pattern.error.limited." + type, number);
         }
     }
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index b746f67a2cd..6e100aeadeb 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -7,6 +7,7 @@
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
+import gregtech.api.pattern.StructureInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTUtility;
@@ -352,18 +353,22 @@ public void drawInfo(@NotNull Minecraft minecraft, int recipeWidth, int recipeHe
             TraceabilityPredicate predicates = patterns[currentRendererPage].predicateMap
                     .get(rayTraceResult.getBlockPos());
             if (predicates != null) {
-                BlockWorldState worldState = new BlockWorldState();
-                worldState.update(renderer.world, rayTraceResult.getBlockPos(), new PatternMatchContext(),
-                        new HashMap<>(), new HashMap<>(), predicates);
+
+                StructureInfo info = new StructureInfo(new PatternMatchContext(), null);
+                BlockWorldState worldState = new BlockWorldState(info);
+
+                worldState.setWorld(renderer.world);
+                worldState.setPos(rayTraceResult.getBlockPos());
+
                 for (TraceabilityPredicate.SimplePredicate common : predicates.common) {
-                    if (common.test(worldState)) {
+                    if (common.test(worldState, info)) {
                         predicateTips = common.getToolTips(predicates);
                         break;
                     }
                 }
                 if (predicateTips == null) {
                     for (TraceabilityPredicate.SimplePredicate limit : predicates.limited) {
-                        if (limit.test(worldState)) {
+                        if (limit.test(worldState, info)) {
                             predicateTips = limit.getToolTips(predicates);
                             break;
                         }

From 4ba450319b6888c555949c5dcc7c6ee66316b7ca Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 14 Jul 2024 14:12:32 -0700
Subject: [PATCH 03/64] spotless n stuff

---
 .../multiblock/MultiblockControllerBase.java  |  16 +-
 .../gregtech/api/pattern/BlockPattern.java    | 864 +++++++++---------
 .../gregtech/api/pattern/BlockWorldState.java |   4 +-
 .../api/pattern/FactoryBlockPattern.java      |  34 +-
 .../gregtech/api/pattern/GreggyBlockPos.java  |   1 +
 .../gregtech/api/pattern/PatternAisle.java    |   9 +-
 .../gregtech/api/pattern/PatternError.java    |   2 -
 .../gregtech/api/pattern/StructureInfo.java   |   2 +
 .../api/pattern/TraceabilityPredicate.java    |  61 +-
 9 files changed, 514 insertions(+), 479 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index fb097c8d9b0..56d77842a27 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -552,14 +552,14 @@ public boolean allowsFlip() {
 
     public List getMatchingShapes() {
         return Collections.emptyList();
-//        if (this.structurePattern == null) {
-//            this.reinitializeStructurePattern();
-//            if (this.structurePattern == null) {
-//                return Collections.emptyList();
-//            }
-//        }
-//        int[][] aisleRepetitions = this.structurePattern.aisleRepetitions;
-//        return repetitionDFS(new ArrayList<>(), aisleRepetitions, new Stack<>());
+        // if (this.structurePattern == null) {
+        // this.reinitializeStructurePattern();
+        // if (this.structurePattern == null) {
+        // return Collections.emptyList();
+        // }
+        // }
+        // int[][] aisleRepetitions = this.structurePattern.aisleRepetitions;
+        // return repetitionDFS(new ArrayList<>(), aisleRepetitions, new Stack<>());
     }
 
     private List repetitionDFS(List pages, int[][] aisleRepetitions,
diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java
index 7583eb8fee4..2c7eb980548 100644
--- a/src/main/java/gregtech/api/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/BlockPattern.java
@@ -1,44 +1,25 @@
 package gregtech.api.pattern;
 
-import gregtech.api.GregTechAPI;
-import gregtech.api.metatileentity.MetaTileEntity;
-import gregtech.api.metatileentity.MetaTileEntityHolder;
-import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.metatileentity.registry.MTERegistry;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.RelativeDirection;
 
-import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
-
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-
-import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
-
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.entity.player.EntityPlayer;
-import net.minecraft.init.Blocks;
-import net.minecraft.item.Item;
-import net.minecraft.item.ItemBlock;
-import net.minecraft.item.ItemStack;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
 
+import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
-import org.apache.commons.lang3.ArrayUtils;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.lang.reflect.Array;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
 
 public class BlockPattern {
 
@@ -56,7 +37,8 @@ public class BlockPattern {
     protected final int[] dimensions;
 
     /**
-     * In the form of [ aisleOffset, stringOffset, charOffset ] where the offsets are the opposite {@link RelativeDirection} of structure directions
+     * In the form of [ aisleOffset, stringOffset, charOffset ] where the offsets are the opposite
+     * {@link RelativeDirection} of structure directions
      */
     protected final int[] startOffset;
     protected final PatternAisle[] aisles;
@@ -75,15 +57,21 @@ public class BlockPattern {
     public int[] formedRepetitionCount;
 
     // how many not nulls to keep someone from not passing in null?
-    public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] dimensions, @NotNull RelativeDirection @NotNull [] directions,
-                        int @Nullable [] startOffset, @NotNull Char2ObjectMap predicates, char centerChar) {
+    public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] dimensions,
+                        @NotNull RelativeDirection @NotNull [] directions,
+                        int @Nullable [] startOffset, @NotNull Char2ObjectMap predicates,
+                        char centerChar) {
         this.aisles = aisles;
         this.dimensions = dimensions;
         this.structureDir = directions;
         this.predicates = predicates;
-        this.startOffset = startOffset;
 
-        if (this.startOffset == null) legacyStartOffset(centerChar);
+        if (startOffset == null) {
+            this.startOffset = new int[3];
+            legacyStartOffset(centerChar);
+        } else {
+            this.startOffset = startOffset;
+        }
 
         this.info = new StructureInfo(new PatternMatchContext(), null);
         this.worldState = new BlockWorldState(info);
@@ -91,6 +79,7 @@ public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] di
 
     /**
      * For legacy compat only,
+     * 
      * @param center The center char to look for
      */
     private void legacyStartOffset(char center) {
@@ -100,7 +89,7 @@ private void legacyStartOffset(char center) {
             if (result != null) {
                 startOffset[0] = aisleI;
                 startOffset[1] = result[0];
-                startOffset[2] = result[2];
+                startOffset[2] = result[1];
                 return;
             }
         }
@@ -151,7 +140,7 @@ public void clearCache() {
     }
 
     private boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
-                                               EnumFacing upwardsFacing, boolean isFlipped) {
+                                   EnumFacing upwardsFacing, boolean isFlipped) {
         this.matchContext.reset();
         this.globalCount.clear();
         this.layerCount.clear();
@@ -166,17 +155,23 @@ private boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing front
             PatternAisle aisle = aisles[aisleI];
 
             for (int repeats = aisle.minRepeats; repeats <= aisle.maxRepeats; repeats++) {
-                boolean aisleResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI, aisleOffset + repeats, isFlipped);
+                boolean aisleResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI,
+                        aisleOffset + repeats, isFlipped);
 
-                // since aisle repetitions goes up, if this amount of repetitions are invalid, and all smaller repetitions
-                // have already been checked, then all larger repetitions will also be invalid(since they must also contain
+                // since aisle repetitions goes up, if this amount of repetitions are invalid, and all smaller
+                // repetitions
+                // have already been checked, then all larger repetitions will also be invalid(since they must also
+                // contain
                 // the problematic section), so the pattern cannot be correct
                 if (!aisleResult) return false;
 
-                // if this isn't the last aisle, then check the next aisle after to see if this repetition matches with that
-                // if not, then this repetition is invalid. This only checks the first aisle even if it has a min repeat > 1, but yeah
+                // if this isn't the last aisle, then check the next aisle after to see if this repetition matches with
+                // that
+                // if not, then this repetition is invalid. This only checks the first aisle even if it has a min repeat
+                // > 1, but yeah
                 if (aisleI != aisles.length - 1) {
-                    boolean nextResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI + 1, aisleOffset + repeats + 1, isFlipped);
+                    boolean nextResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI + 1,
+                            aisleOffset + repeats + 1, isFlipped);
 
                     // if the next aisle is also valid, then move on
                     if (nextResult) {
@@ -194,15 +189,19 @@ private boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing front
 
     /**
      * Checks a specific aisle for validity
+     * 
      * @param controllerPos The position of the controller
-     * @param frontFacing The front facing of the controller
-     * @param upFacing The up facing of the controller
-     * @param aisleIndex The index of the aisle, this is where the pattern is gotten from, treats repeatable aisles as only 1
-     * @param aisleOffset The offset of the aisle, how much offset in aisleDir to check the blocks in world, for example, if the first aisle is repeated 2 times, aisleIndex is 1 while this is 2
-     * @param flip Whether to flip or not
+     * @param frontFacing   The front facing of the controller
+     * @param upFacing      The up facing of the controller
+     * @param aisleIndex    The index of the aisle, this is where the pattern is gotten from, treats repeatable aisles
+     *                      as only 1
+     * @param aisleOffset   The offset of the aisle, how much offset in aisleDir to check the blocks in world, for
+     *                      example, if the first aisle is repeated 2 times, aisleIndex is 1 while this is 2
+     * @param flip          Whether to flip or not
      * @return True if the check passed, otherwise the {@link StructureInfo} would have been updated with an error
      */
-    public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing, int aisleIndex, int aisleOffset, boolean flip) {
+    public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing, int aisleIndex,
+                              int aisleOffset, boolean flip) {
         // absolute facings from the relative facings
         EnumFacing absoluteAisle = structureDir[0].getRelativeFacing(frontFacing, upFacing, flip);
         EnumFacing absoluteString = structureDir[1].getRelativeFacing(frontFacing, upFacing, flip);
@@ -245,399 +244,402 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
     }
 
     public void autoBuild(EntityPlayer player, MultiblockControllerBase controllerBase) {
-//        World world = player.world;
-//        BlockWorldState worldState = new BlockWorldState();
-//        int minZ = -centerOffset[4];
-//        EnumFacing facing = controllerBase.getFrontFacing().getOpposite();
-//        BlockPos centerPos = controllerBase.getPos();
-//        Map cacheInfos = new HashMap<>();
-//        Map cacheGlobal = new HashMap<>();
-//        Map blocks = new HashMap<>();
-//        blocks.put(controllerBase.getPos(), controllerBase);
-//        for (int c = 0, z = minZ++, r; c < this.fingerLength; c++) {
-//            for (r = 0; r < aisleRepetitions[c][0]; r++) {
-//                Map cacheLayer = new HashMap<>();
-//                for (int b = 0, y = -centerOffset[1]; b < this.thumbLength; b++, y++) {
-//                    for (int a = 0, x = -centerOffset[0]; a < this.palmLength; a++, x++) {
-//                        TraceabilityPredicate predicate = this.blockMatches[c][b][a];
-//                        BlockPos pos = setActualRelativeOffset(x, y, z, facing, controllerBase.getUpwardsFacing(),
-//                                controllerBase.isFlipped())
-//                                        .add(centerPos.getX(), centerPos.getY(), centerPos.getZ());
-//                        worldState.update(world, pos, matchContext, globalCount, layerCount, predicate);
-//                        if (!world.getBlockState(pos).getMaterial().isReplaceable()) {
-//                            blocks.put(pos, world.getBlockState(pos));
-//                            for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-//                                limit.testLimited(worldState);
-//                            }
-//                        } else {
-//                            boolean find = false;
-//                            BlockInfo[] infos = new BlockInfo[0];
-//                            for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-//                                if (limit.minLayerCount > 0) {
-//                                    if (!cacheLayer.containsKey(limit)) {
-//                                        cacheLayer.put(limit, 1);
-//                                    } else
-//                                        if (cacheLayer.get(limit) < limit.minLayerCount && (limit.maxLayerCount == -1 ||
-//                                                cacheLayer.get(limit) < limit.maxLayerCount)) {
-//                                                    cacheLayer.put(limit, cacheLayer.get(limit) + 1);
-//                                                } else {
-//                                                    continue;
-//                                                }
-//                                } else {
-//                                    continue;
-//                                }
-//                                if (!cacheInfos.containsKey(limit)) {
-//                                    cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-//                                }
-//                                infos = cacheInfos.get(limit);
-//                                find = true;
-//                                break;
-//                            }
-//                            if (!find) {
-//                                for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-//                                    if (limit.minGlobalCount > 0) {
-//                                        if (!cacheGlobal.containsKey(limit)) {
-//                                            cacheGlobal.put(limit, 1);
-//                                        } else if (cacheGlobal.get(limit) < limit.minGlobalCount &&
-//                                                (limit.maxGlobalCount == -1 ||
-//                                                        cacheGlobal.get(limit) < limit.maxGlobalCount)) {
-//                                                            cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-//                                                        } else {
-//                                                            continue;
-//                                                        }
-//                                    } else {
-//                                        continue;
-//                                    }
-//                                    if (!cacheInfos.containsKey(limit)) {
-//                                        cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-//                                    }
-//                                    infos = cacheInfos.get(limit);
-//                                    find = true;
-//                                    break;
-//                                }
-//                            }
-//                            if (!find) { // no limited
-//                                for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-//                                    if (limit.maxLayerCount != -1 &&
-//                                            cacheLayer.getOrDefault(limit, Integer.MAX_VALUE) == limit.maxLayerCount)
-//                                        continue;
-//                                    if (limit.maxGlobalCount != -1 &&
-//                                            cacheGlobal.getOrDefault(limit, Integer.MAX_VALUE) == limit.maxGlobalCount)
-//                                        continue;
-//                                    if (!cacheInfos.containsKey(limit)) {
-//                                        cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-//                                    }
-//                                    if (cacheLayer.containsKey(limit)) {
-//                                        cacheLayer.put(limit, cacheLayer.get(limit) + 1);
-//                                    } else {
-//                                        cacheLayer.put(limit, 1);
-//                                    }
-//                                    if (cacheGlobal.containsKey(limit)) {
-//                                        cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-//                                    } else {
-//                                        cacheGlobal.put(limit, 1);
-//                                    }
-//                                    infos = ArrayUtils.addAll(infos, cacheInfos.get(limit));
-//                                }
-//                                for (TraceabilityPredicate.SimplePredicate common : predicate.common) {
-//                                    if (!cacheInfos.containsKey(common)) {
-//                                        cacheInfos.put(common,
-//                                                common.candidates == null ? null : common.candidates.get());
-//                                    }
-//                                    infos = ArrayUtils.addAll(infos, cacheInfos.get(common));
-//                                }
-//                            }
-//
-//                            List candidates = Arrays.stream(infos)
-//                                    .filter(info -> info.getBlockState().getBlock() != Blocks.AIR).map(info -> {
-//                                        IBlockState blockState = info.getBlockState();
-//                                        MetaTileEntity metaTileEntity = info
-//                                                .getTileEntity() instanceof IGregTechTileEntity ?
-//                                                        ((IGregTechTileEntity) info.getTileEntity())
-//                                                                .getMetaTileEntity() :
-//                                                        null;
-//                                        if (metaTileEntity != null) {
-//                                            return metaTileEntity.getStackForm();
-//                                        } else {
-//                                            return new ItemStack(Item.getItemFromBlock(blockState.getBlock()), 1,
-//                                                    blockState.getBlock().damageDropped(blockState));
-//                                        }
-//                                    }).collect(Collectors.toList());
-//                            if (candidates.isEmpty()) continue;
-//                            // check inventory
-//                            ItemStack found = null;
-//                            if (!player.isCreative()) {
-//                                for (ItemStack itemStack : player.inventory.mainInventory) {
-//                                    if (candidates.stream().anyMatch(candidate -> candidate.isItemEqual(itemStack)) &&
-//                                            !itemStack.isEmpty() && itemStack.getItem() instanceof ItemBlock) {
-//                                        found = itemStack.copy();
-//                                        itemStack.setCount(itemStack.getCount() - 1);
-//                                        break;
-//                                    }
-//                                }
-//                                if (found == null) continue;
-//                            } else {
-//                                for (int i = candidates.size() - 1; i >= 0; i--) {
-//                                    found = candidates.get(i).copy();
-//                                    if (!found.isEmpty() && found.getItem() instanceof ItemBlock) {
-//                                        break;
-//                                    }
-//                                    found = null;
-//                                }
-//                                if (found == null) continue;
-//                            }
-//                            ItemBlock itemBlock = (ItemBlock) found.getItem();
-//                            IBlockState state = itemBlock.getBlock()
-//                                    .getStateFromMeta(itemBlock.getMetadata(found.getMetadata()));
-//                            blocks.put(pos, state);
-//                            world.setBlockState(pos, state);
-//                            TileEntity holder = world.getTileEntity(pos);
-//                            if (holder instanceof IGregTechTileEntity igtte) {
-//                                MTERegistry registry = GregTechAPI.mteManager
-//                                        .getRegistry(found.getItem().getRegistryName().getNamespace());
-//                                MetaTileEntity sampleMetaTileEntity = registry.getObjectById(found.getItemDamage());
-//                                if (sampleMetaTileEntity != null) {
-//                                    MetaTileEntity metaTileEntity = igtte.setMetaTileEntity(sampleMetaTileEntity);
-//                                    metaTileEntity.onPlacement();
-//                                    blocks.put(pos, metaTileEntity);
-//                                    if (found.getTagCompound() != null) {
-//                                        metaTileEntity.initFromItemStackData(found.getTagCompound());
-//                                    }
-//                                }
-//                            }
-//                        }
-//                    }
-//                }
-//                z++;
-//            }
-//        }
-//        EnumFacing[] facings = ArrayUtils.addAll(new EnumFacing[] { controllerBase.getFrontFacing() }, FACINGS); // follow
-//                                                                                                                 // controller
-//                                                                                                                 // first
-//        blocks.forEach((pos, block) -> { // adjust facing
-//            if (block instanceof MetaTileEntity) {
-//                MetaTileEntity metaTileEntity = (MetaTileEntity) block;
-//                boolean find = false;
-//                for (EnumFacing enumFacing : facings) {
-//                    if (metaTileEntity.isValidFrontFacing(enumFacing)) {
-//                        if (!blocks.containsKey(pos.offset(enumFacing))) {
-//                            metaTileEntity.setFrontFacing(enumFacing);
-//                            find = true;
-//                            break;
-//                        }
-//                    }
-//                }
-//                if (!find) {
-//                    for (EnumFacing enumFacing : FACINGS) {
-//                        if (world.isAirBlock(pos.offset(enumFacing)) && metaTileEntity.isValidFrontFacing(enumFacing)) {
-//                            metaTileEntity.setFrontFacing(enumFacing);
-//                            break;
-//                        }
-//                    }
-//                }
-//            }
-//        });
+        // World world = player.world;
+        // BlockWorldState worldState = new BlockWorldState();
+        // int minZ = -centerOffset[4];
+        // EnumFacing facing = controllerBase.getFrontFacing().getOpposite();
+        // BlockPos centerPos = controllerBase.getPos();
+        // Map cacheInfos = new HashMap<>();
+        // Map cacheGlobal = new HashMap<>();
+        // Map blocks = new HashMap<>();
+        // blocks.put(controllerBase.getPos(), controllerBase);
+        // for (int c = 0, z = minZ++, r; c < this.fingerLength; c++) {
+        // for (r = 0; r < aisleRepetitions[c][0]; r++) {
+        // Map cacheLayer = new HashMap<>();
+        // for (int b = 0, y = -centerOffset[1]; b < this.thumbLength; b++, y++) {
+        // for (int a = 0, x = -centerOffset[0]; a < this.palmLength; a++, x++) {
+        // TraceabilityPredicate predicate = this.blockMatches[c][b][a];
+        // BlockPos pos = setActualRelativeOffset(x, y, z, facing, controllerBase.getUpwardsFacing(),
+        // controllerBase.isFlipped())
+        // .add(centerPos.getX(), centerPos.getY(), centerPos.getZ());
+        // worldState.update(world, pos, matchContext, globalCount, layerCount, predicate);
+        // if (!world.getBlockState(pos).getMaterial().isReplaceable()) {
+        // blocks.put(pos, world.getBlockState(pos));
+        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
+        // limit.testLimited(worldState);
+        // }
+        // } else {
+        // boolean find = false;
+        // BlockInfo[] infos = new BlockInfo[0];
+        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
+        // if (limit.minLayerCount > 0) {
+        // if (!cacheLayer.containsKey(limit)) {
+        // cacheLayer.put(limit, 1);
+        // } else
+        // if (cacheLayer.get(limit) < limit.minLayerCount && (limit.maxLayerCount == -1 ||
+        // cacheLayer.get(limit) < limit.maxLayerCount)) {
+        // cacheLayer.put(limit, cacheLayer.get(limit) + 1);
+        // } else {
+        // continue;
+        // }
+        // } else {
+        // continue;
+        // }
+        // if (!cacheInfos.containsKey(limit)) {
+        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
+        // }
+        // infos = cacheInfos.get(limit);
+        // find = true;
+        // break;
+        // }
+        // if (!find) {
+        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
+        // if (limit.minGlobalCount > 0) {
+        // if (!cacheGlobal.containsKey(limit)) {
+        // cacheGlobal.put(limit, 1);
+        // } else if (cacheGlobal.get(limit) < limit.minGlobalCount &&
+        // (limit.maxGlobalCount == -1 ||
+        // cacheGlobal.get(limit) < limit.maxGlobalCount)) {
+        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
+        // } else {
+        // continue;
+        // }
+        // } else {
+        // continue;
+        // }
+        // if (!cacheInfos.containsKey(limit)) {
+        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
+        // }
+        // infos = cacheInfos.get(limit);
+        // find = true;
+        // break;
+        // }
+        // }
+        // if (!find) { // no limited
+        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
+        // if (limit.maxLayerCount != -1 &&
+        // cacheLayer.getOrDefault(limit, Integer.MAX_VALUE) == limit.maxLayerCount)
+        // continue;
+        // if (limit.maxGlobalCount != -1 &&
+        // cacheGlobal.getOrDefault(limit, Integer.MAX_VALUE) == limit.maxGlobalCount)
+        // continue;
+        // if (!cacheInfos.containsKey(limit)) {
+        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
+        // }
+        // if (cacheLayer.containsKey(limit)) {
+        // cacheLayer.put(limit, cacheLayer.get(limit) + 1);
+        // } else {
+        // cacheLayer.put(limit, 1);
+        // }
+        // if (cacheGlobal.containsKey(limit)) {
+        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
+        // } else {
+        // cacheGlobal.put(limit, 1);
+        // }
+        // infos = ArrayUtils.addAll(infos, cacheInfos.get(limit));
+        // }
+        // for (TraceabilityPredicate.SimplePredicate common : predicate.common) {
+        // if (!cacheInfos.containsKey(common)) {
+        // cacheInfos.put(common,
+        // common.candidates == null ? null : common.candidates.get());
+        // }
+        // infos = ArrayUtils.addAll(infos, cacheInfos.get(common));
+        // }
+        // }
+        //
+        // List candidates = Arrays.stream(infos)
+        // .filter(info -> info.getBlockState().getBlock() != Blocks.AIR).map(info -> {
+        // IBlockState blockState = info.getBlockState();
+        // MetaTileEntity metaTileEntity = info
+        // .getTileEntity() instanceof IGregTechTileEntity ?
+        // ((IGregTechTileEntity) info.getTileEntity())
+        // .getMetaTileEntity() :
+        // null;
+        // if (metaTileEntity != null) {
+        // return metaTileEntity.getStackForm();
+        // } else {
+        // return new ItemStack(Item.getItemFromBlock(blockState.getBlock()), 1,
+        // blockState.getBlock().damageDropped(blockState));
+        // }
+        // }).collect(Collectors.toList());
+        // if (candidates.isEmpty()) continue;
+        // // check inventory
+        // ItemStack found = null;
+        // if (!player.isCreative()) {
+        // for (ItemStack itemStack : player.inventory.mainInventory) {
+        // if (candidates.stream().anyMatch(candidate -> candidate.isItemEqual(itemStack)) &&
+        // !itemStack.isEmpty() && itemStack.getItem() instanceof ItemBlock) {
+        // found = itemStack.copy();
+        // itemStack.setCount(itemStack.getCount() - 1);
+        // break;
+        // }
+        // }
+        // if (found == null) continue;
+        // } else {
+        // for (int i = candidates.size() - 1; i >= 0; i--) {
+        // found = candidates.get(i).copy();
+        // if (!found.isEmpty() && found.getItem() instanceof ItemBlock) {
+        // break;
+        // }
+        // found = null;
+        // }
+        // if (found == null) continue;
+        // }
+        // ItemBlock itemBlock = (ItemBlock) found.getItem();
+        // IBlockState state = itemBlock.getBlock()
+        // .getStateFromMeta(itemBlock.getMetadata(found.getMetadata()));
+        // blocks.put(pos, state);
+        // world.setBlockState(pos, state);
+        // TileEntity holder = world.getTileEntity(pos);
+        // if (holder instanceof IGregTechTileEntity igtte) {
+        // MTERegistry registry = GregTechAPI.mteManager
+        // .getRegistry(found.getItem().getRegistryName().getNamespace());
+        // MetaTileEntity sampleMetaTileEntity = registry.getObjectById(found.getItemDamage());
+        // if (sampleMetaTileEntity != null) {
+        // MetaTileEntity metaTileEntity = igtte.setMetaTileEntity(sampleMetaTileEntity);
+        // metaTileEntity.onPlacement();
+        // blocks.put(pos, metaTileEntity);
+        // if (found.getTagCompound() != null) {
+        // metaTileEntity.initFromItemStackData(found.getTagCompound());
+        // }
+        // }
+        // }
+        // }
+        // }
+        // }
+        // z++;
+        // }
+        // }
+        // EnumFacing[] facings = ArrayUtils.addAll(new EnumFacing[] { controllerBase.getFrontFacing() }, FACINGS); //
+        // follow
+        // // controller
+        // // first
+        // blocks.forEach((pos, block) -> { // adjust facing
+        // if (block instanceof MetaTileEntity) {
+        // MetaTileEntity metaTileEntity = (MetaTileEntity) block;
+        // boolean find = false;
+        // for (EnumFacing enumFacing : facings) {
+        // if (metaTileEntity.isValidFrontFacing(enumFacing)) {
+        // if (!blocks.containsKey(pos.offset(enumFacing))) {
+        // metaTileEntity.setFrontFacing(enumFacing);
+        // find = true;
+        // break;
+        // }
+        // }
+        // }
+        // if (!find) {
+        // for (EnumFacing enumFacing : FACINGS) {
+        // if (world.isAirBlock(pos.offset(enumFacing)) && metaTileEntity.isValidFrontFacing(enumFacing)) {
+        // metaTileEntity.setFrontFacing(enumFacing);
+        // break;
+        // }
+        // }
+        // }
+        // }
+        // });
     }
 
     public BlockInfo[][][] getPreview(int[] repetition) {
         return null;
-//        Map cacheInfos = new HashMap<>();
-//        Map cacheGlobal = new HashMap<>();
-//        Map blocks = new HashMap<>();
-//        int minX = Integer.MAX_VALUE;
-//        int minY = Integer.MAX_VALUE;
-//        int minZ = Integer.MAX_VALUE;
-//        int maxX = Integer.MIN_VALUE;
-//        int maxY = Integer.MIN_VALUE;
-//        int maxZ = Integer.MIN_VALUE;
-//        for (int l = 0, x = 0; l < this.fingerLength; l++) {
-//            for (int r = 0; r < repetition[l]; r++) {
-//                // Checking single slice
-//                Map cacheLayer = new HashMap<>();
-//                for (int y = 0; y < this.thumbLength; y++) {
-//                    for (int z = 0; z < this.palmLength; z++) {
-//                        TraceabilityPredicate predicate = this.blockMatches[l][y][z];
-//                        boolean find = false;
-//                        BlockInfo[] infos = null;
-//                        for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { // check layer and
-//                                                                                                // previewCount
-//                            if (limit.minLayerCount > 0) {
-//                                if (!cacheLayer.containsKey(limit)) {
-//                                    cacheLayer.put(limit, 1);
-//                                } else if (cacheLayer.get(limit) < limit.minLayerCount) {
-//                                    cacheLayer.put(limit, cacheLayer.get(limit) + 1);
-//                                } else {
-//                                    continue;
-//                                }
-//                                if (cacheGlobal.getOrDefault(limit, 0) < limit.previewCount) {
-//                                    if (!cacheGlobal.containsKey(limit)) {
-//                                        cacheGlobal.put(limit, 1);
-//                                    } else if (cacheGlobal.get(limit) < limit.previewCount) {
-//                                        cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-//                                    } else {
-//                                        continue;
-//                                    }
-//                                }
-//                            } else {
-//                                continue;
-//                            }
-//                            if (!cacheInfos.containsKey(limit)) {
-//                                cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-//                            }
-//                            infos = cacheInfos.get(limit);
-//                            find = true;
-//                            break;
-//                        }
-//                        if (!find) { // check global and previewCount
-//                            for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-//                                if (limit.minGlobalCount == -1 && limit.previewCount == -1) continue;
-//                                if (cacheGlobal.getOrDefault(limit, 0) < limit.previewCount) {
-//                                    if (!cacheGlobal.containsKey(limit)) {
-//                                        cacheGlobal.put(limit, 1);
-//                                    } else if (cacheGlobal.get(limit) < limit.previewCount) {
-//                                        cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-//                                    } else {
-//                                        continue;
-//                                    }
-//                                } else if (limit.minGlobalCount > 0) {
-//                                    if (!cacheGlobal.containsKey(limit)) {
-//                                        cacheGlobal.put(limit, 1);
-//                                    } else if (cacheGlobal.get(limit) < limit.minGlobalCount) {
-//                                        cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-//                                    } else {
-//                                        continue;
-//                                    }
-//                                } else {
-//                                    continue;
-//                                }
-//                                if (!cacheInfos.containsKey(limit)) {
-//                                    cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-//                                }
-//                                infos = cacheInfos.get(limit);
-//                                find = true;
-//                                break;
-//                            }
-//                        }
-//                        if (!find) { // check common with previewCount
-//                            for (TraceabilityPredicate.SimplePredicate common : predicate.common) {
-//                                if (common.previewCount > 0) {
-//                                    if (!cacheGlobal.containsKey(common)) {
-//                                        cacheGlobal.put(common, 1);
-//                                    } else if (cacheGlobal.get(common) < common.previewCount) {
-//                                        cacheGlobal.put(common, cacheGlobal.get(common) + 1);
-//                                    } else {
-//                                        continue;
-//                                    }
-//                                } else {
-//                                    continue;
-//                                }
-//                                if (!cacheInfos.containsKey(common)) {
-//                                    cacheInfos.put(common, common.candidates == null ? null : common.candidates.get());
-//                                }
-//                                infos = cacheInfos.get(common);
-//                                find = true;
-//                                break;
-//                            }
-//                        }
-//                        if (!find) { // check without previewCount
-//                            for (TraceabilityPredicate.SimplePredicate common : predicate.common) {
-//                                if (common.previewCount == -1) {
-//                                    if (!cacheInfos.containsKey(common)) {
-//                                        cacheInfos.put(common,
-//                                                common.candidates == null ? null : common.candidates.get());
-//                                    }
-//                                    infos = cacheInfos.get(common);
-//                                    find = true;
-//                                    break;
-//                                }
-//                            }
-//                        }
-//                        if (!find) { // check max
-//                            for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-//                                if (limit.previewCount != -1) {
-//                                    continue;
-//                                } else if (limit.maxGlobalCount != -1 || limit.maxLayerCount != -1) {
-//                                    if (cacheGlobal.getOrDefault(limit, 0) < limit.maxGlobalCount) {
-//                                        if (!cacheGlobal.containsKey(limit)) {
-//                                            cacheGlobal.put(limit, 1);
-//                                        } else {
-//                                            cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-//                                        }
-//                                    } else if (cacheLayer.getOrDefault(limit, 0) < limit.maxLayerCount) {
-//                                        if (!cacheLayer.containsKey(limit)) {
-//                                            cacheLayer.put(limit, 1);
-//                                        } else {
-//                                            cacheLayer.put(limit, cacheLayer.get(limit) + 1);
-//                                        }
-//                                    } else {
-//                                        continue;
-//                                    }
-//                                }
-//
-//                                if (!cacheInfos.containsKey(limit)) {
-//                                    cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-//                                }
-//                                infos = cacheInfos.get(limit);
-//                                break;
-//                            }
-//                        }
-//                        BlockInfo info = infos == null || infos.length == 0 ? BlockInfo.EMPTY : infos[0];
-//                        BlockPos pos = setActualRelativeOffset(z, y, x, EnumFacing.NORTH, EnumFacing.UP, false);
-//                        // TODO
-//                        if (info.getTileEntity() instanceof MetaTileEntityHolder) {
-//                            MetaTileEntityHolder holder = new MetaTileEntityHolder();
-//                            holder.setMetaTileEntity(((MetaTileEntityHolder) info.getTileEntity()).getMetaTileEntity());
-//                            holder.getMetaTileEntity().onPlacement();
-//                            info = new BlockInfo(holder.getMetaTileEntity().getBlock().getDefaultState(), holder);
-//                        }
-//                        blocks.put(pos, info);
-//                        minX = Math.min(pos.getX(), minX);
-//                        minY = Math.min(pos.getY(), minY);
-//                        minZ = Math.min(pos.getZ(), minZ);
-//                        maxX = Math.max(pos.getX(), maxX);
-//                        maxY = Math.max(pos.getY(), maxY);
-//                        maxZ = Math.max(pos.getZ(), maxZ);
-//                    }
-//                }
-//                x++;
-//            }
-//        }
-//        BlockInfo[][][] result = (BlockInfo[][][]) Array.newInstance(BlockInfo.class, maxX - minX + 1, maxY - minY + 1,
-//                maxZ - minZ + 1);
-//        int finalMinX = minX;
-//        int finalMinY = minY;
-//        int finalMinZ = minZ;
-//        blocks.forEach((pos, info) -> {
-//            if (info.getTileEntity() instanceof MetaTileEntityHolder) {
-//                MetaTileEntity metaTileEntity = ((MetaTileEntityHolder) info.getTileEntity()).getMetaTileEntity();
-//                boolean find = false;
-//                for (EnumFacing enumFacing : FACINGS) {
-//                    if (metaTileEntity.isValidFrontFacing(enumFacing)) {
-//                        if (!blocks.containsKey(pos.offset(enumFacing))) {
-//                            metaTileEntity.setFrontFacing(enumFacing);
-//                            find = true;
-//                            break;
-//                        }
-//                    }
-//                }
-//                if (!find) {
-//                    for (EnumFacing enumFacing : FACINGS) {
-//                        BlockInfo blockInfo = blocks.get(pos.offset(enumFacing));
-//                        if (blockInfo != null && blockInfo.getBlockState().getBlock() == Blocks.AIR &&
-//                                metaTileEntity.isValidFrontFacing(enumFacing)) {
-//                            metaTileEntity.setFrontFacing(enumFacing);
-//                            break;
-//                        }
-//                    }
-//                }
-//            }
-//            result[pos.getX() - finalMinX][pos.getY() - finalMinY][pos.getZ() - finalMinZ] = info;
-//        });
-//        return result;
+        // Map cacheInfos = new HashMap<>();
+        // Map cacheGlobal = new HashMap<>();
+        // Map blocks = new HashMap<>();
+        // int minX = Integer.MAX_VALUE;
+        // int minY = Integer.MAX_VALUE;
+        // int minZ = Integer.MAX_VALUE;
+        // int maxX = Integer.MIN_VALUE;
+        // int maxY = Integer.MIN_VALUE;
+        // int maxZ = Integer.MIN_VALUE;
+        // for (int l = 0, x = 0; l < this.fingerLength; l++) {
+        // for (int r = 0; r < repetition[l]; r++) {
+        // // Checking single slice
+        // Map cacheLayer = new HashMap<>();
+        // for (int y = 0; y < this.thumbLength; y++) {
+        // for (int z = 0; z < this.palmLength; z++) {
+        // TraceabilityPredicate predicate = this.blockMatches[l][y][z];
+        // boolean find = false;
+        // BlockInfo[] infos = null;
+        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { // check layer and
+        // // previewCount
+        // if (limit.minLayerCount > 0) {
+        // if (!cacheLayer.containsKey(limit)) {
+        // cacheLayer.put(limit, 1);
+        // } else if (cacheLayer.get(limit) < limit.minLayerCount) {
+        // cacheLayer.put(limit, cacheLayer.get(limit) + 1);
+        // } else {
+        // continue;
+        // }
+        // if (cacheGlobal.getOrDefault(limit, 0) < limit.previewCount) {
+        // if (!cacheGlobal.containsKey(limit)) {
+        // cacheGlobal.put(limit, 1);
+        // } else if (cacheGlobal.get(limit) < limit.previewCount) {
+        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
+        // } else {
+        // continue;
+        // }
+        // }
+        // } else {
+        // continue;
+        // }
+        // if (!cacheInfos.containsKey(limit)) {
+        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
+        // }
+        // infos = cacheInfos.get(limit);
+        // find = true;
+        // break;
+        // }
+        // if (!find) { // check global and previewCount
+        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
+        // if (limit.minGlobalCount == -1 && limit.previewCount == -1) continue;
+        // if (cacheGlobal.getOrDefault(limit, 0) < limit.previewCount) {
+        // if (!cacheGlobal.containsKey(limit)) {
+        // cacheGlobal.put(limit, 1);
+        // } else if (cacheGlobal.get(limit) < limit.previewCount) {
+        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
+        // } else {
+        // continue;
+        // }
+        // } else if (limit.minGlobalCount > 0) {
+        // if (!cacheGlobal.containsKey(limit)) {
+        // cacheGlobal.put(limit, 1);
+        // } else if (cacheGlobal.get(limit) < limit.minGlobalCount) {
+        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
+        // } else {
+        // continue;
+        // }
+        // } else {
+        // continue;
+        // }
+        // if (!cacheInfos.containsKey(limit)) {
+        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
+        // }
+        // infos = cacheInfos.get(limit);
+        // find = true;
+        // break;
+        // }
+        // }
+        // if (!find) { // check common with previewCount
+        // for (TraceabilityPredicate.SimplePredicate common : predicate.common) {
+        // if (common.previewCount > 0) {
+        // if (!cacheGlobal.containsKey(common)) {
+        // cacheGlobal.put(common, 1);
+        // } else if (cacheGlobal.get(common) < common.previewCount) {
+        // cacheGlobal.put(common, cacheGlobal.get(common) + 1);
+        // } else {
+        // continue;
+        // }
+        // } else {
+        // continue;
+        // }
+        // if (!cacheInfos.containsKey(common)) {
+        // cacheInfos.put(common, common.candidates == null ? null : common.candidates.get());
+        // }
+        // infos = cacheInfos.get(common);
+        // find = true;
+        // break;
+        // }
+        // }
+        // if (!find) { // check without previewCount
+        // for (TraceabilityPredicate.SimplePredicate common : predicate.common) {
+        // if (common.previewCount == -1) {
+        // if (!cacheInfos.containsKey(common)) {
+        // cacheInfos.put(common,
+        // common.candidates == null ? null : common.candidates.get());
+        // }
+        // infos = cacheInfos.get(common);
+        // find = true;
+        // break;
+        // }
+        // }
+        // }
+        // if (!find) { // check max
+        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
+        // if (limit.previewCount != -1) {
+        // continue;
+        // } else if (limit.maxGlobalCount != -1 || limit.maxLayerCount != -1) {
+        // if (cacheGlobal.getOrDefault(limit, 0) < limit.maxGlobalCount) {
+        // if (!cacheGlobal.containsKey(limit)) {
+        // cacheGlobal.put(limit, 1);
+        // } else {
+        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
+        // }
+        // } else if (cacheLayer.getOrDefault(limit, 0) < limit.maxLayerCount) {
+        // if (!cacheLayer.containsKey(limit)) {
+        // cacheLayer.put(limit, 1);
+        // } else {
+        // cacheLayer.put(limit, cacheLayer.get(limit) + 1);
+        // }
+        // } else {
+        // continue;
+        // }
+        // }
+        //
+        // if (!cacheInfos.containsKey(limit)) {
+        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
+        // }
+        // infos = cacheInfos.get(limit);
+        // break;
+        // }
+        // }
+        // BlockInfo info = infos == null || infos.length == 0 ? BlockInfo.EMPTY : infos[0];
+        // BlockPos pos = setActualRelativeOffset(z, y, x, EnumFacing.NORTH, EnumFacing.UP, false);
+        // // TODO
+        // if (info.getTileEntity() instanceof MetaTileEntityHolder) {
+        // MetaTileEntityHolder holder = new MetaTileEntityHolder();
+        // holder.setMetaTileEntity(((MetaTileEntityHolder) info.getTileEntity()).getMetaTileEntity());
+        // holder.getMetaTileEntity().onPlacement();
+        // info = new BlockInfo(holder.getMetaTileEntity().getBlock().getDefaultState(), holder);
+        // }
+        // blocks.put(pos, info);
+        // minX = Math.min(pos.getX(), minX);
+        // minY = Math.min(pos.getY(), minY);
+        // minZ = Math.min(pos.getZ(), minZ);
+        // maxX = Math.max(pos.getX(), maxX);
+        // maxY = Math.max(pos.getY(), maxY);
+        // maxZ = Math.max(pos.getZ(), maxZ);
+        // }
+        // }
+        // x++;
+        // }
+        // }
+        // BlockInfo[][][] result = (BlockInfo[][][]) Array.newInstance(BlockInfo.class, maxX - minX + 1, maxY - minY +
+        // 1,
+        // maxZ - minZ + 1);
+        // int finalMinX = minX;
+        // int finalMinY = minY;
+        // int finalMinZ = minZ;
+        // blocks.forEach((pos, info) -> {
+        // if (info.getTileEntity() instanceof MetaTileEntityHolder) {
+        // MetaTileEntity metaTileEntity = ((MetaTileEntityHolder) info.getTileEntity()).getMetaTileEntity();
+        // boolean find = false;
+        // for (EnumFacing enumFacing : FACINGS) {
+        // if (metaTileEntity.isValidFrontFacing(enumFacing)) {
+        // if (!blocks.containsKey(pos.offset(enumFacing))) {
+        // metaTileEntity.setFrontFacing(enumFacing);
+        // find = true;
+        // break;
+        // }
+        // }
+        // }
+        // if (!find) {
+        // for (EnumFacing enumFacing : FACINGS) {
+        // BlockInfo blockInfo = blocks.get(pos.offset(enumFacing));
+        // if (blockInfo != null && blockInfo.getBlockState().getBlock() == Blocks.AIR &&
+        // metaTileEntity.isValidFrontFacing(enumFacing)) {
+        // metaTileEntity.setFrontFacing(enumFacing);
+        // break;
+        // }
+        // }
+        // }
+        // }
+        // result[pos.getX() - finalMinX][pos.getY() - finalMinY][pos.getZ() - finalMinZ] = info;
+        // });
+        // return result;
     }
 
-    private GreggyBlockPos offsetFrom(GreggyBlockPos start, int aisleOffset, int stringOffset, int charOffset, @NotNull EnumFacing frontFacing,
-                                @NotNull EnumFacing upFacing, boolean flip) {
+    private GreggyBlockPos offsetFrom(GreggyBlockPos start, int aisleOffset, int stringOffset, int charOffset,
+                                      @NotNull EnumFacing frontFacing,
+                                      @NotNull EnumFacing upFacing, boolean flip) {
         GreggyBlockPos pos = start.copy();
         pos.offset(structureDir[0].getRelativeFacing(frontFacing, upFacing, flip), aisleOffset);
         pos.offset(structureDir[1].getRelativeFacing(frontFacing, upFacing, flip), stringOffset);
@@ -645,9 +647,11 @@ private GreggyBlockPos offsetFrom(GreggyBlockPos start, int aisleOffset, int str
         return pos;
     }
 
-    private GreggyBlockPos startPos(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing, boolean flip) {
+    private GreggyBlockPos startPos(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing,
+                                    boolean flip) {
         // negate since the offsets are the opposite direction of structureDir
-        return offsetFrom(controllerPos, -startOffset[0], -startOffset[1], -startOffset[2], frontFacing, upFacing, flip);
+        return offsetFrom(controllerPos, -startOffset[0], -startOffset[1], -startOffset[2], frontFacing, upFacing,
+                flip);
     }
 
     private BlockPos setActualRelativeOffset(int x, int y, int z, EnumFacing facing, EnumFacing upwardsFacing,
diff --git a/src/main/java/gregtech/api/pattern/BlockWorldState.java b/src/main/java/gregtech/api/pattern/BlockWorldState.java
index 3c2dae0771a..60453626510 100644
--- a/src/main/java/gregtech/api/pattern/BlockWorldState.java
+++ b/src/main/java/gregtech/api/pattern/BlockWorldState.java
@@ -13,6 +13,7 @@
  * Class allowing access to a block at a certain pos for structure checks and contains structure information for legacy
  */
 public class BlockWorldState {
+
     protected static boolean warned = false;
     protected World world;
     protected BlockPos pos;
@@ -99,7 +100,8 @@ public void setWorld(World world) {
     protected void warn(String name) {
         if (warned) return;
 
-        GTLog.logger.warn("Calling " + name + " on BlockWorldState is deprecated! Use the method on StructureInfo, obtained via BlockWorldState#getStructureInfo() !");
+        GTLog.logger.warn("Calling " + name +
+                " on BlockWorldState is deprecated! Use the method on StructureInfo, obtained via BlockWorldState#getStructureInfo() !");
         warned = true;
     }
 }
diff --git a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
index f92e9337ec0..5a575b85723 100644
--- a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
@@ -19,8 +19,11 @@
  * A builder class for {@link BlockPattern}
* When the multiblock is placed, its facings are concrete. Then, the {@link RelativeDirection}s passed into * {@link FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)} are ways in which the - * pattern progresses. It can be thought like this, where startPos() is either defined via {@link FactoryBlockPattern#setStartOffset(int, int, int)} - * , or automatically detected(for legacy compat only, you should use {@link FactoryBlockPattern#setStartOffset(int, int, int)} always for new code): + * pattern progresses. It can be thought like this, where startPos() is either defined via + * {@link FactoryBlockPattern#setStartOffset(int, int, int)} + * , or automatically detected(for legacy compat only, you should use + * {@link FactoryBlockPattern#setStartOffset(int, int, int)} always for new code): + * *
  * {@code
  * for(int aisleI in 0..aisles):
@@ -43,9 +46,10 @@ public class FactoryBlockPattern {
      */
     private final int[] dimensions = { -1, -1, -1 };
     /**
-     * In the form of [ aisleOffset, stringOffset, charOffset ] where the offsets are the opposite {@link RelativeDirection} of structure directions
+     * In the form of [ aisleOffset, stringOffset, charOffset ] where the offsets are the opposite
+     * {@link RelativeDirection} of structure directions
      */
-    private final int[] startOffset = new int[3];
+    private int[] startOffset;
     private char centerChar;
 
     private final List aisles = new ArrayList<>();
@@ -90,6 +94,7 @@ private FactoryBlockPattern(RelativeDirection charDir, RelativeDirection stringD
 
     /**
      * Adds a repeatable aisle to this pattern.
+     * 
      * @param aisle The aisle to add
      * @see FactoryBlockPattern#setRepeatable(int, int)
      */
@@ -139,6 +144,7 @@ public FactoryBlockPattern aisle(String... aisle) {
 
     /**
      * Set last aisle repeatable
+     * 
      * @param minRepeat Minimum amount of repeats, inclusive
      * @param maxRepeat Maximum amount of repeats, inclusive
      */
@@ -151,6 +157,7 @@ public FactoryBlockPattern setRepeatable(int minRepeat, int maxRepeat) {
 
     /**
      * Set last aisle repeatable
+     * 
      * @param repeatCount The amount to repeat
      */
     public FactoryBlockPattern setRepeatable(int repeatCount) {
@@ -165,7 +172,9 @@ public FactoryBlockPattern setStartOffset(int aisleOffset, int stringOffset, int
     }
 
     /**
-     * Starts the builder, this is equivlent to calling {@link FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)} with RIGHT, UP, BACK
+     * Starts the builder, this is equivlent to calling
+     * {@link FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)} with RIGHT, UP, BACK
+     * 
      * @see FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)
      */
     public static FactoryBlockPattern start() {
@@ -174,9 +183,12 @@ public static FactoryBlockPattern start() {
 
     /**
      * Starts the builder, each pair of {@link RelativeDirection} must be used at exactly once!
-     * @param charDir The direction chars progress in, each successive char in a string progresses by this direction
-     * @param stringDir The direction strings progress in, each successive string in an aisle progresses by this direction
-     * @param aisleDir The direction aisles progress in, each successive {@link FactoryBlockPattern#aisle(String...)} progresses in this direction
+     * 
+     * @param charDir   The direction chars progress in, each successive char in a string progresses by this direction
+     * @param stringDir The direction strings progress in, each successive string in an aisle progresses by this
+     *                  direction
+     * @param aisleDir  The direction aisles progress in, each successive {@link FactoryBlockPattern#aisle(String...)}
+     *                  progresses in this direction
      */
     public static FactoryBlockPattern start(RelativeDirection charDir, RelativeDirection stringDir,
                                             RelativeDirection aisleDir) {
@@ -185,7 +197,8 @@ public static FactoryBlockPattern start(RelativeDirection charDir, RelativeDirec
 
     /**
      * Puts a symbol onto the predicate map
-     * @param symbol The symbol, will override previous identical ones
+     * 
+     * @param symbol       The symbol, will override previous identical ones
      * @param blockMatcher The predicate to put
      */
     public FactoryBlockPattern where(char symbol, TraceabilityPredicate blockMatcher) {
@@ -205,7 +218,8 @@ public FactoryBlockPattern where(String symbol, TraceabilityPredicate blockMatch
     public BlockPattern build() {
         checkMissingPredicates();
         this.dimensions[0] = aisles.size();
-        return new BlockPattern(aisles.toArray(new PatternAisle[0]), dimensions, structureDir, startOffset, symbolMap, centerChar);
+        return new BlockPattern(aisles.toArray(new PatternAisle[0]), dimensions, structureDir, startOffset, symbolMap,
+                centerChar);
     }
 
     private void checkMissingPredicates() {
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index 7b0826ed1a2..a640601b4c3 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -64,6 +64,7 @@ public GreggyBlockPos offset(EnumFacing facing, int amount) {
 
     /**
      * Sets this pos's position to be the same as the other one
+     * 
      * @param other The other pos to get position from
      */
     public GreggyBlockPos from(GreggyBlockPos other) {
diff --git a/src/main/java/gregtech/api/pattern/PatternAisle.java b/src/main/java/gregtech/api/pattern/PatternAisle.java
index 06a8f38d633..240c13a058b 100644
--- a/src/main/java/gregtech/api/pattern/PatternAisle.java
+++ b/src/main/java/gregtech/api/pattern/PatternAisle.java
@@ -1,10 +1,12 @@
 package gregtech.api.pattern;
 
 public class PatternAisle {
+
     // not final because of setRepeatable and i need to have compat
     // actualRepeats stores the information for multiblock checks, while minRepeats and maxRepeats are rules
     protected int minRepeats, maxRepeats, actualRepeats;
     protected final String[] pattern;
+
     public PatternAisle(int minRepeats, int maxRepeats, String[] pattern) {
         this.minRepeats = minRepeats;
         this.maxRepeats = maxRepeats;
@@ -35,8 +37,10 @@ public int getActualRepeats() {
 
     /**
      * Gets the first instance of the char in the pattern
+     * 
      * @param c The char to find
-     * @return An int array in the form of [ index into String[], index into String#charAt ], or null if it was not found
+     * @return An int array in the form of [ index into String[], index into String#charAt ], or null if it was not
+     *         found
      */
     public int[] firstInstanceOf(char c) {
         for (int strI = 0; strI < pattern.length; strI++) {
@@ -49,8 +53,9 @@ public int[] firstInstanceOf(char c) {
 
     /**
      * Gets the char at the specified position.
+     * 
      * @param stringI The string index to get from
-     * @param charI The char index to get
+     * @param charI   The char index to get
      * @return The char
      */
     public char charAt(int stringI, int charI) {
diff --git a/src/main/java/gregtech/api/pattern/PatternError.java b/src/main/java/gregtech/api/pattern/PatternError.java
index 3aa7fb8290e..091d53ee139 100644
--- a/src/main/java/gregtech/api/pattern/PatternError.java
+++ b/src/main/java/gregtech/api/pattern/PatternError.java
@@ -3,13 +3,11 @@
 import net.minecraft.client.resources.I18n;
 import net.minecraft.item.ItemStack;
 import net.minecraft.util.math.BlockPos;
-import net.minecraft.world.World;
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
 
 import org.jetbrains.annotations.Nullable;
 
-import java.util.ArrayList;
 import java.util.List;
 
 public class PatternError {
diff --git a/src/main/java/gregtech/api/pattern/StructureInfo.java b/src/main/java/gregtech/api/pattern/StructureInfo.java
index 8d806e1c3ef..7da31169a89 100644
--- a/src/main/java/gregtech/api/pattern/StructureInfo.java
+++ b/src/main/java/gregtech/api/pattern/StructureInfo.java
@@ -1,8 +1,10 @@
 package gregtech.api.pattern;
 
 public class StructureInfo {
+
     protected final PatternMatchContext context;
     protected PatternError error;
+
     public StructureInfo(PatternMatchContext context, PatternError error) {
         this.context = context;
         this.error = error;
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index aee7041aaaa..18ebebc7de0 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -7,8 +7,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.util.BlockInfo;
 
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.resources.I18n;
 import net.minecraft.init.Blocks;
@@ -18,6 +16,8 @@
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
 
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+
 import java.util.*;
 import java.util.function.BiPredicate;
 import java.util.function.Predicate;
@@ -33,25 +33,26 @@ public class TraceabilityPredicate {
             blockWorldState -> blockWorldState.getBlockState().getBlock().isAir(blockWorldState.getBlockState(),
                     blockWorldState.getWorld(), blockWorldState.getPos()));
     // Allow all heating coils, and require them to have the same type.
-    public static Supplier HEATING_COILS = () -> new TraceabilityPredicate((blockWorldState, info) -> {
-        IBlockState blockState = blockWorldState.getBlockState();
-        if (GregTechAPI.HEATING_COILS.containsKey(blockState)) {
-            IHeatingCoilBlockStats stats = GregTechAPI.HEATING_COILS.get(blockState);
-            Object currentCoil = info.getContext().getOrPut("CoilType", stats);
-            if (!currentCoil.equals(stats)) {
-                info.setError(new PatternStringError("gregtech.multiblock.pattern.error.coils"));
+    public static Supplier HEATING_COILS = () -> new TraceabilityPredicate(
+            (blockWorldState, info) -> {
+                IBlockState blockState = blockWorldState.getBlockState();
+                if (GregTechAPI.HEATING_COILS.containsKey(blockState)) {
+                    IHeatingCoilBlockStats stats = GregTechAPI.HEATING_COILS.get(blockState);
+                    Object currentCoil = info.getContext().getOrPut("CoilType", stats);
+                    if (!currentCoil.equals(stats)) {
+                        info.setError(new PatternStringError("gregtech.multiblock.pattern.error.coils"));
+                        return false;
+                    }
+                    info.getContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos());
+                    return true;
+                }
                 return false;
-            }
-            info.getContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos());
-            return true;
-        }
-        return false;
-    }, () -> GregTechAPI.HEATING_COILS.entrySet().stream()
-            // sort to make autogenerated jei previews not pick random coils each game load
-            .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
-            .map(entry -> new BlockInfo(entry.getKey(), null))
-            .toArray(BlockInfo[]::new))
-                    .addTooltips("gregtech.multiblock.pattern.error.coils");
+            }, () -> GregTechAPI.HEATING_COILS.entrySet().stream()
+                    // sort to make autogenerated jei previews not pick random coils each game load
+                    .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
+                    .map(entry -> new BlockInfo(entry.getKey(), null))
+                    .toArray(BlockInfo[]::new))
+                            .addTooltips("gregtech.multiblock.pattern.error.coils");
 
     public final List common = new ArrayList<>();
     public final List limited = new ArrayList<>();
@@ -79,7 +80,8 @@ public TraceabilityPredicate(Predicate predicate) {
     }
 
     @Deprecated
-    public TraceabilityPredicate(BiPredicate predicate, Supplier candidates) {
+    public TraceabilityPredicate(BiPredicate predicate,
+                                 Supplier candidates) {
         common.add(new SimplePredicate(predicate, candidates));
     }
 
@@ -133,6 +135,7 @@ public TraceabilityPredicate addTooltips(String... tips) {
 
     /**
      * Gets the candidates for this predicate.
+     * 
      * @return A list containing lists which group together candidates
      */
     public List> getCandidates() {
@@ -238,7 +241,8 @@ public TraceabilityPredicate setPreviewCount(int count) {
         return this;
     }
 
-    public boolean test(BlockWorldState blockWorldState, StructureInfo info, Object2IntMap globalCache, Object2IntMap layerCache) {
+    public boolean test(BlockWorldState blockWorldState, StructureInfo info, Object2IntMap globalCache,
+                        Object2IntMap layerCache) {
         for (SimplePredicate predicate : limited) {
             if (predicate.testLimited(blockWorldState, info, globalCache, layerCache)) {
                 return true;
@@ -286,7 +290,8 @@ public SimplePredicate(Predicate predicate, Supplier predicate, Supplier candidates) {
+        public SimplePredicate(BiPredicate predicate,
+                               Supplier candidates) {
             this.predicate = predicate;
             this.candidates = candidates;
         }
@@ -330,11 +335,14 @@ public boolean test(BlockWorldState state, StructureInfo info) {
             return predicate.test(state, info);
         }
 
-        public boolean testLimited(BlockWorldState blockWorldState, StructureInfo info, Object2IntMap globalCache, Object2IntMap layerCache) {
+        public boolean testLimited(BlockWorldState blockWorldState, StructureInfo info,
+                                   Object2IntMap globalCache,
+                                   Object2IntMap layerCache) {
             return testGlobal(blockWorldState, info, globalCache) && testLayer(blockWorldState, info, layerCache);
         }
 
-        public boolean testGlobal(BlockWorldState blockWorldState, StructureInfo info,Object2IntMap cache) {
+        public boolean testGlobal(BlockWorldState blockWorldState, StructureInfo info,
+                                  Object2IntMap cache) {
             if (minGlobalCount == -1 && maxGlobalCount == -1) return true;
 
             boolean base = predicate.test(blockWorldState, info);
@@ -346,7 +354,8 @@ public boolean testGlobal(BlockWorldState blockWorldState, StructureInfo info,Ob
             return false;
         }
 
-        public boolean testLayer(BlockWorldState blockWorldState, StructureInfo info,Object2IntMap cache) {
+        public boolean testLayer(BlockWorldState blockWorldState, StructureInfo info,
+                                 Object2IntMap cache) {
             if (minLayerCount == -1 && maxLayerCount == -1) return true;
 
             boolean base = predicate.test(blockWorldState, info);

From 4a45b628b3a1d4014cfd3771b377d6ae8f453ba2 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 14 Jul 2024 20:21:20 -0700
Subject: [PATCH 04/64] it actually kinda works

---
 .../gregtech/api/pattern/BlockPattern.java    | 133 ++++--------------
 .../api/pattern/FactoryBlockPattern.java      |   3 +
 .../gregtech/api/pattern/GreggyBlockPos.java  |   5 +
 .../MultiblockInfoRecipeWrapper.java          |   2 +-
 4 files changed, 33 insertions(+), 110 deletions(-)

diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java
index 2c7eb980548..ebccf6006a0 100644
--- a/src/main/java/gregtech/api/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/BlockPattern.java
@@ -2,6 +2,7 @@
 
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.util.BlockInfo;
+import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
 import net.minecraft.block.state.IBlockState;
@@ -20,12 +21,9 @@
 import org.jetbrains.annotations.Nullable;
 
 import java.util.Map;
+import java.util.function.BiPredicate;
 
 public class BlockPattern {
-
-    static EnumFacing[] FACINGS = { EnumFacing.SOUTH, EnumFacing.NORTH, EnumFacing.WEST, EnumFacing.EAST, EnumFacing.UP,
-            EnumFacing.DOWN };
-
     /**
      * In the form of [ charDir, stringDir, aisleDir ]
      */
@@ -128,9 +126,9 @@ public PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, E
         boolean valid = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, false);
         if (valid) return matchContext;
 
-        if (allowsFlip) {
-            valid = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, true);
-        }
+//        if (allowsFlip) {
+//            valid = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, true);
+//        }
         if (!valid) clearCache(); // we don't want a random cache of a partially formed multi
         return null;
     }
@@ -153,33 +151,32 @@ private boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing front
 
         for (int aisleI = 0; aisleI < aisles.length; aisleI++) {
             PatternAisle aisle = aisles[aisleI];
+            // if this doesn't get set in the inner loop, then it means all repeats passed
+            int actualRepeats = aisle.maxRepeats;
 
             for (int repeats = aisle.minRepeats; repeats <= aisle.maxRepeats; repeats++) {
                 boolean aisleResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI,
                         aisleOffset + repeats, isFlipped);
 
-                // since aisle repetitions goes up, if this amount of repetitions are invalid, and all smaller
-                // repetitions
-                // have already been checked, then all larger repetitions will also be invalid(since they must also
-                // contain
-                // the problematic section), so the pattern cannot be correct
-                if (!aisleResult) return false;
-
-                // if this isn't the last aisle, then check the next aisle after to see if this repetition matches with
-                // that
-                // if not, then this repetition is invalid. This only checks the first aisle even if it has a min repeat
-                // > 1, but yeah
-                if (aisleI != aisles.length - 1) {
-                    boolean nextResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI + 1,
-                            aisleOffset + repeats + 1, isFlipped);
-
-                    // if the next aisle is also valid, then move on
-                    if (nextResult) {
-                        aisleOffset += repeats;
-                        break;
+                // greedy search, tries to make the current aisle repeat as much as possible
+                if (!aisleResult) {
+                    // if the min repetition is invalid then the whole pattern is invalid
+                    if (repeats == aisle.minRepeats) {
+                        return false;
                     }
+                    // otherwise this is the max repeats
+                    actualRepeats = repeats - 1;
                 }
             }
+
+            aisleOffset += actualRepeats;
+        }
+
+        for (Object2IntMap.Entry entry : globalCount.object2IntEntrySet()) {
+            if (entry.getIntValue() < entry.getKey().minGlobalCount) {
+                info.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 1));
+                return false;
+            }
         }
 
         info.setError(null);
@@ -222,6 +219,7 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
             for (int charI = 0; charI < dimensions[2]; charI++) {
                 worldState.setPos(charPos);
                 TraceabilityPredicate predicate = predicates.get(aisle.charAt(stringI, charI));
+//                GTLog.logger.info("Checked pos at " + charPos);
                 boolean result = predicate.test(worldState, info, globalCount, layerCount);
                 if (!result) return false;
 
@@ -653,87 +651,4 @@ private GreggyBlockPos startPos(GreggyBlockPos controllerPos, EnumFacing frontFa
         return offsetFrom(controllerPos, -startOffset[0], -startOffset[1], -startOffset[2], frontFacing, upFacing,
                 flip);
     }
-
-    private BlockPos setActualRelativeOffset(int x, int y, int z, EnumFacing facing, EnumFacing upwardsFacing,
-                                             boolean isFlipped) {
-        int[] c0 = new int[] { x, y, z }, c1 = new int[3];
-        if (facing == EnumFacing.UP || facing == EnumFacing.DOWN) {
-            EnumFacing of = facing == EnumFacing.DOWN ? upwardsFacing : upwardsFacing.getOpposite();
-            for (int i = 0; i < 3; i++) {
-                switch (structureDir[i].getActualFacing(of)) {
-                    case UP -> c1[1] = c0[i];
-                    case DOWN -> c1[1] = -c0[i];
-                    case WEST -> c1[0] = -c0[i];
-                    case EAST -> c1[0] = c0[i];
-                    case NORTH -> c1[2] = -c0[i];
-                    case SOUTH -> c1[2] = c0[i];
-                }
-            }
-            int xOffset = upwardsFacing.getXOffset();
-            int zOffset = upwardsFacing.getZOffset();
-            int tmp;
-            if (xOffset == 0) {
-                tmp = c1[2];
-                c1[2] = zOffset > 0 ? c1[1] : -c1[1];
-                c1[1] = zOffset > 0 ? -tmp : tmp;
-            } else {
-                tmp = c1[0];
-                c1[0] = xOffset > 0 ? c1[1] : -c1[1];
-                c1[1] = xOffset > 0 ? -tmp : tmp;
-            }
-            if (isFlipped) {
-                if (upwardsFacing == EnumFacing.NORTH || upwardsFacing == EnumFacing.SOUTH) {
-                    c1[0] = -c1[0]; // flip X-axis
-                } else {
-                    c1[2] = -c1[2]; // flip Z-axis
-                }
-            }
-        } else {
-            for (int i = 0; i < 3; i++) {
-                switch (structureDir[i].getActualFacing(facing)) {
-                    case UP -> c1[1] = c0[i];
-                    case DOWN -> c1[1] = -c0[i];
-                    case WEST -> c1[0] = -c0[i];
-                    case EAST -> c1[0] = c0[i];
-                    case NORTH -> c1[2] = -c0[i];
-                    case SOUTH -> c1[2] = c0[i];
-                }
-            }
-            if (upwardsFacing == EnumFacing.WEST || upwardsFacing == EnumFacing.EAST) {
-                int xOffset = upwardsFacing == EnumFacing.WEST ? facing.rotateY().getXOffset() :
-                        facing.rotateY().getOpposite().getXOffset();
-                int zOffset = upwardsFacing == EnumFacing.WEST ? facing.rotateY().getZOffset() :
-                        facing.rotateY().getOpposite().getZOffset();
-                int tmp;
-                if (xOffset == 0) {
-                    tmp = c1[2];
-                    c1[2] = zOffset > 0 ? -c1[1] : c1[1];
-                    c1[1] = zOffset > 0 ? tmp : -tmp;
-                } else {
-                    tmp = c1[0];
-                    c1[0] = xOffset > 0 ? -c1[1] : c1[1];
-                    c1[1] = xOffset > 0 ? tmp : -tmp;
-                }
-            } else if (upwardsFacing == EnumFacing.SOUTH) {
-                c1[1] = -c1[1];
-                if (facing.getXOffset() == 0) {
-                    c1[0] = -c1[0];
-                } else {
-                    c1[2] = -c1[2];
-                }
-            }
-            if (isFlipped) {
-                if (upwardsFacing == EnumFacing.NORTH || upwardsFacing == EnumFacing.SOUTH) {
-                    if (facing == EnumFacing.NORTH || facing == EnumFacing.SOUTH) {
-                        c1[0] = -c1[0]; // flip X-axis
-                    } else {
-                        c1[2] = -c1[2]; // flip Z-axis
-                    }
-                } else {
-                    c1[1] = -c1[1]; // flip Y-axis
-                }
-            }
-        }
-        return new BlockPos(c1[0], c1[1], c1[2]);
-    }
 }
diff --git a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
index 5a575b85723..81a861f0725 100644
--- a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
@@ -218,6 +218,9 @@ public FactoryBlockPattern where(String symbol, TraceabilityPredicate blockMatch
     public BlockPattern build() {
         checkMissingPredicates();
         this.dimensions[0] = aisles.size();
+        RelativeDirection temp = structureDir[0];
+        structureDir[0] = structureDir[2];
+        structureDir[2] = temp;
         return new BlockPattern(aisles.toArray(new PatternAisle[0]), dimensions, structureDir, startOffset, symbolMap,
                 centerChar);
     }
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index a640601b4c3..50648808a1a 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -103,4 +103,9 @@ public int get(EnumFacing.Axis axis) {
     public GreggyBlockPos copy() {
         return new GreggyBlockPos().from(this);
     }
+
+    @Override
+    public String toString() {
+        return super.toString() + "[x=" + pos[0] + ", y=" + pos[1] + ", z=" + pos[2] + "]";
+    }
 }
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 6e100aeadeb..efdf7d2d72f 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -181,7 +181,7 @@ public void setRecipeLayout(RecipeLayout layout, IGuiHelper guiHelper) {
         } else {
             zoom = (float) MathHelper.clamp(zoom + (Mouse.getEventDWheel() < 0 ? 0.5 : -0.5), 3, 999);
             setNextLayer(getLayerIndex());
-            if (predicates != null && predicates.size() > 0) {
+            if (predicates != null && !predicates.isEmpty()) {
                 setItemStackGroup();
             }
         }

From 49bdb1d8078bc8069a731826b79d2cfbd80b6068 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 14 Jul 2024 22:19:42 -0700
Subject: [PATCH 05/64] caching and stuff

---
 .../multiblock/MultiblockControllerBase.java  |  2 +
 .../gregtech/api/pattern/BlockPattern.java    | 35 ++++++++------
 .../gregtech/api/pattern/GreggyBlockPos.java  | 46 ++++++++++++++++++-
 .../gregtech/api/pattern/PatternAisle.java    |  5 +-
 .../gregtech/api/pattern/StructureInfo.java   |  4 ++
 5 files changed, 74 insertions(+), 18 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 56d77842a27..039fde8a07c 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -335,8 +335,10 @@ protected Function multiblockPartSorter() {
 
     public void checkStructurePattern() {
         if (structurePattern == null) return;
+        long time = System.nanoTime();
         PatternMatchContext context = structurePattern.checkPatternFastAt(getWorld(), getPos(),
                 getFrontFacing().getOpposite(), getUpwardsFacing(), allowsFlip());
+        System.out.println("structure check for " + getClass().getSimpleName() + " took " + (System.nanoTime() - time) + " nanos");
         if (context != null && !structureFormed) {
             Set rawPartsSet = context.getOrCreate("MultiblockParts", HashSet::new);
             ArrayList parts = new ArrayList<>(rawPartsSet);
diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java
index ebccf6006a0..a6d91d54d89 100644
--- a/src/main/java/gregtech/api/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/BlockPattern.java
@@ -1,8 +1,8 @@
 package gregtech.api.pattern;
 
+import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.util.BlockInfo;
-import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
 import net.minecraft.block.state.IBlockState;
@@ -20,9 +20,6 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Map;
-import java.util.function.BiPredicate;
-
 public class BlockPattern {
     /**
      * In the form of [ charDir, stringDir, aisleDir ]
@@ -71,7 +68,7 @@ public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] di
             this.startOffset = startOffset;
         }
 
-        this.info = new StructureInfo(new PatternMatchContext(), null);
+        this.info = new StructureInfo(matchContext, null);
         this.worldState = new BlockWorldState(info);
     }
 
@@ -82,7 +79,7 @@ public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] di
      */
     private void legacyStartOffset(char center) {
         // could also use aisles.length but this is cooler
-        for (int aisleI = 0; aisleI < dimensions[2]; aisleI++) {
+        for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
             int[] result = aisles[aisleI].firstInstanceOf(center);
             if (result != null) {
                 startOffset[0] = aisleI;
@@ -92,7 +89,7 @@ private void legacyStartOffset(char center) {
             }
         }
 
-        throw new IllegalArgumentException("Didn't find center predicate");
+        System.out.println("FAILED TO FIND PREDICATE");
     }
 
     public PatternError getError() {
@@ -103,14 +100,18 @@ public PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, E
                                                   EnumFacing upwardsFacing, boolean allowsFlip) {
         if (!cache.isEmpty()) {
             boolean pass = true;
-            for (Map.Entry entry : cache.entrySet()) {
-                BlockPos pos = BlockPos.fromLong(entry.getKey());
+            GreggyBlockPos gregPos = new GreggyBlockPos();
+            for (Long2ObjectMap.Entry entry : cache.long2ObjectEntrySet()) {
+                BlockPos pos = gregPos.fromLong(entry.getLongKey()).immutable();
                 IBlockState blockState = world.getBlockState(pos);
+
                 if (blockState != entry.getValue().getBlockState()) {
                     pass = false;
                     break;
                 }
+
                 TileEntity cachedTileEntity = entry.getValue().getTileEntity();
+
                 if (cachedTileEntity != null) {
                     TileEntity tileEntity = world.getTileEntity(pos);
                     if (tileEntity != cachedTileEntity) {
@@ -119,16 +120,16 @@ public PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, E
                     }
                 }
             }
-            if (pass) return worldState.hasError() ? null : matchContext;
+            if (pass) return info.hasError() ? null : matchContext;
         }
 
         // First try normal pattern, and if it fails, try flipped (if allowed).
         boolean valid = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, false);
         if (valid) return matchContext;
 
-//        if (allowsFlip) {
-//            valid = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, true);
-//        }
+        if (allowsFlip) {
+            valid = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, true);
+        }
         if (!valid) clearCache(); // we don't want a random cache of a partially formed multi
         return null;
     }
@@ -172,6 +173,7 @@ private boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing front
             aisleOffset += actualRepeats;
         }
 
+        // global minimum checks
         for (Object2IntMap.Entry entry : globalCount.object2IntEntrySet()) {
             if (entry.getIntValue() < entry.getKey().minGlobalCount) {
                 info.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 1));
@@ -219,7 +221,12 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
             for (int charI = 0; charI < dimensions[2]; charI++) {
                 worldState.setPos(charPos);
                 TraceabilityPredicate predicate = predicates.get(aisle.charAt(stringI, charI));
-//                GTLog.logger.info("Checked pos at " + charPos);
+
+                if (predicate != TraceabilityPredicate.ANY) {
+                    TileEntity te = worldState.getTileEntity();
+                    cache.put(charPos.toLong(), new BlockInfo(worldState.getBlockState(), !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
+                }
+
                 boolean result = predicate.test(worldState, info, globalCount, layerCount);
                 if (!result) return false;
 
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index 50648808a1a..72153c046e7 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -1,15 +1,28 @@
 package gregtech.api.pattern;
 
+import gregtech.api.util.GTLog;
+
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.MathHelper;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
 
 /**
  * A possibly saner(and mutable) alternative to BlockPos, where getters and setters use indices and axis instead of
- * separate names to avoid stupid code
+ * separate names to avoid stupid code. All methods that return GreggyBlockPos return {@code this} whenever possible, except for .copy() which returns
+ * a new instance.
  */
 public class GreggyBlockPos {
 
     protected final int[] pos;
+    public static final int NUM_X_BITS = 1 + MathHelper.log2(MathHelper.smallestEncompassingPowerOfTwo(30000000));
+    public static final int NUM_Z_BITS = NUM_X_BITS, NUM_Y_BITS = 64 - 2 * NUM_X_BITS;
+    public static final int Y_SHIFT = NUM_Z_BITS;
+    public static final int X_SHIFT = Y_SHIFT + NUM_Y_BITS;
+    public static final long Z_MASK = (1L << NUM_Z_BITS) - 1;
+    public static final long Y_MASK = (1L << (NUM_Z_BITS + NUM_Y_BITS)) - 1 - Z_MASK;
 
     public GreggyBlockPos() {
         this(0, 0, 0);
@@ -23,6 +36,15 @@ public GreggyBlockPos(BlockPos base) {
         pos = new int[] { base.getX(), base.getY(), base.getZ() };
     }
 
+    /**
+     * Creates a new instance using the serialized long
+     * @see GreggyBlockPos#fromLong(long) 
+     */
+    public GreggyBlockPos(long l) {
+        pos = new int[3];
+        fromLong(l);
+    }
+
     /**
      * Sets a coordinate in the given axis
      * 
@@ -79,6 +101,28 @@ public GreggyBlockPos offset(EnumFacing facing) {
         return offset(facing, 1);
     }
 
+    /**
+     * Serializes this pos to long, this should be identical to {@link BlockPos}.
+     * But the blockpos impl is so bad, who let them cook???
+     * @return Long rep
+     */
+    public long toLong() {
+        // x values too large get automatically cut off
+        return (long) pos[0] << X_SHIFT | ((long) pos[1] << Y_SHIFT) & Y_MASK | (pos[2] & Z_MASK);
+    }
+
+    /**
+     * Sets this pos to the long
+     * @param l Serialized long, from {@link GreggyBlockPos#toLong()}
+     * @see GreggyBlockPos#GreggyBlockPos(long)
+     */
+    public GreggyBlockPos fromLong(long l) {
+        pos[0] = (int) (l >> X_SHIFT);
+        pos[1] = (int) ((l & Y_MASK) >> Y_SHIFT);
+        pos[2] = (int) (l & Z_MASK);
+        return this;
+    }
+
     /**
      * @return A new immutable instance of {@link BlockPos}
      */
diff --git a/src/main/java/gregtech/api/pattern/PatternAisle.java b/src/main/java/gregtech/api/pattern/PatternAisle.java
index 240c13a058b..1c78f7eec88 100644
--- a/src/main/java/gregtech/api/pattern/PatternAisle.java
+++ b/src/main/java/gregtech/api/pattern/PatternAisle.java
@@ -44,9 +44,8 @@ public int getActualRepeats() {
      */
     public int[] firstInstanceOf(char c) {
         for (int strI = 0; strI < pattern.length; strI++) {
-            for (int chrI = 0; chrI < pattern[0].length(); chrI++) {
-                if (pattern[strI].charAt(chrI) == c) return new int[] { strI, chrI };
-            }
+            int pos = pattern[strI].indexOf(c);
+            if (pos != -1) return new int[] { strI, pos };
         }
         return null;
     }
diff --git a/src/main/java/gregtech/api/pattern/StructureInfo.java b/src/main/java/gregtech/api/pattern/StructureInfo.java
index 7da31169a89..11d75fd5767 100644
--- a/src/main/java/gregtech/api/pattern/StructureInfo.java
+++ b/src/main/java/gregtech/api/pattern/StructureInfo.java
@@ -10,6 +10,10 @@ public StructureInfo(PatternMatchContext context, PatternError error) {
         this.error = error;
     }
 
+    public boolean hasError() {
+        return error != null;
+    }
+
     public PatternError getError() {
         return error;
     }

From 2694a0a8cec82bb23c3bbbafed825e48db0bc89a Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 14 Jul 2024 22:24:33 -0700
Subject: [PATCH 06/64] spotless, deprecations, and not nulls

---
 .../multiblock/MultiblockControllerBase.java     | 16 ++++++++--------
 .../java/gregtech/api/pattern/BlockPattern.java  |  4 +++-
 .../gregtech/api/pattern/GreggyBlockPos.java     | 13 ++++++-------
 .../api/pattern/TraceabilityPredicate.java       |  1 -
 .../multi/MetaTileEntityLargeBoiler.java         |  2 +-
 .../multi/MetaTileEntityPrimitiveWaterPump.java  |  2 +-
 .../multi/electric/MetaTileEntityCleanroom.java  |  8 ++++----
 .../electric/MetaTileEntityCrackingUnit.java     |  2 +-
 .../MetaTileEntityElectricBlastFurnace.java      |  2 +-
 .../multi/electric/MetaTileEntityFluidDrill.java |  2 +-
 .../MetaTileEntityImplosionCompressor.java       |  2 +-
 .../MetaTileEntityLargeChemicalReactor.java      |  2 +-
 .../multi/electric/MetaTileEntityLargeMiner.java |  2 +-
 .../electric/MetaTileEntityPowerSubstation.java  |  6 +++---
 .../electric/MetaTileEntityPyrolyseOven.java     |  2 +-
 .../electric/MetaTileEntityVacuumFreezer.java    |  2 +-
 .../MetaTileEntityCentralMonitor.java            |  3 ++-
 .../MetaTileEntityLargeCombustionEngine.java     |  2 +-
 .../generator/MetaTileEntityLargeTurbine.java    |  2 +-
 .../multi/steam/MetaTileEntitySteamGrinder.java  |  2 +-
 20 files changed, 39 insertions(+), 38 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 039fde8a07c..58ba3e4a2f0 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -191,14 +191,14 @@ public TextureAtlasSprite getFrontDefaultTexture() {
 
     public static TraceabilityPredicate tilePredicate(@NotNull BiFunction predicate,
                                                       @Nullable Supplier candidates) {
-        return new TraceabilityPredicate(blockWorldState -> {
+        return new TraceabilityPredicate((blockWorldState, info) -> {
             TileEntity tileEntity = blockWorldState.getTileEntity();
             if (!(tileEntity instanceof IGregTechTileEntity))
                 return false;
             MetaTileEntity metaTileEntity = ((IGregTechTileEntity) tileEntity).getMetaTileEntity();
             if (predicate.apply(blockWorldState, metaTileEntity)) {
                 if (metaTileEntity instanceof IMultiblockPart) {
-                    Set partsFound = blockWorldState.getMatchContext().getOrCreate("MultiblockParts",
+                    Set partsFound = info.getContext().getOrCreate("MultiblockParts",
                             HashSet::new);
                     partsFound.add((IMultiblockPart) metaTileEntity);
                 }
@@ -239,10 +239,10 @@ public static TraceabilityPredicate abilities(MultiblockAbility... allowedAbi
     }
 
     public static TraceabilityPredicate states(IBlockState... allowedStates) {
-        return new TraceabilityPredicate(blockWorldState -> {
+        return new TraceabilityPredicate((blockWorldState, info) -> {
             IBlockState state = blockWorldState.getBlockState();
             if (state.getBlock() instanceof VariantActiveBlock) {
-                blockWorldState.getMatchContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos());
+                info.getContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos());
             }
             return ArrayUtils.contains(allowedStates, state);
         }, getCandidates(allowedStates));
@@ -256,17 +256,16 @@ public static TraceabilityPredicate frames(Material... frameMaterials) {
                 .toArray(IBlockState[]::new))
                         .or(new TraceabilityPredicate(blockWorldState -> {
                             TileEntity tileEntity = blockWorldState.getTileEntity();
-                            if (!(tileEntity instanceof IPipeTile)) {
+                            if (!(tileEntity instanceof IPipeTilepipeTile)) {
                                 return false;
                             }
-                            IPipeTile pipeTile = (IPipeTile) tileEntity;
                             return ArrayUtils.contains(frameMaterials, pipeTile.getFrameMaterial());
                         }));
     }
 
     public static TraceabilityPredicate blocks(Block... block) {
         return new TraceabilityPredicate(
-                blockWorldState -> ArrayUtils.contains(block, blockWorldState.getBlockState().getBlock()),
+                (blockWorldState, info) -> ArrayUtils.contains(block, blockWorldState.getBlockState().getBlock()),
                 getCandidates(Arrays.stream(block).map(Block::getDefaultState).toArray(IBlockState[]::new)));
     }
 
@@ -338,7 +337,8 @@ public void checkStructurePattern() {
         long time = System.nanoTime();
         PatternMatchContext context = structurePattern.checkPatternFastAt(getWorld(), getPos(),
                 getFrontFacing().getOpposite(), getUpwardsFacing(), allowsFlip());
-        System.out.println("structure check for " + getClass().getSimpleName() + " took " + (System.nanoTime() - time) + " nanos");
+        System.out.println(
+                "structure check for " + getClass().getSimpleName() + " took " + (System.nanoTime() - time) + " nanos");
         if (context != null && !structureFormed) {
             Set rawPartsSet = context.getOrCreate("MultiblockParts", HashSet::new);
             ArrayList parts = new ArrayList<>(rawPartsSet);
diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java
index a6d91d54d89..b45f977ed12 100644
--- a/src/main/java/gregtech/api/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/BlockPattern.java
@@ -21,6 +21,7 @@
 import org.jetbrains.annotations.Nullable;
 
 public class BlockPattern {
+
     /**
      * In the form of [ charDir, stringDir, aisleDir ]
      */
@@ -224,7 +225,8 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
 
                 if (predicate != TraceabilityPredicate.ANY) {
                     TileEntity te = worldState.getTileEntity();
-                    cache.put(charPos.toLong(), new BlockInfo(worldState.getBlockState(), !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
+                    cache.put(charPos.toLong(), new BlockInfo(worldState.getBlockState(),
+                            !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
                 }
 
                 boolean result = predicate.test(worldState, info, globalCount, layerCount);
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index 72153c046e7..855cb9ef308 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -1,17 +1,13 @@
 package gregtech.api.pattern;
 
-import gregtech.api.util.GTLog;
-
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.MathHelper;
 
-import java.lang.reflect.Field;
-import java.util.Arrays;
-
 /**
  * A possibly saner(and mutable) alternative to BlockPos, where getters and setters use indices and axis instead of
- * separate names to avoid stupid code. All methods that return GreggyBlockPos return {@code this} whenever possible, except for .copy() which returns
+ * separate names to avoid stupid code. All methods that return GreggyBlockPos return {@code this} whenever possible,
+ * except for .copy() which returns
  * a new instance.
  */
 public class GreggyBlockPos {
@@ -38,7 +34,8 @@ public GreggyBlockPos(BlockPos base) {
 
     /**
      * Creates a new instance using the serialized long
-     * @see GreggyBlockPos#fromLong(long) 
+     * 
+     * @see GreggyBlockPos#fromLong(long)
      */
     public GreggyBlockPos(long l) {
         pos = new int[3];
@@ -104,6 +101,7 @@ public GreggyBlockPos offset(EnumFacing facing) {
     /**
      * Serializes this pos to long, this should be identical to {@link BlockPos}.
      * But the blockpos impl is so bad, who let them cook???
+     * 
      * @return Long rep
      */
     public long toLong() {
@@ -113,6 +111,7 @@ public long toLong() {
 
     /**
      * Sets this pos to the long
+     * 
      * @param l Serialized long, from {@link GreggyBlockPos#toLong()}
      * @see GreggyBlockPos#GreggyBlockPos(long)
      */
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index 18ebebc7de0..92f8e28b2f1 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -79,7 +79,6 @@ public TraceabilityPredicate(Predicate predicate) {
         this(predicate, null);
     }
 
-    @Deprecated
     public TraceabilityPredicate(BiPredicate predicate,
                                  Supplier candidates) {
         common.add(new SimplePredicate(predicate, candidates));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
index 6921ca44d5f..f6e4495b7db 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
@@ -187,7 +187,7 @@ public boolean isActive() {
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("XXX", "CCC", "CCC", "CCC")
                 .aisle("XXX", "CPC", "CPC", "CCC")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
index 0017c750d94..39d61ccb58e 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
@@ -131,7 +131,7 @@ private void resetTileAbilities() {
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("XXXX", "**F*", "**F*")
                 .aisle("XXHX", "F**F", "FFFF")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index 22c6909ca35..f9870eca629 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -385,19 +385,19 @@ protected BlockPattern createStructurePattern() {
 
     @NotNull
     protected TraceabilityPredicate filterPredicate() {
-        return new TraceabilityPredicate(blockWorldState -> {
+        return new TraceabilityPredicate((blockWorldState, info) -> {
             IBlockState blockState = blockWorldState.getBlockState();
             if (GregTechAPI.CLEANROOM_FILTERS.containsKey(blockState)) {
                 ICleanroomFilter cleanroomFilter = GregTechAPI.CLEANROOM_FILTERS.get(blockState);
                 if (cleanroomFilter.getCleanroomType() == null) return false;
 
-                ICleanroomFilter currentFilter = blockWorldState.getMatchContext().getOrPut("FilterType",
+                ICleanroomFilter currentFilter = info.getContext().getOrPut("FilterType",
                         cleanroomFilter);
                 if (!currentFilter.getCleanroomType().equals(cleanroomFilter.getCleanroomType())) {
-                    blockWorldState.setError(new PatternStringError("gregtech.multiblock.pattern.error.filters"));
+                    info.setError(new PatternStringError("gregtech.multiblock.pattern.error.filters"));
                     return false;
                 }
-                blockWorldState.getMatchContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos());
+                info.getContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos());
                 return true;
             }
             return false;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
index b1bd30b8574..2d8c7dfd379 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
@@ -52,7 +52,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("HCHCH", "HCHCH", "HCHCH")
                 .aisle("HCHCH", "H###H", "HCHCH")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index 89c277d7c8d..3d8e8382cbf 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -118,7 +118,7 @@ public boolean checkRecipe(@NotNull Recipe recipe, boolean consumeIfSuccess) {
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("XXX", "CCC", "CCC", "XXX")
                 .aisle("XXX", "C#C", "C#C", "XMX")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
index dca07a39e5f..01d1ea49951 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
@@ -110,7 +110,7 @@ protected void updateFormedValid() {
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("XXX", "#F#", "#F#", "#F#", "###", "###", "###")
                 .aisle("XXX", "FCF", "FCF", "FCF", "#F#", "#F#", "#F#")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java
index 705e38a459c..0180b1414c5 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java
@@ -33,7 +33,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("XXX", "XXX", "XXX")
                 .aisle("XXX", "X#X", "XXX")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
index a15161260ee..c1b82c34791 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
@@ -52,7 +52,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         TraceabilityPredicate casing = states(getCasingState()).setMinGlobalLimited(10);
         TraceabilityPredicate abilities = autoAbilities();
         return FactoryBlockPattern.start()
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
index cc2b78fa6ba..a1b05546da1 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
@@ -185,7 +185,7 @@ protected void updateFormedValid() {
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("XXX", "#F#", "#F#", "#F#", "###", "###", "###")
                 .aisle("XXX", "FCF", "FCF", "FCF", "#F#", "#F#", "#F#")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index 7814f3dccc9..0ff367ea434 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -284,7 +284,7 @@ protected IBlockState getGlassState() {
     }
 
     protected static final Supplier BATTERY_PREDICATE = () -> new TraceabilityPredicate(
-            blockWorldState -> {
+            (blockWorldState, info) -> {
                 IBlockState state = blockWorldState.getBlockState();
                 if (GregTechAPI.PSS_BATTERIES.containsKey(state)) {
                     IBatteryData battery = GregTechAPI.PSS_BATTERIES.get(state);
@@ -292,9 +292,9 @@ protected IBlockState getGlassState() {
                     // This lets you use empty batteries as "filler slots" for convenience if desired.
                     if (battery.getTier() != -1 && battery.getCapacity() > 0) {
                         String key = PMC_BATTERY_HEADER + battery.getBatteryName();
-                        BatteryMatchWrapper wrapper = blockWorldState.getMatchContext().get(key);
+                        BatteryMatchWrapper wrapper = info.getContext().get(key);
                         if (wrapper == null) wrapper = new BatteryMatchWrapper(battery);
-                        blockWorldState.getMatchContext().set(key, wrapper.increment());
+                        info.getContext().set(key, wrapper.increment());
                     }
                     return true;
                 }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
index b4715563582..a32eafb8905 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
@@ -52,7 +52,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("XXX", "XXX", "XXX")
                 .aisle("CCC", "C#C", "CCC")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
index 41c2a1ea1f1..e5712863c7a 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
@@ -33,7 +33,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("XXX", "XXX", "XXX")
                 .aisle("XXX", "X#X", "XXX")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index 4deeabb9a9f..54c1ff724ea 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -60,6 +60,7 @@
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.lwjgl.opengl.GL11;
 
@@ -396,7 +397,7 @@ public Set getAllCovers() {
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         StringBuilder start = new StringBuilder("AS");
         StringBuilder slice = new StringBuilder("BB");
         StringBuilder end = new StringBuilder("AA");
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
index 5bdd31eafc8..8318e55d737 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
@@ -126,7 +126,7 @@ public void addInformation(ItemStack stack, @Nullable World player, List
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("XXX", "XDX", "XXX")
                 .aisle("XCX", "CGC", "XCX")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
index 16522799167..80539cb7c3b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
@@ -184,7 +184,7 @@ public void addInformation(ItemStack stack, @Nullable World player, List
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("CCCC", "CHHC", "CCCC")
                 .aisle("CHHC", "RGGR", "CHHC")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java
index 804980f7952..3055a63ffd6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java
@@ -49,7 +49,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity metaTileEntityHol
     }
 
     @Override
-    protected BlockPattern createStructurePattern() {
+    protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
                 .aisle("XXX", "XXX", "XXX")
                 .aisle("XXX", "X#X", "XXX")

From 96078bb0071069910114912b4bc5be0f69c7d7c3 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Mon, 15 Jul 2024 18:19:10 -0700
Subject: [PATCH 07/64] iterable, apparently this is like 20x faster than the
 blockpos iterable

---
 .../gregtech/api/pattern/GreggyBlockPos.java  | 107 +++++++++++++++++-
 .../gregtech/api/util/TierByVoltageTest.java  |  24 ++++
 2 files changed, 129 insertions(+), 2 deletions(-)

diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index 855cb9ef308..502ebb8d423 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -4,6 +4,12 @@
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.MathHelper;
 
+import com.google.common.collect.AbstractIterator;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
 /**
  * A possibly saner(and mutable) alternative to BlockPos, where getters and setters use indices and axis instead of
  * separate names to avoid stupid code. All methods that return GreggyBlockPos return {@code this} whenever possible,
@@ -18,7 +24,7 @@ public class GreggyBlockPos {
     public static final int Y_SHIFT = NUM_Z_BITS;
     public static final int X_SHIFT = Y_SHIFT + NUM_Y_BITS;
     public static final long Z_MASK = (1L << NUM_Z_BITS) - 1;
-    public static final long Y_MASK = (1L << (NUM_Z_BITS + NUM_Y_BITS)) - 1 - Z_MASK;
+    public static final long Y_MASK = (1L << (NUM_Z_BITS + NUM_Y_BITS)) - 1;
 
     public GreggyBlockPos() {
         this(0, 0, 0);
@@ -53,6 +59,17 @@ public GreggyBlockPos set(EnumFacing.Axis axis, int value) {
         return this;
     }
 
+    /**
+     * Sets a coordinate in the axis to the value of that axis in the other pos.
+     * 
+     * @param axis  The axis to set.
+     * @param other The pos to take the other axis' value from.
+     */
+    public GreggyBlockPos set(EnumFacing.Axis axis, GreggyBlockPos other) {
+        pos[axis.ordinal()] = other.pos[axis.ordinal()];
+        return this;
+    }
+
     /**
      * Sets all 3 coordinates in the given axis order
      * 
@@ -105,7 +122,6 @@ public GreggyBlockPos offset(EnumFacing facing) {
      * @return Long rep
      */
     public long toLong() {
-        // x values too large get automatically cut off
         return (long) pos[0] << X_SHIFT | ((long) pos[1] << Y_SHIFT) & Y_MASK | (pos[2] & Z_MASK);
     }
 
@@ -151,4 +167,91 @@ public GreggyBlockPos copy() {
     public String toString() {
         return super.toString() + "[x=" + pos[0] + ", y=" + pos[1] + ", z=" + pos[2] + "]";
     }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof GreggyBlockPos greg)) return false;
+
+        return Arrays.equals(pos, greg.pos);
+    }
+
+    /**
+     * Returns an iterable going over all blocks in the cube. Although this iterator returns a mutable pos, it has
+     * an internal pos that sets the mutable one so modifying the mutable pos is safe. The iterator starts at one of the
+     * 8 points on the cube.
+     * Which of the 8 is determined by the 3 facings, the selected one is the one in the least {facing} direction for
+     * all 3 facings. The ending
+     * point is simply the point on the opposite corner to the first.
+     * For example, if the 3 facings are UP, NORTH, and WEST, then the first is the point in the most DOWN, SOUTH, and
+     * EAST direction.
+     * The 3 facings must be all in distinct axis, that is, their .getAxis() must all be distinct.
+     * 
+     * @param first   One corner of the cube.
+     * @param second  Other corner of the cube.
+     * @param facings 3 facings in the order of [ point, line, plane ]
+     */
+    public static Iterable allInBox(GreggyBlockPos first, GreggyBlockPos second,
+                                                    EnumFacing... facings) {
+        if (facings.length != 3) throw new IllegalArgumentException("Facings must be array of length 3!");
+
+        // validate facings, int division so opposite facings mark the same element
+        boolean[] dirs = new boolean[3];
+        for (int i = 0; i < 3; i++) {
+            dirs[facings[i].ordinal() / 2] = true;
+        }
+
+        if (!(dirs[0] && dirs[1] && dirs[2]))
+            throw new IllegalArgumentException("The 3 facings must use each axis exactly once!");
+
+        GreggyBlockPos start = new GreggyBlockPos();
+        int[] length = new int[3];
+
+        for (int i = 0; i < 3; i++) {
+            int a = first.get(facings[i].getAxis());
+            int b = second.get(facings[i].getAxis());
+
+            // multiplying by -1 reverses the direction of min(...)
+            int mult = facings[i].getAxisDirection().getOffset();
+
+            start.set(facings[i].getAxis(), Math.min(a * mult, b * mult) * mult);
+
+            // length of the bounding box's axis
+            length[i] = Math.abs(a - b);
+        }
+
+        return new Iterable<>() {
+
+            @NotNull
+            @Override
+            public Iterator iterator() {
+                return new AbstractIterator<>() {
+
+                    // offset, elements are always positive
+                    // the -1 here is to offset the first time offset[0]++ is called, so that the first
+                    // result is the start pos
+                    private final int[] offset = new int[] { -1, 0, 0 };
+                    private final GreggyBlockPos result = start.copy();
+
+                    @Override
+                    protected GreggyBlockPos computeNext() {
+                        offset[0]++;
+                        if (offset[0] > length[0]) {
+                            offset[0] = 0;
+                            offset[1]++;
+                        }
+
+                        if (offset[1] > length[1]) {
+                            offset[1] = 0;
+                            offset[2]++;
+                        }
+
+                        if (offset[2] > length[2]) return endOfData();
+
+                        return result.from(start).offset(facings[0], offset[0]).offset(facings[1], offset[1])
+                                .offset(facings[2], offset[2]);
+                    }
+                };
+            }
+        };
+    }
 }
diff --git a/src/test/java/gregtech/api/util/TierByVoltageTest.java b/src/test/java/gregtech/api/util/TierByVoltageTest.java
index fafe266f771..c31be3f2013 100644
--- a/src/test/java/gregtech/api/util/TierByVoltageTest.java
+++ b/src/test/java/gregtech/api/util/TierByVoltageTest.java
@@ -1,13 +1,37 @@
 package gregtech.api.util;
 
+import gregtech.api.pattern.GreggyBlockPos;
+
+import net.minecraft.util.EnumFacing;
+
+import net.minecraft.util.math.BlockPos;
+
 import org.junit.jupiter.api.Test;
 
 import static gregtech.api.GTValues.*;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class TierByVoltageTest {
 
+    @Test
+    public void temp() {
+        // todo remove this
+        int iters = 0;
+        for (GreggyBlockPos a : GreggyBlockPos.allInBox(new GreggyBlockPos(0, 0, 0), new GreggyBlockPos(2, 3, 4), EnumFacing.NORTH, EnumFacing.UP, EnumFacing.WEST)) {
+            System.out.println(a);
+            iters++;
+        }
+
+//        for (BlockPos a : BlockPos.getAllInBox(new BlockPos(0, 0, 0), new BlockPos(2, 3, 4))) {
+//            System.out.println(a.toString());
+//            iters++;
+//        }
+
+        assertEquals(3 * 4 * 5, iters);
+    }
+
     @Test
     public void testV() {
         expectTier(V[ULV], ULV, ULV);

From 04192712e8e28513d578b89ca03a9689fe408d99 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Tue, 16 Jul 2024 00:20:23 -0700
Subject: [PATCH 08/64] step 1 of 13918238091 to making previews work

---
 .../multiblock/MultiblockControllerBase.java  |  31 ++-
 .../gregtech/api/pattern/BlockPattern.java    |   2 +-
 .../api/pattern/FactoryBlockPattern.java      |   2 +-
 .../api/pattern/MultiblockShapeInfo.java      | 112 ++-------
 .../gregtech/api/pattern/PatternAisle.java    |   8 +
 .../api/pattern/PreviewBlockPattern.java      | 231 ++++++++++++++++++
 .../gregtech/api/util/TierByVoltageTest.java  |  13 +-
 7 files changed, 277 insertions(+), 122 deletions(-)
 create mode 100644 src/main/java/gregtech/api/pattern/PreviewBlockPattern.java

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 58ba3e4a2f0..0c39d676388 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -12,6 +12,7 @@
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
+import gregtech.api.pattern.PreviewBlockPattern;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.unification.material.Material;
@@ -26,6 +27,8 @@
 import gregtech.client.renderer.texture.cube.SimpleOrientedCubeRenderer;
 import gregtech.common.blocks.MetaBlocks;
 
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+
 import net.minecraft.block.Block;
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.renderer.texture.TextureAtlasSprite;
@@ -72,6 +75,7 @@
 import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import static gregtech.api.capability.GregtechDataCodes.*;
 
@@ -564,23 +568,16 @@ public List getMatchingShapes() {
         // return repetitionDFS(new ArrayList<>(), aisleRepetitions, new Stack<>());
     }
 
-    private List repetitionDFS(List pages, int[][] aisleRepetitions,
-                                                    Stack repetitionStack) {
-        if (repetitionStack.size() == aisleRepetitions.length) {
-            int[] repetition = new int[repetitionStack.size()];
-            for (int i = 0; i < repetitionStack.size(); i++) {
-                repetition[i] = repetitionStack.get(i);
-            }
-            pages.add(new MultiblockShapeInfo(Objects.requireNonNull(this.structurePattern).getPreview(repetition)));
-        } else {
-            for (int i = aisleRepetitions[repetitionStack.size()][0]; i <=
-                    aisleRepetitions[repetitionStack.size()][1]; i++) {
-                repetitionStack.push(i);
-                repetitionDFS(pages, aisleRepetitions, repetitionStack);
-                repetitionStack.pop();
-            }
-        }
-        return pages;
+    /**
+     * The new(and better) way of getting shapes for in world, jei, and autobuild. Default impl just converts
+     * {@link MultiblockControllerBase#getMatchingShapes()} to this
+     * @param keyMap A map for autobuild, or null if it is an in world or jei preview.
+     * @param hatches This is whether you should put hatches, JEI previews need hatches, but autobuild and in world
+     *                previews shouldn't(unless the hatch is necessary and only has one valid spot, such as EBF)
+     */
+    // todo add use for the keyMap with the multiblock builder
+    public List getBuildableShapes(@Nullable Object2IntMap keyMap, boolean hatches) {
+        return getMatchingShapes().stream().map(PreviewBlockPattern::new).collect(Collectors.toList());
     }
 
     @SideOnly(Side.CLIENT)
diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java
index b45f977ed12..c7d40b52dd1 100644
--- a/src/main/java/gregtech/api/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/BlockPattern.java
@@ -446,7 +446,7 @@ public void autoBuild(EntityPlayer player, MultiblockControllerBase controllerBa
         // });
     }
 
-    public BlockInfo[][][] getPreview(int[] repetition) {
+    public BlockInfo[][][] getDefaultShape() {
         return null;
         // Map cacheInfos = new HashMap<>();
         // Map cacheGlobal = new HashMap<>();
diff --git a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
index 81a861f0725..43f9a820a66 100644
--- a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
@@ -39,7 +39,7 @@
  */
 public class FactoryBlockPattern {
 
-    private static final Joiner COMMA_JOIN = Joiner.on(",");
+    protected static final Joiner COMMA_JOIN = Joiner.on(",");
 
     /**
      * In the form of [ num aisles, num string per aisle, num char per string ]
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index f82b3dc8319..9dec704f2ec 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -3,85 +3,43 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.util.BlockInfo;
-import gregtech.api.util.RelativeDirection;
 
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
-import net.minecraft.util.math.BlockPos;
 
+import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
+import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Objects;
 import java.util.function.Supplier;
 
-import static gregtech.api.util.RelativeDirection.*;
-
 public class MultiblockShapeInfo {
 
-    /** {@code [x][y][z]} */
-    private final BlockInfo[][][] blocks;
-
-    public MultiblockShapeInfo(BlockInfo[][][] blocks) {
-        this.blocks = blocks;
-    }
-
     /**
-     * @return the blocks in an array of format: {@code [x][y][z]}
+     * Array of aisles, you should use {@link PreviewBlockPattern} instead
      */
-    public BlockInfo[][][] getBlocks() {
-        return blocks;
-    }
+    protected final PatternAisle[] aisles;
+    protected final Char2ObjectMap symbols;
 
-    public static Builder builder() {
-        return builder(RIGHT, DOWN, BACK);
+    public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symbols) {
+        this.aisles = aisles;
+        this.symbols = symbols;
     }
 
-    public static Builder builder(@NotNull RelativeDirection... structureDir) {
-        if (structureDir.length != 3) throw new IllegalArgumentException("Must have exactly 3 directions!");
-        return new Builder(structureDir[0], structureDir[1], structureDir[2]);
+    public static Builder builder() {
+        return new Builder();
     }
 
     public static class Builder {
 
-        private final RelativeDirection[] structureDir = new RelativeDirection[3];
-
-        private List shape = new ArrayList<>();
-        private Map symbolMap = new HashMap<>();
-
-        /**
-         * Use {@link #builder(RelativeDirection...)}
-         * 
-         * @param structureDir The directions that the provided block pattern is based upon (character, string, row).
-         */
-        @Deprecated
-        public Builder(@NotNull RelativeDirection... structureDir) {
-            this(structureDir[0], structureDir[1], structureDir[2]);
-        }
-
-        @Deprecated
-        public Builder(@NotNull RelativeDirection one, @NotNull RelativeDirection two,
-                       @NotNull RelativeDirection three) {
-            this.structureDir[0] = Objects.requireNonNull(one);
-            this.structureDir[1] = Objects.requireNonNull(two);
-            this.structureDir[2] = Objects.requireNonNull(three);
-            int flags = 0;
-            for (int i = 0; i < this.structureDir.length; i++) {
-                switch (structureDir[i]) {
-                    case UP, DOWN -> flags |= 0x1;
-                    case LEFT, RIGHT -> flags |= 0x2;
-                    case FRONT, BACK -> flags |= 0x4;
-                }
-            }
-            if (flags != 0x7) throw new IllegalArgumentException("The directions must be on different axes!");
-        }
+        private List shape = new ArrayList<>();
+        private Char2ObjectMap symbolMap = new Char2ObjectOpenHashMap<>();
 
         public Builder aisle(String... data) {
-            this.shape.add(data);
+            this.shape.add(new PatternAisle(1, data));
             return this;
         }
 
@@ -119,53 +77,15 @@ public Builder where(char symbol, Supplier partSupplier, EnumFacing frontSide
                     "Supplier must supply either a MetaTileEntity or an IBlockState! Actual: " + part.getClass());
         }
 
-        @NotNull
-        private BlockInfo[][][] bakeArray() {
-            final int maxZ = shape.size();
-            final int maxY = shape.get(0).length;
-            final int maxX = shape.get(0)[0].length();
-
-            BlockPos end = RelativeDirection.setActualRelativeOffset(maxX, maxY, maxZ, EnumFacing.SOUTH, EnumFacing.UP,
-                    true, structureDir);
-            BlockPos addition = new BlockPos(end.getX() < 0 ? -end.getX() - 1 : 0, end.getY() < 0 ? -end.getY() - 1 : 0,
-                    end.getZ() < 0 ? -end.getZ() - 1 : 0);
-            BlockPos bound = new BlockPos(Math.abs(end.getX()), Math.abs(end.getY()), Math.abs(end.getZ()));
-            BlockInfo[][][] blockInfos = new BlockInfo[bound.getX()][bound.getY()][bound.getZ()];
-            for (int z = 0; z < maxZ; z++) {
-                String[] aisleEntry = shape.get(z);
-                for (int y = 0; y < maxY; y++) {
-                    String columnEntry = aisleEntry[y];
-                    for (int x = 0; x < maxX; x++) {
-                        BlockInfo info = symbolMap.getOrDefault(columnEntry.charAt(x), BlockInfo.EMPTY);
-                        TileEntity tileEntity = info.getTileEntity();
-                        if (tileEntity instanceof MetaTileEntityHolder holder) {
-                            final MetaTileEntity mte = holder.getMetaTileEntity();
-                            holder = new MetaTileEntityHolder();
-                            holder.setMetaTileEntity(mte);
-                            holder.getMetaTileEntity().onPlacement();
-                            holder.getMetaTileEntity().setFrontFacing(mte.getFrontFacing());
-                            info = new BlockInfo(info.getBlockState(), holder);
-                        } else if (tileEntity != null) {
-                            info = new BlockInfo(info.getBlockState(), tileEntity);
-                        }
-                        BlockPos pos = RelativeDirection.setActualRelativeOffset(x, y, z, EnumFacing.SOUTH,
-                                EnumFacing.UP, true, structureDir).add(addition);
-                        blockInfos[pos.getX()][pos.getY()][pos.getZ()] = info;
-                    }
-                }
-            }
-            return blockInfos;
-        }
-
         public Builder shallowCopy() {
-            Builder builder = new Builder(this.structureDir);
+            Builder builder = new Builder();
             builder.shape = new ArrayList<>(this.shape);
-            builder.symbolMap = new HashMap<>(this.symbolMap);
+            builder.symbolMap = new Char2ObjectOpenHashMap<>(this.symbolMap);
             return builder;
         }
 
         public MultiblockShapeInfo build() {
-            return new MultiblockShapeInfo(bakeArray());
+            return new MultiblockShapeInfo(shape.toArray(new PatternAisle[0]), symbolMap);
         }
     }
 }
diff --git a/src/main/java/gregtech/api/pattern/PatternAisle.java b/src/main/java/gregtech/api/pattern/PatternAisle.java
index 1c78f7eec88..e86180eef9d 100644
--- a/src/main/java/gregtech/api/pattern/PatternAisle.java
+++ b/src/main/java/gregtech/api/pattern/PatternAisle.java
@@ -60,4 +60,12 @@ public int[] firstInstanceOf(char c) {
     public char charAt(int stringI, int charI) {
         return pattern[stringI].charAt(charI);
     }
+
+    public int getStringCount() {
+        return pattern.length;
+    }
+
+    public int getCharCount() {
+        return pattern[0].length();
+    }
 }
diff --git a/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java b/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java
new file mode 100644
index 00000000000..f5c662e8532
--- /dev/null
+++ b/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java
@@ -0,0 +1,231 @@
+package gregtech.api.pattern;
+
+import gregtech.api.metatileentity.MetaTileEntity;
+import gregtech.api.metatileentity.MetaTileEntityHolder;
+import gregtech.api.util.BlockInfo;
+import gregtech.api.util.RelativeDirection;
+
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.EnumFacing;
+
+import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
+import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import static gregtech.api.pattern.FactoryBlockPattern.COMMA_JOIN;
+
+/**
+ * Class holding data for a concrete multiblock. This multiblock can be formed or unformed in this.
+ */
+public class PreviewBlockPattern {
+
+    /**
+     * In [ aisleDir, stringDir, charDir ]
+     */
+    protected final RelativeDirection[] structureDir;
+
+    protected final PatternAisle[] aisles;
+    protected final int[] dimensions, startOffset;
+    protected final Char2ObjectMap symbols;
+
+    /**
+     * Legacy compat only, do not use for new code.
+     */
+    public PreviewBlockPattern(MultiblockShapeInfo info) {
+        this.aisles = info.aisles;
+        this.dimensions = new int[] { aisles.length, aisles[0].getStringCount(), aisles[0].getCharCount() };
+        structureDir = new RelativeDirection[] { RelativeDirection.BACK, RelativeDirection.UP,
+                RelativeDirection.RIGHT };
+        this.startOffset = new int[3];
+        // i am lazy so hopefully addons follow the convention of using 'S' for their self predicate(aka center predicate)
+        legacyStartOffset('S');
+        this.symbols = info.symbols;
+    }
+
+    public PreviewBlockPattern(PatternAisle[] aisles, int[] dimensions, RelativeDirection[] directions, int[] startOffset, Char2ObjectMap symbols) {
+        this.aisles = aisles;
+        this.dimensions = dimensions;
+        this.structureDir = directions;
+        this.startOffset = startOffset;
+        this.symbols = symbols;
+    }
+
+    private void legacyStartOffset(char center) {
+        // could also use aisles.length but this is cooler
+        for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
+            int[] result = aisles[aisleI].firstInstanceOf(center);
+            if (result != null) {
+                startOffset[0] = aisleI;
+                startOffset[1] = result[0];
+                startOffset[2] = result[1];
+                return;
+            }
+        }
+
+        System.out.println("FAILED TO FIND PREDICATE");
+    }
+
+    public static class Builder {
+
+        /**
+         * In [ aisle count, string count, char count ]
+         */
+        protected final int[] dimensions = new int[3];
+
+        /**
+         * In relative directions opposite to {@code directions}
+         */
+        protected final int[] offset = new int[3];
+
+        /**
+         * Way the builder progresses
+         * @see FactoryBlockPattern
+         */
+        protected final RelativeDirection[] directions = new RelativeDirection[3];
+        protected final List aisles = new ArrayList<>();
+        protected final Char2ObjectMap symbolMap = new Char2ObjectOpenHashMap<>();
+
+        /**
+         * @see Builder#start(RelativeDirection, RelativeDirection, RelativeDirection)
+         */
+        protected Builder(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
+            directions[0] = charDir;
+            directions[1] = stringDir;
+            directions[2] = aisleDir;
+
+            boolean[] flags = new boolean[3];
+            for (int i = 0; i < 3; i++) {
+                flags[directions[i].ordinal() / 2] = true;
+            }
+            if (!(flags[0] && flags[1] && flags[2])) throw new IllegalArgumentException("Must have 3 different axes!");
+        }
+
+        /**
+         * Same as calling {@link Builder#start(RelativeDirection, RelativeDirection, RelativeDirection)} with BACK, UP, RIGHT
+         */
+        public static Builder start() {
+            return new Builder(RelativeDirection.BACK, RelativeDirection.UP, RelativeDirection.RIGHT);
+        }
+
+        /**
+         * Starts the builder, each pair of relative directions must be used exactly once!
+         * @param aisleDir The direction for aisles to advance in.
+         * @param stringDir The direction for strings to advance in.
+         * @param charDir The direction for chars to advance in.
+         */
+        public static Builder start(RelativeDirection aisleDir, RelativeDirection stringDir,
+                                    RelativeDirection charDir) {
+            return new Builder(aisleDir, stringDir, charDir);
+        }
+
+        /**
+         * Adds a new aisle to the builder.
+         * @param repeats Amount of repeats.
+         * @param aisle The aisle.
+         */
+        public Builder aisle(int repeats, String... aisle) {
+            // straight up copied from factory block pattern's code
+            if (ArrayUtils.isEmpty(aisle) || StringUtils.isEmpty(aisle[0]))
+                throw new IllegalArgumentException("Empty pattern for aisle");
+
+            // set the dimensions if the user hasn't already
+            if (dimensions[2] == -1) {
+                dimensions[2] = aisle[0].length();
+            }
+            if (dimensions[1] == -1) {
+                dimensions[1] = aisle.length;
+            }
+
+            if (aisle.length != dimensions[1]) {
+                throw new IllegalArgumentException("Expected aisle with height of " + dimensions[1] +
+                        ", but was given one with a height of " + aisle.length + ")");
+            } else {
+                for (String s : aisle) {
+                    if (s.length() != dimensions[2]) {
+                        throw new IllegalArgumentException(
+                                "Not all rows in the given aisle are the correct width (expected " + dimensions[2] +
+                                        ", found one with " + s.length() + ")");
+                    }
+
+                    for (char c : s.toCharArray()) {
+                        if (!this.symbolMap.containsKey(c)) {
+                            this.symbolMap.put(c, null);
+                        }
+                    }
+                }
+
+                aisles.add(new PatternAisle(repeats, repeats, aisle));
+                return this;
+            }
+        }
+
+        /**
+         * Same as calling {@link Builder#aisle(int, String...)} with the first argument being 1
+         */
+        public Builder aisle(String... aisle) {
+            return aisle(1, aisle);
+        }
+
+        // all the .where() are copied from MultiblockShapeInfo, yay for code stealing
+        public Builder where(char symbol, BlockInfo value) {
+            this.symbolMap.put(symbol, value);
+            return this;
+        }
+
+        public Builder where(char symbol, IBlockState blockState) {
+            return where(symbol, new BlockInfo(blockState));
+        }
+
+        public Builder where(char symbol, IBlockState blockState, TileEntity tileEntity) {
+            return where(symbol, new BlockInfo(blockState, tileEntity));
+        }
+
+        public Builder where(char symbol, MetaTileEntity tileEntity, EnumFacing frontSide) {
+            MetaTileEntityHolder holder = new MetaTileEntityHolder();
+            holder.setMetaTileEntity(tileEntity);
+            holder.getMetaTileEntity().onPlacement();
+            holder.getMetaTileEntity().setFrontFacing(frontSide);
+            return where(symbol, new BlockInfo(tileEntity.getBlock().getDefaultState(), holder));
+        }
+
+        /**
+         * @param partSupplier Should supply either a MetaTileEntity or an IBlockState.
+         */
+        public Builder where(char symbol, Supplier partSupplier, EnumFacing frontSideIfTE) {
+            Object part = partSupplier.get();
+            if (part instanceof IBlockState) {
+                return where(symbol, (IBlockState) part);
+            } else if (part instanceof MetaTileEntity) {
+                return where(symbol, (MetaTileEntity) part, frontSideIfTE);
+            } else throw new IllegalArgumentException(
+                    "Supplier must supply either a MetaTileEntity or an IBlockState! Actual: " + part.getClass());
+        }
+
+        protected void validateMissingValues() {
+            List chars = new ArrayList<>();
+
+            for (Char2ObjectMap.Entry entry : symbolMap.char2ObjectEntrySet()) {
+                if (entry.getValue() == null) {
+                    chars.add(entry.getCharKey());
+                }
+            }
+
+            if (!chars.isEmpty()) {
+                throw new IllegalStateException(
+                        "Predicates for character(s) " + COMMA_JOIN.join(chars) + " are missing");
+            }
+        }
+
+        public PreviewBlockPattern build() {
+            validateMissingValues();
+            dimensions[0] = aisles.size();
+            return new PreviewBlockPattern(aisles.toArray(new PatternAisle[0]), dimensions, directions, offset, symbolMap);
+        }
+    }
+}
diff --git a/src/test/java/gregtech/api/util/TierByVoltageTest.java b/src/test/java/gregtech/api/util/TierByVoltageTest.java
index c31be3f2013..caaa40d7998 100644
--- a/src/test/java/gregtech/api/util/TierByVoltageTest.java
+++ b/src/test/java/gregtech/api/util/TierByVoltageTest.java
@@ -4,8 +4,6 @@
 
 import net.minecraft.util.EnumFacing;
 
-import net.minecraft.util.math.BlockPos;
-
 import org.junit.jupiter.api.Test;
 
 import static gregtech.api.GTValues.*;
@@ -19,15 +17,16 @@ public class TierByVoltageTest {
     public void temp() {
         // todo remove this
         int iters = 0;
-        for (GreggyBlockPos a : GreggyBlockPos.allInBox(new GreggyBlockPos(0, 0, 0), new GreggyBlockPos(2, 3, 4), EnumFacing.NORTH, EnumFacing.UP, EnumFacing.WEST)) {
+        for (GreggyBlockPos a : GreggyBlockPos.allInBox(new GreggyBlockPos(0, 0, 0), new GreggyBlockPos(2, 3, 4),
+                EnumFacing.NORTH, EnumFacing.UP, EnumFacing.WEST)) {
             System.out.println(a);
             iters++;
         }
 
-//        for (BlockPos a : BlockPos.getAllInBox(new BlockPos(0, 0, 0), new BlockPos(2, 3, 4))) {
-//            System.out.println(a.toString());
-//            iters++;
-//        }
+        // for (BlockPos a : BlockPos.getAllInBox(new BlockPos(0, 0, 0), new BlockPos(2, 3, 4))) {
+        // System.out.println(a.toString());
+        // iters++;
+        // }
 
         assertEquals(3 * 4 * 5, iters);
     }

From b0bdbd0a2ba2e7446e4537d3ea553c6012211f6a Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Tue, 16 Jul 2024 13:13:17 -0700
Subject: [PATCH 09/64] groundwork for hell

---
 .../multiblock/MultiblockControllerBase.java  |  28 ++-
 .../gregtech/api/pattern/BlockPattern.java    | 197 +-----------------
 .../gregtech/api/pattern/GreggyBlockPos.java  |   2 +-
 .../gregtech/api/pattern/PatternAisle.java    |   6 +
 .../api/pattern/PreviewBlockPattern.java      |  15 ++
 .../handler/MultiblockPreviewRenderer.java    |   3 +-
 .../MultiblockInfoRecipeWrapper.java          |   3 +-
 .../gregtech/api/util/TierByVoltageTest.java  |  22 +-
 8 files changed, 60 insertions(+), 216 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 0c39d676388..f99f79fe200 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -84,6 +84,11 @@ public abstract class MultiblockControllerBase extends MetaTileEntity implements
     @Nullable
     public BlockPattern structurePattern;
 
+    /**
+     * Null until the first time {@link MultiblockControllerBase#getMatchingShapes()} is called, if it is not overriden
+     */
+    protected PreviewBlockPattern defaultPattern;
+
     private final Map, List> multiblockAbilities = new HashMap<>();
     private final List multiblockParts = new ArrayList<>();
     private boolean structureFormed;
@@ -558,14 +563,6 @@ public boolean allowsFlip() {
 
     public List getMatchingShapes() {
         return Collections.emptyList();
-        // if (this.structurePattern == null) {
-        // this.reinitializeStructurePattern();
-        // if (this.structurePattern == null) {
-        // return Collections.emptyList();
-        // }
-        // }
-        // int[][] aisleRepetitions = this.structurePattern.aisleRepetitions;
-        // return repetitionDFS(new ArrayList<>(), aisleRepetitions, new Stack<>());
     }
 
     /**
@@ -577,6 +574,21 @@ public List getMatchingShapes() {
      */
     // todo add use for the keyMap with the multiblock builder
     public List getBuildableShapes(@Nullable Object2IntMap keyMap, boolean hatches) {
+        List infos = getMatchingShapes();
+
+        // if there is no overriden getMatchingShapes() just return the default one
+        if (infos.isEmpty()) {
+            if (defaultPattern == null) {
+                if (structurePattern == null) reinitializeStructurePattern();
+                if (structurePattern == null) return Collections.emptyList();
+
+                defaultPattern = structurePattern.getDefaultShape();
+            }
+
+            return Collections.singletonList(defaultPattern);
+        }
+
+        // otherwise just convert them all the preview block pattern and return
         return getMatchingShapes().stream().map(PreviewBlockPattern::new).collect(Collectors.toList());
     }
 
diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java
index c7d40b52dd1..f874691b559 100644
--- a/src/main/java/gregtech/api/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/BlockPattern.java
@@ -446,202 +446,9 @@ public void autoBuild(EntityPlayer player, MultiblockControllerBase controllerBa
         // });
     }
 
-    public BlockInfo[][][] getDefaultShape() {
+    public PreviewBlockPattern getDefaultShape() {
+        Math.floor(1);
         return null;
-        // Map cacheInfos = new HashMap<>();
-        // Map cacheGlobal = new HashMap<>();
-        // Map blocks = new HashMap<>();
-        // int minX = Integer.MAX_VALUE;
-        // int minY = Integer.MAX_VALUE;
-        // int minZ = Integer.MAX_VALUE;
-        // int maxX = Integer.MIN_VALUE;
-        // int maxY = Integer.MIN_VALUE;
-        // int maxZ = Integer.MIN_VALUE;
-        // for (int l = 0, x = 0; l < this.fingerLength; l++) {
-        // for (int r = 0; r < repetition[l]; r++) {
-        // // Checking single slice
-        // Map cacheLayer = new HashMap<>();
-        // for (int y = 0; y < this.thumbLength; y++) {
-        // for (int z = 0; z < this.palmLength; z++) {
-        // TraceabilityPredicate predicate = this.blockMatches[l][y][z];
-        // boolean find = false;
-        // BlockInfo[] infos = null;
-        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) { // check layer and
-        // // previewCount
-        // if (limit.minLayerCount > 0) {
-        // if (!cacheLayer.containsKey(limit)) {
-        // cacheLayer.put(limit, 1);
-        // } else if (cacheLayer.get(limit) < limit.minLayerCount) {
-        // cacheLayer.put(limit, cacheLayer.get(limit) + 1);
-        // } else {
-        // continue;
-        // }
-        // if (cacheGlobal.getOrDefault(limit, 0) < limit.previewCount) {
-        // if (!cacheGlobal.containsKey(limit)) {
-        // cacheGlobal.put(limit, 1);
-        // } else if (cacheGlobal.get(limit) < limit.previewCount) {
-        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-        // } else {
-        // continue;
-        // }
-        // }
-        // } else {
-        // continue;
-        // }
-        // if (!cacheInfos.containsKey(limit)) {
-        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-        // }
-        // infos = cacheInfos.get(limit);
-        // find = true;
-        // break;
-        // }
-        // if (!find) { // check global and previewCount
-        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-        // if (limit.minGlobalCount == -1 && limit.previewCount == -1) continue;
-        // if (cacheGlobal.getOrDefault(limit, 0) < limit.previewCount) {
-        // if (!cacheGlobal.containsKey(limit)) {
-        // cacheGlobal.put(limit, 1);
-        // } else if (cacheGlobal.get(limit) < limit.previewCount) {
-        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-        // } else {
-        // continue;
-        // }
-        // } else if (limit.minGlobalCount > 0) {
-        // if (!cacheGlobal.containsKey(limit)) {
-        // cacheGlobal.put(limit, 1);
-        // } else if (cacheGlobal.get(limit) < limit.minGlobalCount) {
-        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-        // } else {
-        // continue;
-        // }
-        // } else {
-        // continue;
-        // }
-        // if (!cacheInfos.containsKey(limit)) {
-        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-        // }
-        // infos = cacheInfos.get(limit);
-        // find = true;
-        // break;
-        // }
-        // }
-        // if (!find) { // check common with previewCount
-        // for (TraceabilityPredicate.SimplePredicate common : predicate.common) {
-        // if (common.previewCount > 0) {
-        // if (!cacheGlobal.containsKey(common)) {
-        // cacheGlobal.put(common, 1);
-        // } else if (cacheGlobal.get(common) < common.previewCount) {
-        // cacheGlobal.put(common, cacheGlobal.get(common) + 1);
-        // } else {
-        // continue;
-        // }
-        // } else {
-        // continue;
-        // }
-        // if (!cacheInfos.containsKey(common)) {
-        // cacheInfos.put(common, common.candidates == null ? null : common.candidates.get());
-        // }
-        // infos = cacheInfos.get(common);
-        // find = true;
-        // break;
-        // }
-        // }
-        // if (!find) { // check without previewCount
-        // for (TraceabilityPredicate.SimplePredicate common : predicate.common) {
-        // if (common.previewCount == -1) {
-        // if (!cacheInfos.containsKey(common)) {
-        // cacheInfos.put(common,
-        // common.candidates == null ? null : common.candidates.get());
-        // }
-        // infos = cacheInfos.get(common);
-        // find = true;
-        // break;
-        // }
-        // }
-        // }
-        // if (!find) { // check max
-        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-        // if (limit.previewCount != -1) {
-        // continue;
-        // } else if (limit.maxGlobalCount != -1 || limit.maxLayerCount != -1) {
-        // if (cacheGlobal.getOrDefault(limit, 0) < limit.maxGlobalCount) {
-        // if (!cacheGlobal.containsKey(limit)) {
-        // cacheGlobal.put(limit, 1);
-        // } else {
-        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-        // }
-        // } else if (cacheLayer.getOrDefault(limit, 0) < limit.maxLayerCount) {
-        // if (!cacheLayer.containsKey(limit)) {
-        // cacheLayer.put(limit, 1);
-        // } else {
-        // cacheLayer.put(limit, cacheLayer.get(limit) + 1);
-        // }
-        // } else {
-        // continue;
-        // }
-        // }
-        //
-        // if (!cacheInfos.containsKey(limit)) {
-        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-        // }
-        // infos = cacheInfos.get(limit);
-        // break;
-        // }
-        // }
-        // BlockInfo info = infos == null || infos.length == 0 ? BlockInfo.EMPTY : infos[0];
-        // BlockPos pos = setActualRelativeOffset(z, y, x, EnumFacing.NORTH, EnumFacing.UP, false);
-        // // TODO
-        // if (info.getTileEntity() instanceof MetaTileEntityHolder) {
-        // MetaTileEntityHolder holder = new MetaTileEntityHolder();
-        // holder.setMetaTileEntity(((MetaTileEntityHolder) info.getTileEntity()).getMetaTileEntity());
-        // holder.getMetaTileEntity().onPlacement();
-        // info = new BlockInfo(holder.getMetaTileEntity().getBlock().getDefaultState(), holder);
-        // }
-        // blocks.put(pos, info);
-        // minX = Math.min(pos.getX(), minX);
-        // minY = Math.min(pos.getY(), minY);
-        // minZ = Math.min(pos.getZ(), minZ);
-        // maxX = Math.max(pos.getX(), maxX);
-        // maxY = Math.max(pos.getY(), maxY);
-        // maxZ = Math.max(pos.getZ(), maxZ);
-        // }
-        // }
-        // x++;
-        // }
-        // }
-        // BlockInfo[][][] result = (BlockInfo[][][]) Array.newInstance(BlockInfo.class, maxX - minX + 1, maxY - minY +
-        // 1,
-        // maxZ - minZ + 1);
-        // int finalMinX = minX;
-        // int finalMinY = minY;
-        // int finalMinZ = minZ;
-        // blocks.forEach((pos, info) -> {
-        // if (info.getTileEntity() instanceof MetaTileEntityHolder) {
-        // MetaTileEntity metaTileEntity = ((MetaTileEntityHolder) info.getTileEntity()).getMetaTileEntity();
-        // boolean find = false;
-        // for (EnumFacing enumFacing : FACINGS) {
-        // if (metaTileEntity.isValidFrontFacing(enumFacing)) {
-        // if (!blocks.containsKey(pos.offset(enumFacing))) {
-        // metaTileEntity.setFrontFacing(enumFacing);
-        // find = true;
-        // break;
-        // }
-        // }
-        // }
-        // if (!find) {
-        // for (EnumFacing enumFacing : FACINGS) {
-        // BlockInfo blockInfo = blocks.get(pos.offset(enumFacing));
-        // if (blockInfo != null && blockInfo.getBlockState().getBlock() == Blocks.AIR &&
-        // metaTileEntity.isValidFrontFacing(enumFacing)) {
-        // metaTileEntity.setFrontFacing(enumFacing);
-        // break;
-        // }
-        // }
-        // }
-        // }
-        // result[pos.getX() - finalMinX][pos.getY() - finalMinY][pos.getZ() - finalMinZ] = info;
-        // });
-        // return result;
     }
 
     private GreggyBlockPos offsetFrom(GreggyBlockPos start, int aisleOffset, int stringOffset, int charOffset,
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index 502ebb8d423..d75617a80a4 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -83,7 +83,7 @@ public GreggyBlockPos set(EnumFacing.Axis axis, GreggyBlockPos other) {
     public GreggyBlockPos setAxisRelative(EnumFacing.Axis a1, EnumFacing.Axis a2, int p1, int p2, int p3) {
         set(a1, p1);
         set(a2, p2);
-        // the 3 ordinals add up to 3, so to find the third axis just subtract the other 2 from 3
+        // the 3 ordinals add up to 3, so to find the third axis just subtract the other 2 ordinals from 3
         pos[3 - a1.ordinal() - a2.ordinal()] = p3;
         return this;
     }
diff --git a/src/main/java/gregtech/api/pattern/PatternAisle.java b/src/main/java/gregtech/api/pattern/PatternAisle.java
index e86180eef9d..189c79a97b7 100644
--- a/src/main/java/gregtech/api/pattern/PatternAisle.java
+++ b/src/main/java/gregtech/api/pattern/PatternAisle.java
@@ -68,4 +68,10 @@ public int getStringCount() {
     public int getCharCount() {
         return pattern[0].length();
     }
+
+    public PatternAisle copy() {
+        PatternAisle clone = new PatternAisle(minRepeats, maxRepeats, pattern.clone());
+        clone.actualRepeats = this.actualRepeats;
+        return clone;
+    }
 }
diff --git a/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java b/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java
index f5c662e8532..6d07fdd3a92 100644
--- a/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java
@@ -124,6 +124,21 @@ public static Builder start(RelativeDirection aisleDir, RelativeDirection string
             return new Builder(aisleDir, stringDir, charDir);
         }
 
+        // protected because it doesn't do any dimension checks and just uses trust
+        protected Builder aisle(int repeats, PatternAisle aisle) {
+            for (String str : aisle.pattern) {
+                for (char c : str.toCharArray()) {
+                    if (!this.symbolMap.containsKey(c)) {
+                        this.symbolMap.put(c, null);
+                    }
+                }
+            }
+            PatternAisle copy = aisle.copy();
+            copy.actualRepeats = repeats;
+            aisles.add(copy);
+            return this;
+        }
+
         /**
          * Adds a new aisle to the builder.
          * @param repeats Amount of repeats.
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index d3531a43d66..8a45ebbb1d5 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -106,7 +106,8 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas
         previewFacing = controllerBase.getFrontFacing();
         BlockPos controllerPos = BlockPos.ORIGIN;
         MultiblockControllerBase mte = null;
-        BlockInfo[][][] blocks = shapeInfo.getBlocks();
+        // todo fix
+        BlockInfo[][][] blocks = null;
         Map blockMap = new HashMap<>();
         int maxY = 0;
         for (int x = 0; x < blocks.length; x++) {
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index efdf7d2d72f..5b70339494f 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -572,7 +572,8 @@ private static Collection gatherStructureBlocks(World world, @NotNull
     private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @NotNull Set parts) {
         Map blockMap = new HashMap<>();
         MultiblockControllerBase controllerBase = null;
-        BlockInfo[][][] blocks = shapeInfo.getBlocks();
+        BlockInfo[][][] blocks = null;
+        // todo fix
         for (int x = 0; x < blocks.length; x++) {
             BlockInfo[][] aisle = blocks[x];
             for (int y = 0; y < aisle.length; y++) {
diff --git a/src/test/java/gregtech/api/util/TierByVoltageTest.java b/src/test/java/gregtech/api/util/TierByVoltageTest.java
index caaa40d7998..7b715f3419d 100644
--- a/src/test/java/gregtech/api/util/TierByVoltageTest.java
+++ b/src/test/java/gregtech/api/util/TierByVoltageTest.java
@@ -4,6 +4,8 @@
 
 import net.minecraft.util.EnumFacing;
 
+import net.minecraft.util.math.BlockPos;
+
 import org.junit.jupiter.api.Test;
 
 import static gregtech.api.GTValues.*;
@@ -17,16 +19,16 @@ public class TierByVoltageTest {
     public void temp() {
         // todo remove this
         int iters = 0;
-        for (GreggyBlockPos a : GreggyBlockPos.allInBox(new GreggyBlockPos(0, 0, 0), new GreggyBlockPos(2, 3, 4),
-                EnumFacing.NORTH, EnumFacing.UP, EnumFacing.WEST)) {
-            System.out.println(a);
-            iters++;
-        }
-
-        // for (BlockPos a : BlockPos.getAllInBox(new BlockPos(0, 0, 0), new BlockPos(2, 3, 4))) {
-        // System.out.println(a.toString());
-        // iters++;
-        // }
+//        for (GreggyBlockPos a : GreggyBlockPos.allInBox(new GreggyBlockPos(0, 0, 0), new GreggyBlockPos(2, 3, 4),
+//                EnumFacing.NORTH, EnumFacing.UP, EnumFacing.WEST)) {
+//            System.out.println(a);
+//            iters++;
+//        }
+
+         for (BlockPos.MutableBlockPos a : BlockPos.getAllInBoxMutable(new BlockPos(0, 0, 0), new BlockPos(2, 3, 4))) {
+         System.out.println(a.toString());
+         iters++;
+         }
 
         assertEquals(3 * 4 * 5, iters);
     }

From f7e76e6d9a208a308317da3528cc66b4b7418e17 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Tue, 16 Jul 2024 16:55:07 -0700
Subject: [PATCH 10/64] time for hell

---
 .../multiblock/MultiblockControllerBase.java  | 157 ++++++++++++------
 .../gregtech/api/pattern/BlockPattern.java    |  27 ++-
 .../api/pattern/MultiblockShapeInfo.java      |   1 -
 .../api/pattern/PreviewBlockPattern.java      |  21 ++-
 .../gregtech/api/util/TierByVoltageTest.java  |  24 ++-
 5 files changed, 159 insertions(+), 71 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index f99f79fe200..40bfadf9fb5 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -27,7 +27,8 @@
 import gregtech.client.renderer.texture.cube.SimpleOrientedCubeRenderer;
 import gregtech.common.blocks.MetaBlocks;
 
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 
 import net.minecraft.block.Block;
 import net.minecraft.block.state.IBlockState;
@@ -54,6 +55,7 @@
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
 import codechicken.lib.vec.Rotation;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.jetbrains.annotations.ApiStatus;
@@ -71,7 +73,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.Stack;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -81,8 +82,8 @@
 
 public abstract class MultiblockControllerBase extends MetaTileEntity implements IMultiblockController {
 
-    @Nullable
-    public BlockPattern structurePattern;
+    // array is not null, but elements can be null
+    protected @Nullable BlockPattern @NotNull [] structurePatterns = new BlockPattern[64];
 
     /**
      * Null until the first time {@link MultiblockControllerBase#getMatchingShapes()} is called, if it is not overriden
@@ -91,10 +92,13 @@ public abstract class MultiblockControllerBase extends MetaTileEntity implements
 
     private final Map, List> multiblockAbilities = new HashMap<>();
     private final List multiblockParts = new ArrayList<>();
-    private boolean structureFormed;
+//    private boolean structureFormed;
 
     protected EnumFacing upwardsFacing = EnumFacing.NORTH;
-    protected boolean isFlipped;
+    // todo unplaceholder value
+    protected boolean[] isFlipped = new boolean[64];
+    protected boolean[] structuresFormed = new boolean[64];
+    protected long lastStructureFormedState = 0;
 
     public MultiblockControllerBase(ResourceLocation metaTileEntityId) {
         super(metaTileEntityId);
@@ -107,7 +111,8 @@ public void onPlacement(EntityLivingBase placer) {
     }
 
     public void reinitializeStructurePattern() {
-        this.structurePattern = createStructurePattern();
+        createStructurePatterns();
+        validateStructurePatterns();
     }
 
     @Override
@@ -136,6 +141,29 @@ public void update() {
     @NotNull
     protected abstract BlockPattern createStructurePattern();
 
+    /**
+     * Populate the structurePatterns array with structure patterns, values can be null. Doing this prevents
+     * frequent new BlockPatterns from being made.
+     */
+    protected void createStructurePatterns() {
+        structurePatterns[0] = createStructurePattern();
+    }
+
+    private void validateStructurePatterns() {
+        IntList failures = new IntArrayList();
+
+        for (int i = 1; i < structurePatterns.length; i++) {
+            //noinspection DataFlowIssue
+            if (structurePatterns[i] != null && !structurePatterns[i].hasStartOffset()) {
+                failures.add(i);
+            }
+        }
+
+        if (!failures.isEmpty()) {
+            throw new IllegalStateException("Structure patterns " + Arrays.toString(failures.toArray()) + " didn't have a manually set start offset");
+        }
+    }
+
     public EnumFacing getUpwardsFacing() {
         return upwardsFacing;
     }
@@ -152,26 +180,28 @@ public void setUpwardsFacing(EnumFacing upwardsFacing) {
                 notifyBlockUpdate();
                 markDirty();
                 writeCustomData(UPDATE_UPWARDS_FACING, buf -> buf.writeByte(upwardsFacing.getIndex()));
-                if (structurePattern != null) {
-                    structurePattern.clearCache();
-                    checkStructurePattern();
+                for (BlockPattern pattern : structurePatterns) {
+                    if (pattern != null) pattern.clearCache();
                 }
+                checkStructurePattern();
             }
         }
     }
 
     public boolean isFlipped() {
-        return isFlipped;
+        return isFlipped[0];
     }
 
     /** Should not be called outside of structure formation logic! */
     @ApiStatus.Internal
-    protected void setFlipped(boolean isFlipped) {
-        if (this.isFlipped != isFlipped) {
-            this.isFlipped = isFlipped;
+    protected void setFlipped(boolean flipped, int index) {
+        if (index >= 64) throw new IllegalArgumentException("Max structure count of 64, dont @ me");
+        boolean flip = isFlipped[index];
+        if (flip != flipped) {
+            isFlipped[index] = flipped;
             notifyBlockUpdate();
             markDirty();
-            writeCustomData(UPDATE_FLIP, buf -> buf.writeBoolean(isFlipped));
+            writeCustomData(UPDATE_FLIP, buf -> buf.writeLong(GTUtility.boolArrToLong(isFlipped)));
         }
     }
 
@@ -341,14 +371,36 @@ protected Function multiblockPartSorter() {
         return BlockPos::hashCode;
     }
 
+    /**
+     * Whether a structure at the index should be checked. The check is only performed if this returns true and the structure is not null.
+     * @param index The index, with 0 being the main structure
+     * @return True if the structure should be checked
+     */
+    protected boolean shouldCheckStructure(int index) {
+        return true;
+    }
+
     public void checkStructurePattern() {
-        if (structurePattern == null) return;
+
+    }
+
+    public void checkStructurePatterns() {
+        for (int i = 0; i < structurePatterns.length; i++) {
+            checkStructurePattern(i);
+        }
+    }
+
+    public void checkStructurePattern(int index) {
+        BlockPattern pattern = structurePatterns[index];
+        if (pattern == null || !shouldCheckStructure(index)) return;
+
         long time = System.nanoTime();
-        PatternMatchContext context = structurePattern.checkPatternFastAt(getWorld(), getPos(),
+        PatternMatchContext context = pattern.checkPatternFastAt(getWorld(), getPos(),
                 getFrontFacing().getOpposite(), getUpwardsFacing(), allowsFlip());
         System.out.println(
                 "structure check for " + getClass().getSimpleName() + " took " + (System.nanoTime() - time) + " nanos");
-        if (context != null && !structureFormed) {
+
+        if (context != null && !structuresFormed[index]) {
             Set rawPartsSet = context.getOrCreate("MultiblockParts", HashSet::new);
             ArrayList parts = new ArrayList<>(rawPartsSet);
             for (IMultiblockPart part : parts) {
@@ -358,7 +410,8 @@ public void checkStructurePattern() {
                     }
                 }
             }
-            this.setFlipped(context.neededFlip());
+            this.setFlipped(context.neededFlip(), index);
+
             parts.sort(Comparator.comparing(it -> multiblockPartSorter().apply(((MetaTileEntity) it).getPos())));
             Map, List> abilities = new HashMap<>();
             for (IMultiblockPart multiblockPart : parts) {
@@ -370,18 +423,19 @@ public void checkStructurePattern() {
                     abilityPart.registerAbilities(abilityInstancesList);
                 }
             }
+
             this.multiblockParts.addAll(parts);
             this.multiblockAbilities.putAll(abilities);
             parts.forEach(part -> part.addToMultiBlock(this));
-            this.structureFormed = true;
+            this.structuresFormed[index] = true;
             writeCustomData(STRUCTURE_FORMED, buf -> buf.writeBoolean(true));
             formStructure(context);
-        } else if (context == null && structureFormed) {
+        } else if (context == null && structuresFormed[index]) {
             invalidateStructure();
         } else if (context != null) {
             // ensure flip is ok, possibly not necessary but good to check just in case
             if (context.neededFlip() != isFlipped()) {
-                setFlipped(context.neededFlip());
+                setFlipped(context.neededFlip(), index);
             }
         }
     }
@@ -392,15 +446,21 @@ public void invalidateStructure() {
         this.multiblockParts.forEach(part -> part.removeFromMultiBlock(this));
         this.multiblockAbilities.clear();
         this.multiblockParts.clear();
-        this.structureFormed = false;
-        this.setFlipped(false);
+        Arrays.fill(structuresFormed, false);
+        Arrays.fill(isFlipped, false);
         writeCustomData(STRUCTURE_FORMED, buf -> buf.writeBoolean(false));
     }
 
+    protected void invalidStructureCaches() {
+        for (BlockPattern pattern : structurePatterns) {
+            if (pattern != null) pattern.clearCache();
+        }
+    }
+
     @Override
     public void onRemoval() {
         super.onRemoval();
-        if (!getWorld().isRemote && structureFormed) {
+        if (!getWorld().isRemote) {
             invalidateStructure();
         }
     }
@@ -422,7 +482,7 @@ public void readFromNBT(NBTTagCompound data) {
             this.upwardsFacing = EnumFacing.VALUES[data.getByte("UpwardsFacing")];
         }
         if (data.hasKey("IsFlipped")) {
-            this.isFlipped = data.getBoolean("IsFlipped");
+            GTUtility.longToBoolArr(data.getLong("IsFlipped"), isFlipped);
         }
         this.reinitializeStructurePattern();
     }
@@ -431,39 +491,40 @@ public void readFromNBT(NBTTagCompound data) {
     public NBTTagCompound writeToNBT(NBTTagCompound data) {
         super.writeToNBT(data);
         data.setByte("UpwardsFacing", (byte) upwardsFacing.getIndex());
-        data.setBoolean("IsFlipped", isFlipped);
+        data.setLong("IsFlipped", GTUtility.boolArrToLong(isFlipped));
         return data;
     }
 
     @Override
     public void writeInitialSyncData(PacketBuffer buf) {
         super.writeInitialSyncData(buf);
-        buf.writeBoolean(structureFormed);
         buf.writeByte(upwardsFacing.getIndex());
-        buf.writeBoolean(isFlipped);
+        buf.writeLong(GTUtility.boolArrToLong(structuresFormed));
+        buf.writeLong(GTUtility.boolArrToLong(isFlipped));
     }
 
     @Override
     public void receiveInitialSyncData(PacketBuffer buf) {
         super.receiveInitialSyncData(buf);
-        this.structureFormed = buf.readBoolean();
         this.upwardsFacing = EnumFacing.VALUES[buf.readByte()];
-        this.isFlipped = buf.readBoolean();
+        GTUtility.longToBoolArr(buf.readLong(), structuresFormed);
+        GTUtility.longToBoolArr(buf.readLong(), isFlipped);
     }
 
     @Override
     public void receiveCustomData(int dataId, PacketBuffer buf) {
         super.receiveCustomData(dataId, buf);
-        if (dataId == STRUCTURE_FORMED) {
-            this.structureFormed = buf.readBoolean();
-            if (!structureFormed) {
-                GregTechAPI.soundManager.stopTileSound(getPos());
-            }
-        } else if (dataId == UPDATE_UPWARDS_FACING) {
+        if (dataId == UPDATE_UPWARDS_FACING) {
             this.upwardsFacing = EnumFacing.VALUES[buf.readByte()];
             scheduleRenderUpdate();
-        } else if (dataId == UPDATE_FLIP) {
-            this.isFlipped = buf.readBoolean();
+        } else if (dataId == STRUCTURE_FORMED) {
+            GTUtility.longToBoolArr(buf.readLong(), structuresFormed);
+            if (!isStructureFormed()) {
+                GregTechAPI.soundManager.stopTileSound(getPos());
+            }
+        }
+        else if (dataId == UPDATE_FLIP) {
+            GTUtility.longToBoolArr(buf.readLong(), isFlipped);
         }
     }
 
@@ -479,7 +540,7 @@ public  T getCapability(Capability capability, EnumFacing side) {
     }
 
     public boolean isStructureFormed() {
-        return structureFormed;
+        return structuresFormed[0];
     }
 
     @Override
@@ -494,10 +555,10 @@ public void setFrontFacing(EnumFacing frontFacing) {
             setUpwardsFacing(newUpwardsFacing);
         }
 
-        if (getWorld() != null && !getWorld().isRemote && structurePattern != null) {
+        if (getWorld() != null && !getWorld().isRemote) {
             // clear cache since the cache has no concept of pre-existing facing
             // for the controller block (or any block) in the structure
-            structurePattern.clearCache();
+            invalidStructureCaches();
             // recheck structure pattern immediately to avoid a slight "lag"
             // on deforming when rotating a multiblock controller
             checkStructurePattern();
@@ -567,8 +628,10 @@ public List getMatchingShapes() {
 
     /**
      * The new(and better) way of getting shapes for in world, jei, and autobuild. Default impl just converts
-     * {@link MultiblockControllerBase#getMatchingShapes()} to this
-     * @param keyMap A map for autobuild, or null if it is an in world or jei preview.
+     * {@link MultiblockControllerBase#getMatchingShapes()}, if not empty, to this. If getMatchingShapes is empty, uses
+     * a default generated structure pattern, it's not very good which is why you should override this.
+     * 
+     * @param keyMap  A map for autobuild, or null if it is an in world or jei preview.
      * @param hatches This is whether you should put hatches, JEI previews need hatches, but autobuild and in world
      *                previews shouldn't(unless the hatch is necessary and only has one valid spot, such as EBF)
      */
@@ -579,10 +642,10 @@ public List getBuildableShapes(@Nullable Object2IntMap predicates;
     protected final StructureInfo info;
@@ -61,6 +66,7 @@ public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] di
         this.dimensions = dimensions;
         this.structureDir = directions;
         this.predicates = predicates;
+        hasStartOffset = startOffset != null;
 
         if (startOffset == null) {
             this.startOffset = new int[3];
@@ -79,6 +85,8 @@ public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] di
      * @param center The center char to look for
      */
     private void legacyStartOffset(char center) {
+        // don't do anything if center char isn't specified, this allows MultiblockControllerBase#validateStructurePatterns to do its thing while not logging an error here
+        if (center == 0) return;
         // could also use aisles.length but this is cooler
         for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
             int[] result = aisles[aisleI].firstInstanceOf(center);
@@ -90,7 +98,7 @@ private void legacyStartOffset(char center) {
             }
         }
 
-        System.out.println("FAILED TO FIND PREDICATE");
+        throw new IllegalStateException("Failed to find center char: '" + center + "'");
     }
 
     public PatternError getError() {
@@ -447,10 +455,25 @@ public void autoBuild(EntityPlayer player, MultiblockControllerBase controllerBa
     }
 
     public PreviewBlockPattern getDefaultShape() {
-        Math.floor(1);
+        char[][][] pattern = new char[dimensions[2]][dimensions[1]][dimensions[0]];
+
+        for (PatternAisle aisle : aisles) {
+            char[][] resultAisle = new char[dimensions[2]][dimensions[1]];
+
+            for (String str : aisle.pattern) {
+                for (char c : str.toCharArray()) {
+                    TraceabilityPredicate predicate =
+                }
+            }
+        }
+
         return null;
     }
 
+    public boolean hasStartOffset() {
+        return hasStartOffset;
+    }
+
     private GreggyBlockPos offsetFrom(GreggyBlockPos start, int aisleOffset, int stringOffset, int charOffset,
                                       @NotNull EnumFacing frontFacing,
                                       @NotNull EnumFacing upFacing, boolean flip) {
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index 9dec704f2ec..ca0e72e590c 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -10,7 +10,6 @@
 
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
-import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java b/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java
index 6d07fdd3a92..3cabec44d9c 100644
--- a/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java
@@ -43,12 +43,14 @@ public PreviewBlockPattern(MultiblockShapeInfo info) {
         structureDir = new RelativeDirection[] { RelativeDirection.BACK, RelativeDirection.UP,
                 RelativeDirection.RIGHT };
         this.startOffset = new int[3];
-        // i am lazy so hopefully addons follow the convention of using 'S' for their self predicate(aka center predicate)
+        // i am lazy so hopefully addons follow the convention of using 'S' for their self predicate(aka center
+        // predicate)
         legacyStartOffset('S');
         this.symbols = info.symbols;
     }
 
-    public PreviewBlockPattern(PatternAisle[] aisles, int[] dimensions, RelativeDirection[] directions, int[] startOffset, Char2ObjectMap symbols) {
+    public PreviewBlockPattern(PatternAisle[] aisles, int[] dimensions, RelativeDirection[] directions,
+                               int[] startOffset, Char2ObjectMap symbols) {
         this.aisles = aisles;
         this.dimensions = dimensions;
         this.structureDir = directions;
@@ -85,6 +87,7 @@ public static class Builder {
 
         /**
          * Way the builder progresses
+         * 
          * @see FactoryBlockPattern
          */
         protected final RelativeDirection[] directions = new RelativeDirection[3];
@@ -107,7 +110,8 @@ protected Builder(RelativeDirection aisleDir, RelativeDirection stringDir, Relat
         }
 
         /**
-         * Same as calling {@link Builder#start(RelativeDirection, RelativeDirection, RelativeDirection)} with BACK, UP, RIGHT
+         * Same as calling {@link Builder#start(RelativeDirection, RelativeDirection, RelativeDirection)} with BACK, UP,
+         * RIGHT
          */
         public static Builder start() {
             return new Builder(RelativeDirection.BACK, RelativeDirection.UP, RelativeDirection.RIGHT);
@@ -115,9 +119,10 @@ public static Builder start() {
 
         /**
          * Starts the builder, each pair of relative directions must be used exactly once!
-         * @param aisleDir The direction for aisles to advance in.
+         * 
+         * @param aisleDir  The direction for aisles to advance in.
          * @param stringDir The direction for strings to advance in.
-         * @param charDir The direction for chars to advance in.
+         * @param charDir   The direction for chars to advance in.
          */
         public static Builder start(RelativeDirection aisleDir, RelativeDirection stringDir,
                                     RelativeDirection charDir) {
@@ -141,8 +146,9 @@ protected Builder aisle(int repeats, PatternAisle aisle) {
 
         /**
          * Adds a new aisle to the builder.
+         * 
          * @param repeats Amount of repeats.
-         * @param aisle The aisle.
+         * @param aisle   The aisle.
          */
         public Builder aisle(int repeats, String... aisle) {
             // straight up copied from factory block pattern's code
@@ -240,7 +246,8 @@ protected void validateMissingValues() {
         public PreviewBlockPattern build() {
             validateMissingValues();
             dimensions[0] = aisles.size();
-            return new PreviewBlockPattern(aisles.toArray(new PatternAisle[0]), dimensions, directions, offset, symbolMap);
+            return new PreviewBlockPattern(aisles.toArray(new PatternAisle[0]), dimensions, directions, offset,
+                    symbolMap);
         }
     }
 }
diff --git a/src/test/java/gregtech/api/util/TierByVoltageTest.java b/src/test/java/gregtech/api/util/TierByVoltageTest.java
index 7b715f3419d..4c4e920d6a6 100644
--- a/src/test/java/gregtech/api/util/TierByVoltageTest.java
+++ b/src/test/java/gregtech/api/util/TierByVoltageTest.java
@@ -1,9 +1,5 @@
 package gregtech.api.util;
 
-import gregtech.api.pattern.GreggyBlockPos;
-
-import net.minecraft.util.EnumFacing;
-
 import net.minecraft.util.math.BlockPos;
 
 import org.junit.jupiter.api.Test;
@@ -19,16 +15,16 @@ public class TierByVoltageTest {
     public void temp() {
         // todo remove this
         int iters = 0;
-//        for (GreggyBlockPos a : GreggyBlockPos.allInBox(new GreggyBlockPos(0, 0, 0), new GreggyBlockPos(2, 3, 4),
-//                EnumFacing.NORTH, EnumFacing.UP, EnumFacing.WEST)) {
-//            System.out.println(a);
-//            iters++;
-//        }
-
-         for (BlockPos.MutableBlockPos a : BlockPos.getAllInBoxMutable(new BlockPos(0, 0, 0), new BlockPos(2, 3, 4))) {
-         System.out.println(a.toString());
-         iters++;
-         }
+        // for (GreggyBlockPos a : GreggyBlockPos.allInBox(new GreggyBlockPos(0, 0, 0), new GreggyBlockPos(2, 3, 4),
+        // EnumFacing.NORTH, EnumFacing.UP, EnumFacing.WEST)) {
+        // System.out.println(a);
+        // iters++;
+        // }
+
+        for (BlockPos.MutableBlockPos a : BlockPos.getAllInBoxMutable(new BlockPos(0, 0, 0), new BlockPos(2, 3, 4))) {
+            System.out.println(a.toString());
+            iters++;
+        }
 
         assertEquals(3 * 4 * 5, iters);
     }

From 0f21cf7dd8b991b95502d0e8123d8e2e13ce22d2 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Tue, 16 Jul 2024 17:02:03 -0700
Subject: [PATCH 11/64] free from terminal

---
 .../java/gregtech/api/pattern/BlockPattern.java    |  2 +-
 .../multiblock/MultiblockInfoRecipeWrapper.java    | 14 +++++++-------
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java
index d5bff85db51..f5947ba64e7 100644
--- a/src/main/java/gregtech/api/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/BlockPattern.java
@@ -462,7 +462,7 @@ public PreviewBlockPattern getDefaultShape() {
 
             for (String str : aisle.pattern) {
                 for (char c : str.toCharArray()) {
-                    TraceabilityPredicate predicate =
+//                    TraceabilityPredicate predicate =
                 }
             }
         }
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 5b70339494f..2c68ffe3973 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -618,13 +618,13 @@ private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @Not
 
         Map predicateMap = new HashMap<>();
         if (controllerBase != null) {
-            if (controllerBase.structurePattern == null) {
-                controllerBase.reinitializeStructurePattern();
-            }
-            if (controllerBase.structurePattern != null) {
-                controllerBase.structurePattern.cache.forEach((pos, blockInfo) -> predicateMap
-                        .put(BlockPos.fromLong(pos), (TraceabilityPredicate) blockInfo.getInfo()));
-            }
+//            if (controllerBase.structurePattern == null) {
+//                controllerBase.reinitializeStructurePattern();
+//            }
+//            if (controllerBase.structurePattern != null) {
+//                controllerBase.structurePattern.cache.forEach((pos, blockInfo) -> predicateMap
+//                        .put(BlockPos.fromLong(pos), (TraceabilityPredicate) blockInfo.getInfo()));
+//            }
         }
 
         List sortedParts = gatherStructureBlocks(worldSceneRenderer.world, blockMap, parts).stream()

From 47a7737d87aa84b72c2d692b4641e35e39a48978 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Tue, 16 Jul 2024 17:16:08 -0700
Subject: [PATCH 12/64] shoutout to crtl-slash i love you

---
 .../multiblock/MultiblockControllerBase.java  |  23 ++--
 .../gregtech/api/pattern/BlockPattern.java    |   5 +-
 .../behaviors/MultiblockBuilderBehavior.java  |  16 +--
 .../AdvancedMonitorPluginBehavior.java        | 118 +++++++++---------
 .../monitorplugin/FakeGuiPluginBehavior.java  |  57 ++++-----
 .../MetaTileEntityDistillationTower.java      |   4 +-
 .../electric/MetaTileEntityVacuumFreezer.java |  10 ++
 .../MultiblockInfoRecipeWrapper.java          |  14 +--
 8 files changed, 129 insertions(+), 118 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 40bfadf9fb5..85697bf6c0f 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -27,9 +27,6 @@
 import gregtech.client.renderer.texture.cube.SimpleOrientedCubeRenderer;
 import gregtech.common.blocks.MetaBlocks;
 
-import it.unimi.dsi.fastutil.ints.IntArrayList;
-import it.unimi.dsi.fastutil.ints.IntList;
-
 import net.minecraft.block.Block;
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.renderer.texture.TextureAtlasSprite;
@@ -55,6 +52,8 @@
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
 import codechicken.lib.vec.Rotation;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.tuple.Pair;
@@ -92,7 +91,7 @@ public abstract class MultiblockControllerBase extends MetaTileEntity implements
 
     private final Map, List> multiblockAbilities = new HashMap<>();
     private final List multiblockParts = new ArrayList<>();
-//    private boolean structureFormed;
+    // private boolean structureFormed;
 
     protected EnumFacing upwardsFacing = EnumFacing.NORTH;
     // todo unplaceholder value
@@ -153,14 +152,15 @@ private void validateStructurePatterns() {
         IntList failures = new IntArrayList();
 
         for (int i = 1; i < structurePatterns.length; i++) {
-            //noinspection DataFlowIssue
+            // noinspection DataFlowIssue
             if (structurePatterns[i] != null && !structurePatterns[i].hasStartOffset()) {
                 failures.add(i);
             }
         }
 
         if (!failures.isEmpty()) {
-            throw new IllegalStateException("Structure patterns " + Arrays.toString(failures.toArray()) + " didn't have a manually set start offset");
+            throw new IllegalStateException("Structure patterns " + Arrays.toString(failures.toArray()) +
+                    " didn't have a manually set start offset");
         }
     }
 
@@ -372,7 +372,9 @@ protected Function multiblockPartSorter() {
     }
 
     /**
-     * Whether a structure at the index should be checked. The check is only performed if this returns true and the structure is not null.
+     * Whether a structure at the index should be checked. The check is only performed if this returns true and the
+     * structure is not null.
+     * 
      * @param index The index, with 0 being the main structure
      * @return True if the structure should be checked
      */
@@ -380,9 +382,7 @@ protected boolean shouldCheckStructure(int index) {
         return true;
     }
 
-    public void checkStructurePattern() {
-
-    }
+    public void checkStructurePattern() {}
 
     public void checkStructurePatterns() {
         for (int i = 0; i < structurePatterns.length; i++) {
@@ -522,8 +522,7 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
             if (!isStructureFormed()) {
                 GregTechAPI.soundManager.stopTileSound(getPos());
             }
-        }
-        else if (dataId == UPDATE_FLIP) {
+        } else if (dataId == UPDATE_FLIP) {
             GTUtility.longToBoolArr(buf.readLong(), isFlipped);
         }
     }
diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java
index f5947ba64e7..b415e980092 100644
--- a/src/main/java/gregtech/api/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/BlockPattern.java
@@ -85,7 +85,8 @@ public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] di
      * @param center The center char to look for
      */
     private void legacyStartOffset(char center) {
-        // don't do anything if center char isn't specified, this allows MultiblockControllerBase#validateStructurePatterns to do its thing while not logging an error here
+        // don't do anything if center char isn't specified, this allows
+        // MultiblockControllerBase#validateStructurePatterns to do its thing while not logging an error here
         if (center == 0) return;
         // could also use aisles.length but this is cooler
         for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
@@ -462,7 +463,7 @@ public PreviewBlockPattern getDefaultShape() {
 
             for (String str : aisle.pattern) {
                 for (char c : str.toCharArray()) {
-//                    TraceabilityPredicate predicate =
+                    // TraceabilityPredicate predicate =
                 }
             }
         }
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index c1148f5453b..b22915acfe0 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -44,20 +44,20 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
             if (!multiblock.isStructureFormed()) {
-                multiblock.structurePattern.autoBuild(player, multiblock);
+//                multiblock.structurePatterns[0].autoBuild(player, multiblock);
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
         } else {
             // If not sneaking, try to show structure debug info (if any) in chat.
             if (!multiblock.isStructureFormed()) {
-                PatternError error = multiblock.structurePattern.getError();
-                if (error != null) {
-                    player.sendMessage(
-                            new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header"));
-                    player.sendMessage(new TextComponentString(error.getErrorInfo()));
-                    return EnumActionResult.SUCCESS;
-                }
+//                PatternError error = multiblock.structurePatterns[0].getError();
+//                if (error != null) {
+//                    player.sendMessage(
+//                            new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header"));
+//                    player.sendMessage(new TextComponentString(error.getErrorInfo()));
+//                    return EnumActionResult.SUCCESS;
+//                }
             }
             player.sendMessage(new TextComponentTranslation("gregtech.multiblock.pattern.no_errors")
                     .setStyle(new Style().setColor(TextFormatting.GREEN)));
diff --git a/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
index 7c3da2f0166..70d835a3730 100644
--- a/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
@@ -219,65 +219,65 @@ public void setConfig(float scale, int rY, int rX, float spin, boolean connect)
 
     @Override
     public void update() {
-        super.update();
-        if (this.screen.getOffsetTimer() % 20 == 0) {
-            if (this.screen.getWorld().isRemote) { // check connections
-                if (worldSceneRenderer == null && validPos != null && validPos.size() > 0) {
-                    createWorldScene();
-                }
-                if (this.connect && worldSceneRenderer != null &&
-                        this.screen.getController() instanceof MetaTileEntityCentralMonitor) {
-                    if (connections == null) connections = new HashMap<>();
-                    connections.clear();
-                    for (MetaTileEntityMonitorScreen[] monitorScreens : ((MetaTileEntityCentralMonitor) this.screen
-                            .getController()).screens) {
-                        for (MetaTileEntityMonitorScreen screen : monitorScreens) {
-                            if (screen != null && screen.plugin instanceof FakeGuiPluginBehavior &&
-                                    ((FakeGuiPluginBehavior) screen.plugin).getHolder() == this.holder) {
-                                MetaTileEntity met = ((FakeGuiPluginBehavior) screen.plugin).getRealMTE();
-                                if (met != null) {
-                                    BlockPos pos = met.getPos();
-                                    Pair, Vector3f> tuple = connections
-                                            .getOrDefault(pos, new MutablePair<>(new ArrayList<>(), null));
-                                    tuple.getLeft().add(screen);
-                                    connections.put(pos, tuple);
-                                }
-                            }
-                        }
-                    }
-                }
-            } else { // check multi-block valid
-                if (holder != null && holder.getMetaTileEntity() instanceof MultiblockControllerBase) {
-                    MultiblockControllerBase entity = (MultiblockControllerBase) holder.getMetaTileEntity();
-                    if (entity.isStructureFormed()) {
-                        if (!isValid) {
-                            PatternMatchContext result = entity.structurePattern.checkPatternFastAt(
-                                    entity.getWorld(), entity.getPos(), entity.getFrontFacing().getOpposite(),
-                                    entity.getUpwardsFacing(), entity.allowsFlip());
-                            if (result != null) {
-                                validPos = entity.structurePattern.cache.keySet().stream().map(BlockPos::fromLong)
-                                        .collect(Collectors.toSet());
-                                writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> {
-                                    buf.writeVarInt(validPos.size());
-                                    for (BlockPos pos : validPos) {
-                                        buf.writeBlockPos(pos);
-                                    }
-                                });
-                                isValid = true;
-                            } else {
-                                validPos = Collections.emptySet();
-                            }
-                        }
-                    } else if (isValid) {
-                        writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> buf.writeVarInt(0));
-                        isValid = false;
-                    }
-                }
-            }
-        }
-        if (this.screen.getWorld().isRemote && spin > 0 && lastMouse == null) {
-            rotationPitch = (int) ((rotationPitch + spin * 4) % 360);
-        }
+//        super.update();
+//        if (this.screen.getOffsetTimer() % 20 == 0) {
+//            if (this.screen.getWorld().isRemote) { // check connections
+//                if (worldSceneRenderer == null && validPos != null && validPos.size() > 0) {
+//                    createWorldScene();
+//                }
+//                if (this.connect && worldSceneRenderer != null &&
+//                        this.screen.getController() instanceof MetaTileEntityCentralMonitor) {
+//                    if (connections == null) connections = new HashMap<>();
+//                    connections.clear();
+//                    for (MetaTileEntityMonitorScreen[] monitorScreens : ((MetaTileEntityCentralMonitor) this.screen
+//                            .getController()).screens) {
+//                        for (MetaTileEntityMonitorScreen screen : monitorScreens) {
+//                            if (screen != null && screen.plugin instanceof FakeGuiPluginBehavior &&
+//                                    ((FakeGuiPluginBehavior) screen.plugin).getHolder() == this.holder) {
+//                                MetaTileEntity met = ((FakeGuiPluginBehavior) screen.plugin).getRealMTE();
+//                                if (met != null) {
+//                                    BlockPos pos = met.getPos();
+//                                    Pair, Vector3f> tuple = connections
+//                                            .getOrDefault(pos, new MutablePair<>(new ArrayList<>(), null));
+//                                    tuple.getLeft().add(screen);
+//                                    connections.put(pos, tuple);
+//                                }
+//                            }
+//                        }
+//                    }
+//                }
+//            } else { // check multi-block valid
+//                if (holder != null && holder.getMetaTileEntity() instanceof MultiblockControllerBase) {
+//                    MultiblockControllerBase entity = (MultiblockControllerBase) holder.getMetaTileEntity();
+//                    if (entity.isStructureFormed()) {
+//                        if (!isValid) {
+//                            PatternMatchContext result = entity.structurePattern.checkPatternFastAt(
+//                                    entity.getWorld(), entity.getPos(), entity.getFrontFacing().getOpposite(),
+//                                    entity.getUpwardsFacing(), entity.allowsFlip());
+//                            if (result != null) {
+//                                validPos = entity.structurePattern.cache.keySet().stream().map(BlockPos::fromLong)
+//                                        .collect(Collectors.toSet());
+//                                writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> {
+//                                    buf.writeVarInt(validPos.size());
+//                                    for (BlockPos pos : validPos) {
+//                                        buf.writeBlockPos(pos);
+//                                    }
+//                                });
+//                                isValid = true;
+//                            } else {
+//                                validPos = Collections.emptySet();
+//                            }
+//                        }
+//                    } else if (isValid) {
+//                        writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> buf.writeVarInt(0));
+//                        isValid = false;
+//                    }
+//                }
+//            }
+//        }
+//        if (this.screen.getWorld().isRemote && spin > 0 && lastMouse == null) {
+//            rotationPitch = (int) ((rotationPitch + spin * 4) % 360);
+//        }
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/items/behaviors/monitorplugin/FakeGuiPluginBehavior.java b/src/main/java/gregtech/common/items/behaviors/monitorplugin/FakeGuiPluginBehavior.java
index 3328ee49a26..012bcbf4570 100644
--- a/src/main/java/gregtech/common/items/behaviors/monitorplugin/FakeGuiPluginBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/monitorplugin/FakeGuiPluginBehavior.java
@@ -66,34 +66,35 @@ public void setConfig(int partIndex) {
     }
 
     public MetaTileEntity getRealMTE() {
-        MetaTileEntity target = this.holder.getMetaTileEntity();
-        if (target instanceof MultiblockControllerBase multi && partIndex > 0) {
-            if (partPos != null) {
-                TileEntity entity = this.screen.getWorld().getTileEntity(partPos);
-                if (entity instanceof IGregTechTileEntity) {
-                    return ((IGregTechTileEntity) entity).getMetaTileEntity();
-                } else {
-                    partPos = null;
-                    return null;
-                }
-            }
-            PatternMatchContext context = multi.structurePattern.checkPatternFastAt(
-                    target.getWorld(), target.getPos(), target.getFrontFacing().getOpposite(), multi.getUpwardsFacing(),
-                    multi.allowsFlip());
-            if (context == null) {
-                return null;
-            }
-            Set rawPartsSet = context.getOrCreate("MultiblockParts", HashSet::new);
-            List parts = new ArrayList<>(rawPartsSet);
-            parts.sort(Comparator.comparing((it) -> ((MetaTileEntity) it).getPos().hashCode()));
-            if (parts.size() > partIndex - 1 && parts.get(partIndex - 1) instanceof MetaTileEntity) {
-                target = (MetaTileEntity) parts.get(partIndex - 1);
-                partPos = target.getPos();
-            } else {
-                return null;
-            }
-        }
-        return target;
+        return null;
+//        MetaTileEntity target = this.holder.getMetaTileEntity();
+//        if (target instanceof MultiblockControllerBase multi && partIndex > 0) {
+//            if (partPos != null) {
+//                TileEntity entity = this.screen.getWorld().getTileEntity(partPos);
+//                if (entity instanceof IGregTechTileEntity) {
+//                    return ((IGregTechTileEntity) entity).getMetaTileEntity();
+//                } else {
+//                    partPos = null;
+//                    return null;
+//                }
+//            }
+//            PatternMatchContext context = multi.structurePattern.checkPatternFastAt(
+//                    target.getWorld(), target.getPos(), target.getFrontFacing().getOpposite(), multi.getUpwardsFacing(),
+//                    multi.allowsFlip());
+//            if (context == null) {
+//                return null;
+//            }
+//            Set rawPartsSet = context.getOrCreate("MultiblockParts", HashSet::new);
+//            List parts = new ArrayList<>(rawPartsSet);
+//            parts.sort(Comparator.comparing((it) -> ((MetaTileEntity) it).getPos().hashCode()));
+//            if (parts.size() > partIndex - 1 && parts.get(partIndex - 1) instanceof MetaTileEntity) {
+//                target = (MetaTileEntity) parts.get(partIndex - 1);
+//                partPos = target.getPos();
+//            } else {
+//                return null;
+//            }
+//        }
+//        return target;
     }
 
     public void createFakeGui() {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
index 2c7eea8410a..c0a4be09a56 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
@@ -105,8 +105,8 @@ protected void addDisplayText(List textList) {
     @Override
     protected void formStructure(PatternMatchContext context) {
         super.formStructure(context);
-        if (this.handler == null || this.structurePattern == null) return;
-        handler.determineLayerCount(this.structurePattern);
+        if (this.handler == null || this.structurePatterns[0] == null) return;
+        handler.determineLayerCount(this.structurePatterns[0]);
         handler.determineOrderedFluidOutputs();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
index e5712863c7a..a580988c94b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
@@ -44,6 +44,16 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
                 .build();
     }
 
+    @Override
+    protected void createStructurePatterns() {
+        super.createStructurePatterns();
+        structurePatterns[1] = FactoryBlockPattern.start()
+                .aisle("X")
+                .where('X', states(getCasingState()))
+                .setStartOffset(5, 0, 0)
+                .build();
+    }
+
     @SideOnly(Side.CLIENT)
     @Override
     public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 2c68ffe3973..dfb837cbda9 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -618,13 +618,13 @@ private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @Not
 
         Map predicateMap = new HashMap<>();
         if (controllerBase != null) {
-//            if (controllerBase.structurePattern == null) {
-//                controllerBase.reinitializeStructurePattern();
-//            }
-//            if (controllerBase.structurePattern != null) {
-//                controllerBase.structurePattern.cache.forEach((pos, blockInfo) -> predicateMap
-//                        .put(BlockPos.fromLong(pos), (TraceabilityPredicate) blockInfo.getInfo()));
-//            }
+            // if (controllerBase.structurePattern == null) {
+            // controllerBase.reinitializeStructurePattern();
+            // }
+            // if (controllerBase.structurePattern != null) {
+            // controllerBase.structurePattern.cache.forEach((pos, blockInfo) -> predicateMap
+            // .put(BlockPos.fromLong(pos), (TraceabilityPredicate) blockInfo.getInfo()));
+            // }
         }
 
         List sortedParts = gatherStructureBlocks(worldSceneRenderer.world, blockMap, parts).stream()

From f9e4ccbe3b9c574560fa11fe5c999761209b89c6 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Tue, 16 Jul 2024 23:06:51 -0700
Subject: [PATCH 13/64] startoffset stuff

---
 .../multiblock/MultiblockControllerBase.java  | 27 +++++---
 .../gregtech/api/pattern/BlockPattern.java    | 65 +++++++++++++------
 .../api/pattern/FactoryBlockPattern.java      | 48 +++++++++-----
 .../gregtech/api/util/RelativeDirection.java  |  1 +
 .../electric/MetaTileEntityVacuumFreezer.java | 32 ++++++++-
 5 files changed, 127 insertions(+), 46 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 85697bf6c0f..345a0beb4e5 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -119,7 +119,7 @@ public void update() {
         super.update();
         if (!getWorld().isRemote) {
             if (getOffsetTimer() % 20 == 0 || isFirstTick()) {
-                checkStructurePattern();
+                checkStructurePatterns();
             }
             // DummyWorld is the world for the JEI preview. We do not want to update the Multi in this world,
             // besides initially forming it in checkStructurePattern
@@ -183,7 +183,7 @@ public void setUpwardsFacing(EnumFacing upwardsFacing) {
                 for (BlockPattern pattern : structurePatterns) {
                     if (pattern != null) pattern.clearCache();
                 }
-                checkStructurePattern();
+                checkStructurePatterns();
             }
         }
     }
@@ -373,7 +373,7 @@ protected Function multiblockPartSorter() {
 
     /**
      * Whether a structure at the index should be checked. The check is only performed if this returns true and the
-     * structure is not null.
+     * structure is not null. This helps save on performance if there is a redundant structure.
      * 
      * @param index The index, with 0 being the main structure
      * @return True if the structure should be checked
@@ -382,7 +382,9 @@ protected boolean shouldCheckStructure(int index) {
         return true;
     }
 
-    public void checkStructurePattern() {}
+    public void checkStructurePattern() {
+        checkStructurePattern(0);
+    }
 
     public void checkStructurePatterns() {
         for (int i = 0; i < structurePatterns.length; i++) {
@@ -396,7 +398,7 @@ public void checkStructurePattern(int index) {
 
         long time = System.nanoTime();
         PatternMatchContext context = pattern.checkPatternFastAt(getWorld(), getPos(),
-                getFrontFacing().getOpposite(), getUpwardsFacing(), allowsFlip());
+                getFrontFacing(), getUpwardsFacing(), allowsFlip());
         System.out.println(
                 "structure check for " + getClass().getSimpleName() + " took " + (System.nanoTime() - time) + " nanos");
 
@@ -428,7 +430,7 @@ public void checkStructurePattern(int index) {
             this.multiblockAbilities.putAll(abilities);
             parts.forEach(part -> part.addToMultiBlock(this));
             this.structuresFormed[index] = true;
-            writeCustomData(STRUCTURE_FORMED, buf -> buf.writeBoolean(true));
+            writeCustomData(STRUCTURE_FORMED, buf -> buf.writeVarInt(index));
             formStructure(context);
         } else if (context == null && structuresFormed[index]) {
             invalidateStructure();
@@ -438,6 +440,8 @@ public void checkStructurePattern(int index) {
                 setFlipped(context.neededFlip(), index);
             }
         }
+
+        GTLog.logger.info("Checked pattern " + index + " verdict: " + structuresFormed[index]);
     }
 
     protected void formStructure(PatternMatchContext context) {}
@@ -448,7 +452,7 @@ public void invalidateStructure() {
         this.multiblockParts.clear();
         Arrays.fill(structuresFormed, false);
         Arrays.fill(isFlipped, false);
-        writeCustomData(STRUCTURE_FORMED, buf -> buf.writeBoolean(false));
+        writeCustomData(STRUCTURE_FORMED, buf -> buf.writeVarInt(-1));
     }
 
     protected void invalidStructureCaches() {
@@ -518,7 +522,12 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
             this.upwardsFacing = EnumFacing.VALUES[buf.readByte()];
             scheduleRenderUpdate();
         } else if (dataId == STRUCTURE_FORMED) {
-            GTUtility.longToBoolArr(buf.readLong(), structuresFormed);
+            int result = buf.readVarInt();
+            if (result == -1) {
+                Arrays.fill(structuresFormed, false);
+            } else {
+                structuresFormed[result] = true;
+            }
             if (!isStructureFormed()) {
                 GregTechAPI.soundManager.stopTileSound(getPos());
             }
@@ -560,7 +569,7 @@ public void setFrontFacing(EnumFacing frontFacing) {
             invalidStructureCaches();
             // recheck structure pattern immediately to avoid a slight "lag"
             // on deforming when rotating a multiblock controller
-            checkStructurePattern();
+            checkStructurePatterns();
         }
     }
 
diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java
index b415e980092..8a99e1d5745 100644
--- a/src/main/java/gregtech/api/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/BlockPattern.java
@@ -3,6 +3,7 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.util.BlockInfo;
+import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
 import net.minecraft.block.state.IBlockState;
@@ -23,7 +24,7 @@
 public class BlockPattern {
 
     /**
-     * In the form of [ charDir, stringDir, aisleDir ]
+     * In the form of [ aisleDir, stringDir, charDir ]
      */
     public final RelativeDirection[] structureDir;
 
@@ -33,8 +34,10 @@ public class BlockPattern {
     protected final int[] dimensions;
 
     /**
-     * In the form of [ aisleOffset, stringOffset, charOffset ] where the offsets are the opposite
-     * {@link RelativeDirection} of structure directions
+     * In the form of { pair 1 -> amount, pair 2 -> amount, pair 3 -> amount }.
+     * Each pair is relative directions, such as FRONT, BACK, or RIGHT, LEFT.
+     * The amount is the offset in the first of each pair, can be negative.
+     * {@link RelativeDirection} of structure directions, stored as ordinals
      */
     protected final int[] startOffset;
 
@@ -92,9 +95,11 @@ private void legacyStartOffset(char center) {
         for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
             int[] result = aisles[aisleI].firstInstanceOf(center);
             if (result != null) {
-                startOffset[0] = aisleI;
-                startOffset[1] = result[0];
-                startOffset[2] = result[1];
+                // when legacyStartOffset() is called, aisles have been reversed, so don't reverse it manually here
+                // the scuffed ternary is so that if the structure dir is the first thing, then don't reverse it
+                startOffset[0] = aisleI * (structureDir[0] == RelativeDirection.VALUES[0] ? 1 : -1);
+                startOffset[1] = (dimensions[1] - 1 - result[0]) * (structureDir[1] == RelativeDirection.VALUES[2] ? 1 : -1);
+                startOffset[2] = (dimensions[2] - 1 - result[1]) * (structureDir[2] == RelativeDirection.VALUES[4] ? 1 : -1);
                 return;
             }
         }
@@ -238,6 +243,8 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
                             !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
                 }
 
+                GTLog.logger.info("Checked pos at " + charPos + " with flip " + flip);
+
                 boolean result = predicate.test(worldState, info, globalCount, layerCount);
                 if (!result) return false;
 
@@ -475,20 +482,40 @@ public boolean hasStartOffset() {
         return hasStartOffset;
     }
 
-    private GreggyBlockPos offsetFrom(GreggyBlockPos start, int aisleOffset, int stringOffset, int charOffset,
-                                      @NotNull EnumFacing frontFacing,
-                                      @NotNull EnumFacing upFacing, boolean flip) {
-        GreggyBlockPos pos = start.copy();
-        pos.offset(structureDir[0].getRelativeFacing(frontFacing, upFacing, flip), aisleOffset);
-        pos.offset(structureDir[1].getRelativeFacing(frontFacing, upFacing, flip), stringOffset);
-        pos.offset(structureDir[2].getRelativeFacing(frontFacing, upFacing, flip), charOffset);
-        return pos;
-    }
-
     private GreggyBlockPos startPos(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing,
                                     boolean flip) {
-        // negate since the offsets are the opposite direction of structureDir
-        return offsetFrom(controllerPos, -startOffset[0], -startOffset[1], -startOffset[2], frontFacing, upFacing,
-                flip);
+        GreggyBlockPos start = controllerPos.copy();
+        for (int i = 0; i < 3; i++) {
+            start.offset(RelativeDirection.VALUES[2 * i].getRelativeFacing(frontFacing, upFacing, flip), startOffset[i]);
+        }
+        return start;
+    }
+
+    /**
+     * Moves the start offset in the given direction and amount, use {@link BlockPattern#clearCache()} after to prevent the cache from being stuck in the old offset.
+     * @param dir The direction, relative to controller.
+     * @param amount The amount to offset.
+     */
+    public void moveStartOffset(RelativeDirection dir, int amount) {
+        // reverse amount if its in the opposite direction
+        amount *= (dir.ordinal() % 2 == 0) ? 1 : -1;
+        startOffset[dir.ordinal() / 2] += amount;
+    }
+
+    /**
+     * Gets the start offset. You probably should use {@link BlockPattern#moveStartOffset(RelativeDirection, int)} instead of mutating the result, but I can't stop you.
+     * @return The start offset.
+     */
+    public int[] getStartOffset() {
+        return startOffset;
+    }
+
+    /**
+     * Get the start offset in the given direction.
+     * @param dir The direction, relative to controller.
+     * @return The amount, can be negative.
+     */
+    public int getStartOffset(RelativeDirection dir) {
+        return startOffset[dir.ordinal() / 2] * (dir.ordinal() % 2 == 0 ? 1 : -1);
     }
 }
diff --git a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
index 43f9a820a66..f0699f23f55 100644
--- a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
@@ -1,10 +1,13 @@
 package gregtech.api.pattern;
 
+import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
 import com.google.common.base.Joiner;
+
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
+
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.NotNull;
@@ -20,9 +23,9 @@
  * When the multiblock is placed, its facings are concrete. Then, the {@link RelativeDirection}s passed into
  * {@link FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)} are ways in which the
  * pattern progresses. It can be thought like this, where startPos() is either defined via
- * {@link FactoryBlockPattern#setStartOffset(int, int, int)}
+ * {@link FactoryBlockPattern#startOffset(RelativeDirection, int)}
  * , or automatically detected(for legacy compat only, you should use
- * {@link FactoryBlockPattern#setStartOffset(int, int, int)} always for new code):
+ * {@link FactoryBlockPattern#startOffset(RelativeDirection, int)} always for new code):
  * 
  * 
  * {@code
@@ -46,11 +49,11 @@ public class FactoryBlockPattern {
      */
     private final int[] dimensions = { -1, -1, -1 };
     /**
-     * In the form of [ aisleOffset, stringOffset, charOffset ] where the offsets are the opposite
-     * {@link RelativeDirection} of structure directions
+     * Look at the field with the same name in  {@link BlockPattern} for docs
      */
     private int[] startOffset;
     private char centerChar;
+    private boolean reverse = true;
 
     private final List aisles = new ArrayList<>();
 
@@ -72,7 +75,7 @@ private FactoryBlockPattern(RelativeDirection charDir, RelativeDirection stringD
         structureDir[1] = stringDir;
         structureDir[2] = aisleDir;
         int flags = 0;
-        for (int i = 0; i < this.structureDir.length; i++) {
+        for (int i = 0; i < 3; i++) {
             switch (structureDir[i]) {
                 case UP:
                 case DOWN:
@@ -164,10 +167,15 @@ public FactoryBlockPattern setRepeatable(int repeatCount) {
         return setRepeatable(repeatCount, repeatCount);
     }
 
-    public FactoryBlockPattern setStartOffset(int aisleOffset, int stringOffset, int charOffset) {
-        this.startOffset[0] = aisleOffset;
-        this.startOffset[1] = stringOffset;
-        this.startOffset[2] = charOffset;
+    /**
+     * Sets a part of the start offset in the given direction.
+     * @param dir The direction to offset, relative to controller.
+     * @param amount The amount to offset.
+     */
+    public FactoryBlockPattern startOffset(RelativeDirection dir, int amount) {
+        if (startOffset == null) startOffset = new int[3];
+
+        startOffset[dir.ordinal() / 2] = amount * (dir.ordinal() % 2 == 0 ? 1 : -1);
         return this;
     }
 
@@ -207,12 +215,13 @@ public FactoryBlockPattern where(char symbol, TraceabilityPredicate blockMatcher
         return this;
     }
 
-    public FactoryBlockPattern where(String symbol, TraceabilityPredicate blockMatcher) {
-        if (symbol.length() == 1) {
-            return where(symbol.charAt(0), blockMatcher);
-        }
-        throw new IllegalArgumentException(
-                String.format("Symbol \"%s\" is invalid! It must be exactly one character!", symbol));
+    /**
+     * Calling this stops the reversal of aisles, you should call this on all new patterns
+     */
+    // todo remove this stuff
+    public FactoryBlockPattern modern() {
+        reverse = false;
+        return this;
     }
 
     public BlockPattern build() {
@@ -221,8 +230,13 @@ public BlockPattern build() {
         RelativeDirection temp = structureDir[0];
         structureDir[0] = structureDir[2];
         structureDir[2] = temp;
-        return new BlockPattern(aisles.toArray(new PatternAisle[0]), dimensions, structureDir, startOffset, symbolMap,
-                centerChar);
+        PatternAisle[] aisleArray = aisles.toArray(new PatternAisle[0]);
+        if (reverse) {
+            ArrayUtils.reverse(aisleArray);
+        } else {
+            if (startOffset == null) GTLog.logger.warn("You used .modern() on the builder without using .startOffset()! This will have unintended behavior!");
+        }
+        return new BlockPattern(aisleArray, dimensions, structureDir, startOffset, symbolMap, centerChar);
     }
 
     private void checkMissingPredicates() {
diff --git a/src/main/java/gregtech/api/util/RelativeDirection.java b/src/main/java/gregtech/api/util/RelativeDirection.java
index 41c67e91f09..a1381405efa 100644
--- a/src/main/java/gregtech/api/util/RelativeDirection.java
+++ b/src/main/java/gregtech/api/util/RelativeDirection.java
@@ -20,6 +20,7 @@ public enum RelativeDirection {
     BACK(EnumFacing::getOpposite);
 
     final Function actualFacing;
+    public static final RelativeDirection[] VALUES = values();
 
     RelativeDirection(Function actualFacing) {
         this.actualFacing = actualFacing;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
index a580988c94b..cb7273c31e3 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
@@ -1,5 +1,6 @@
 package gregtech.common.metatileentities.multi.electric;
 
+import gregtech.api.gui.Widget;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
@@ -7,6 +8,7 @@
 import gregtech.api.pattern.BlockPattern;
 import gregtech.api.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
+import gregtech.api.util.RelativeDirection;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.common.blocks.BlockMetalCasing.MetalCasingType;
@@ -16,11 +18,17 @@
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.util.SoundEvent;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.TextComponentString;
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
 
 import org.jetbrains.annotations.NotNull;
 
+import java.util.List;
+
+import static gregtech.api.gui.widgets.AdvancedTextWidget.withButton;
+
 public class MetaTileEntityVacuumFreezer extends RecipeMapMultiblockController {
 
     public MetaTileEntityVacuumFreezer(ResourceLocation metaTileEntityId) {
@@ -50,10 +58,32 @@ protected void createStructurePatterns() {
         structurePatterns[1] = FactoryBlockPattern.start()
                 .aisle("X")
                 .where('X', states(getCasingState()))
-                .setStartOffset(5, 0, 0)
+                .startOffset(RelativeDirection.FRONT, 5)
                 .build();
     }
 
+    @Override
+    protected void addDisplayText(List textList) {
+        super.addDisplayText(textList);
+
+        ITextComponent button = new TextComponentString("Second structure offset: " + structurePatterns[1].getStartOffset(RelativeDirection.FRONT));
+        button.appendText(" ");
+        button.appendSibling(withButton(new TextComponentString("[-]"), "sub"));
+        button.appendText(" ");
+        button.appendSibling(withButton(new TextComponentString("[+]"), "add"));
+        textList.add(button);
+
+        textList.add(new TextComponentString("Second structure: " + (structuresFormed[1] ? "FORMED" : "UNFORMED")));
+    }
+
+    @Override
+    protected void handleDisplayClick(String componentData, Widget.ClickData clickData) {
+        super.handleDisplayClick(componentData, clickData);
+        int mod = componentData.equals("add") ? 1 : -1;
+        structurePatterns[1].moveStartOffset(RelativeDirection.FRONT, mod);
+        structurePatterns[1].clearCache();
+    }
+
     @SideOnly(Side.CLIENT)
     @Override
     public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {

From d9dc706ae64d14602f50e9e44eb21d57fc2d0e0a Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Wed, 17 Jul 2024 12:38:44 -0700
Subject: [PATCH 14/64] default impls

---
 .../multiblock/MultiblockControllerBase.java   | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 345a0beb4e5..ecf9585f2d9 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -372,7 +372,7 @@ protected Function multiblockPartSorter() {
     }
 
     /**
-     * Whether a structure at the index should be checked. The check is only performed if this returns true and the
+     * Whether a structure at the index should be checked for correct pattern. The check is only performed if this returns true and the
      * structure is not null. This helps save on performance if there is a redundant structure.
      * 
      * @param index The index, with 0 being the main structure
@@ -388,7 +388,7 @@ public void checkStructurePattern() {
 
     public void checkStructurePatterns() {
         for (int i = 0; i < structurePatterns.length; i++) {
-            checkStructurePattern(i);
+            if (shouldCheckStructure(i)) checkStructurePattern(i);
         }
     }
 
@@ -431,9 +431,9 @@ public void checkStructurePattern(int index) {
             parts.forEach(part -> part.addToMultiBlock(this));
             this.structuresFormed[index] = true;
             writeCustomData(STRUCTURE_FORMED, buf -> buf.writeVarInt(index));
-            formStructure(context);
+            formStructure(context, index);
         } else if (context == null && structuresFormed[index]) {
-            invalidateStructure();
+            invalidateStructure(index);
         } else if (context != null) {
             // ensure flip is ok, possibly not necessary but good to check just in case
             if (context.neededFlip() != isFlipped()) {
@@ -446,6 +446,11 @@ public void checkStructurePattern(int index) {
 
     protected void formStructure(PatternMatchContext context) {}
 
+    protected void formStructure(PatternMatchContext context, int index) {
+        // form the main structure
+        if (index == 0) formStructure(context);
+    }
+
     public void invalidateStructure() {
         this.multiblockParts.forEach(part -> part.removeFromMultiBlock(this));
         this.multiblockAbilities.clear();
@@ -455,6 +460,11 @@ public void invalidateStructure() {
         writeCustomData(STRUCTURE_FORMED, buf -> buf.writeVarInt(-1));
     }
 
+    public void invalidateStructure(int index) {
+        // invalidate the main structure
+        if (index == 0) invalidateStructure();
+    }
+
     protected void invalidStructureCaches() {
         for (BlockPattern pattern : structurePatterns) {
             if (pattern != null) pattern.clearCache();

From 45691e8b1756c24ff5c51af60ad53fbd920c6757 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Wed, 17 Jul 2024 20:29:16 -0700
Subject: [PATCH 15/64] the third dimension

---
 .../impl/DistillationTowerLogicHandler.java   |   2 +-
 .../multiblock/MultiblockControllerBase.java  |  25 +-
 .../gregtech/api/pattern/GreggyBlockPos.java  | 103 ++++++-
 .../api/pattern/MultiblockShapeInfo.java      |  14 +
 .../api/pattern/PatternMatchContext.java      |   1 +
 .../api/pattern/TraceabilityPredicate.java    |   8 +-
 .../pattern/{ => pattern}/BlockPattern.java   |  35 ++-
 .../pattern/pattern/ExpandablePattern.java    | 164 ++++++++++
 .../{ => pattern}/FactoryBlockPattern.java    |  20 +-
 .../pattern/FactoryExpandablePattern.java     |  67 ++++
 .../api/pattern/pattern/IBlockPattern.java    |  51 ++++
 .../pattern/{ => pattern}/PatternAisle.java   |   2 +-
 .../{ => pattern}/PreviewBlockPattern.java    |   9 +-
 .../gregtech/api/util/RelativeDirection.java  |  15 +
 .../api/util/function/QuadFunction.java       |   6 +
 .../handler/MultiblockPreviewRenderer.java    |   2 +
 .../multi/MetaTileEntityCokeOven.java         |   4 +-
 .../multi/MetaTileEntityLargeBoiler.java      |   4 +-
 .../multi/MetaTileEntityMultiblockTank.java   |   4 +-
 .../MetaTileEntityPrimitiveBlastFurnace.java  |   4 +-
 .../MetaTileEntityPrimitiveWaterPump.java     |   4 +-
 .../MetaTileEntityActiveTransformer.java      |   4 +-
 .../electric/MetaTileEntityAssemblyLine.java  |   4 +-
 .../electric/MetaTileEntityCleanroom.java     | 288 ++++--------------
 .../electric/MetaTileEntityCrackingUnit.java  |   4 +-
 .../electric/MetaTileEntityDataBank.java      |   4 +-
 .../MetaTileEntityDistillationTower.java      |   6 +-
 .../MetaTileEntityElectricBlastFurnace.java   |   4 +-
 .../electric/MetaTileEntityFluidDrill.java    |   4 +-
 .../electric/MetaTileEntityFusionReactor.java |   4 +-
 .../multi/electric/MetaTileEntityHPCA.java    |   4 +-
 .../MetaTileEntityImplosionCompressor.java    |   4 +-
 .../MetaTileEntityLargeChemicalReactor.java   |   4 +-
 .../electric/MetaTileEntityLargeMiner.java    |   4 +-
 .../electric/MetaTileEntityMultiSmelter.java  |   4 +-
 .../electric/MetaTileEntityNetworkSwitch.java |   4 +-
 .../MetaTileEntityPowerSubstation.java        |   2 +
 .../electric/MetaTileEntityPyrolyseOven.java  |   4 +-
 .../MetaTileEntityResearchStation.java        |   4 +-
 .../electric/MetaTileEntityVacuumFreezer.java |   8 +-
 .../MetaTileEntityCentralMonitor.java         |   4 +-
 .../MetaTileEntityLargeCombustionEngine.java  |   4 +-
 .../generator/MetaTileEntityLargeTurbine.java |   4 +-
 .../steam/MetaTileEntitySteamGrinder.java     |   4 +-
 .../multi/steam/MetaTileEntitySteamOven.java  |   4 +-
 .../MetaTileEntityCharcoalPileIgniter.java    |   2 +
 46 files changed, 589 insertions(+), 341 deletions(-)
 rename src/main/java/gregtech/api/pattern/{ => pattern}/BlockPattern.java (94%)
 create mode 100644 src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
 rename src/main/java/gregtech/api/pattern/{ => pattern}/FactoryBlockPattern.java (93%)
 create mode 100644 src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
 create mode 100644 src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
 rename src/main/java/gregtech/api/pattern/{ => pattern}/PatternAisle.java (98%)
 rename src/main/java/gregtech/api/pattern/{ => pattern}/PreviewBlockPattern.java (97%)
 create mode 100644 src/main/java/gregtech/api/util/function/QuadFunction.java

diff --git a/src/main/java/gregtech/api/capability/impl/DistillationTowerLogicHandler.java b/src/main/java/gregtech/api/capability/impl/DistillationTowerLogicHandler.java
index e674db66763..76da13f1518 100644
--- a/src/main/java/gregtech/api/capability/impl/DistillationTowerLogicHandler.java
+++ b/src/main/java/gregtech/api/capability/impl/DistillationTowerLogicHandler.java
@@ -4,7 +4,7 @@
 import gregtech.api.capability.IMultipleTankHandler;
 import gregtech.api.metatileentity.multiblock.IMultiblockAbilityPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
-import gregtech.api.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.util.GTLog;
 import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityMultiblockPart;
 
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index ecf9585f2d9..c9b76257604 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -8,12 +8,13 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
-import gregtech.api.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
-import gregtech.api.pattern.PreviewBlockPattern;
+import gregtech.api.pattern.pattern.PreviewBlockPattern;
 import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.unification.material.Material;
 import gregtech.api.util.BlockInfo;
@@ -79,10 +80,11 @@
 
 import static gregtech.api.capability.GregtechDataCodes.*;
 
+// todo use bitsets you idiot
 public abstract class MultiblockControllerBase extends MetaTileEntity implements IMultiblockController {
 
     // array is not null, but elements can be null
-    protected @Nullable BlockPattern @NotNull [] structurePatterns = new BlockPattern[64];
+    protected @Nullable IBlockPattern @NotNull [] structurePatterns = new IBlockPattern[64];
 
     /**
      * Null until the first time {@link MultiblockControllerBase#getMatchingShapes()} is called, if it is not overriden
@@ -138,7 +140,7 @@ public void update() {
      * @return structure pattern of this multiblock
      */
     @NotNull
-    protected abstract BlockPattern createStructurePattern();
+    protected abstract IBlockPattern createStructurePattern();
 
     /**
      * Populate the structurePatterns array with structure patterns, values can be null. Doing this prevents
@@ -153,14 +155,14 @@ private void validateStructurePatterns() {
 
         for (int i = 1; i < structurePatterns.length; i++) {
             // noinspection DataFlowIssue
-            if (structurePatterns[i] != null && !structurePatterns[i].hasStartOffset()) {
+            if (structurePatterns[i] != null && structurePatterns[i].legacyBuilderError()) {
                 failures.add(i);
             }
         }
 
         if (!failures.isEmpty()) {
             throw new IllegalStateException("Structure patterns " + Arrays.toString(failures.toArray()) +
-                    " didn't have a manually set start offset");
+                    " needs some legacy updating");
         }
     }
 
@@ -180,7 +182,7 @@ public void setUpwardsFacing(EnumFacing upwardsFacing) {
                 notifyBlockUpdate();
                 markDirty();
                 writeCustomData(UPDATE_UPWARDS_FACING, buf -> buf.writeByte(upwardsFacing.getIndex()));
-                for (BlockPattern pattern : structurePatterns) {
+                for (IBlockPattern pattern : structurePatterns) {
                     if (pattern != null) pattern.clearCache();
                 }
                 checkStructurePatterns();
@@ -393,7 +395,7 @@ public void checkStructurePatterns() {
     }
 
     public void checkStructurePattern(int index) {
-        BlockPattern pattern = structurePatterns[index];
+        IBlockPattern pattern = structurePatterns[index];
         if (pattern == null || !shouldCheckStructure(index)) return;
 
         long time = System.nanoTime();
@@ -429,7 +431,6 @@ public void checkStructurePattern(int index) {
             this.multiblockParts.addAll(parts);
             this.multiblockAbilities.putAll(abilities);
             parts.forEach(part -> part.addToMultiBlock(this));
-            this.structuresFormed[index] = true;
             writeCustomData(STRUCTURE_FORMED, buf -> buf.writeVarInt(index));
             formStructure(context, index);
         } else if (context == null && structuresFormed[index]) {
@@ -449,6 +450,8 @@ protected void formStructure(PatternMatchContext context) {}
     protected void formStructure(PatternMatchContext context, int index) {
         // form the main structure
         if (index == 0) formStructure(context);
+
+        this.structuresFormed[index] = true;
     }
 
     public void invalidateStructure() {
@@ -463,10 +466,12 @@ public void invalidateStructure() {
     public void invalidateStructure(int index) {
         // invalidate the main structure
         if (index == 0) invalidateStructure();
+
+        structuresFormed[index] = false;
     }
 
     protected void invalidStructureCaches() {
-        for (BlockPattern pattern : structurePatterns) {
+        for (IBlockPattern pattern : structurePatterns) {
             if (pattern != null) pattern.clearCache();
         }
     }
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index d75617a80a4..f2fcb1a8c5b 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -138,6 +138,46 @@ public GreggyBlockPos fromLong(long l) {
         return this;
     }
 
+    /**
+     * Adds the other pos's position to this pos.
+     * @param other The other pos, not mutated.
+     */
+    public GreggyBlockPos add(GreggyBlockPos other) {
+        pos[0] += other.pos[0];
+        pos[1] += other.pos[1];
+        pos[2] += other.pos[2];
+        return this;
+    }
+
+    /**
+     * Subtracts the other pos's position to this pos.
+     * @param other The other pos, not mutated.
+     */
+    public GreggyBlockPos subtract(GreggyBlockPos other) {
+        pos[0] -= other.pos[0];
+        pos[1] -= other.pos[1];
+        pos[2] -= other.pos[2];
+        return this;
+    }
+
+    /**
+     * Same as {@link GreggyBlockPos#subtract(GreggyBlockPos)} but sets this pos to be the absolute value of the operation.
+     * @param other The other pos, not mutated.
+     */
+    public GreggyBlockPos diff(GreggyBlockPos other) {
+        pos[0] = Math.abs(pos[0] - other.pos[0]);
+        pos[1] = Math.abs(pos[1] - other.pos[1]);
+        pos[2] = Math.abs(pos[2] - other.pos[2]);
+        return this;
+    }
+
+    /**
+     * @return True if all 3 of the coordinates are 0.
+     */
+    public boolean origin() {
+        return equals(BlockPos.ORIGIN);
+    }
+
     /**
      * @return A new immutable instance of {@link BlockPos}
      */
@@ -168,11 +208,58 @@ public String toString() {
         return super.toString() + "[x=" + pos[0] + ", y=" + pos[1] + ", z=" + pos[2] + "]";
     }
 
+    /**
+     * Compares for the same coordinate.
+     * @param other The object to compare to, can be either GreggyBlockPos or BlockPos
+     */
     @Override
     public boolean equals(Object other) {
-        if (!(other instanceof GreggyBlockPos greg)) return false;
+        if (other == null) return false;
+
+        if (other instanceof GreggyBlockPos greg) {
+            return Arrays.equals(pos, greg.pos);
+        } else if (other instanceof BlockPos p) {
+            return p.getX() == pos[0] && p.getY() == pos[1] && p.getZ() == pos[2];
+        }
+
+        return false;
+    }
+
+    /**
+     * Validates the facings argument to be of length 3 and use each pair of facings exactly once.
+     */
+    public static void validateFacingsArray(EnumFacing... facings) {
+        if (facings.length != 3) throw new IllegalArgumentException("Facings must be array of length 3!");
+
+        // validate facings, int division so opposite facings mark the same element
+        boolean[] dirs = new boolean[3];
+        for (int i = 0; i < 3; i++) {
+            dirs[facings[i].ordinal() / 2] = true;
+        }
+
+        if (!(dirs[0] && dirs[1] && dirs[2]))
+            throw new IllegalArgumentException("The 3 facings must use each axis exactly once!");
+    }
+
+    /**
+     * Gets the starting position for {@link GreggyBlockPos#allInBox(GreggyBlockPos, GreggyBlockPos, EnumFacing...)}.
+     */
+    public static GreggyBlockPos startPos(GreggyBlockPos first, GreggyBlockPos second, EnumFacing... facings) {
+        validateFacingsArray(facings);
+
+        GreggyBlockPos start = new GreggyBlockPos();
+
+        for (int i = 0; i < 3; i++) {
+            int a = first.get(facings[i].getAxis());
+            int b = second.get(facings[i].getAxis());
 
-        return Arrays.equals(pos, greg.pos);
+            // multiplying by -1 reverses the direction of min(...)
+            int mult = facings[i].getAxisDirection().getOffset();
+
+            start.set(facings[i].getAxis(), Math.min(a * mult, b * mult) * mult);
+        }
+
+        return start;
     }
 
     /**
@@ -192,17 +279,9 @@ public boolean equals(Object other) {
      */
     public static Iterable allInBox(GreggyBlockPos first, GreggyBlockPos second,
                                                     EnumFacing... facings) {
-        if (facings.length != 3) throw new IllegalArgumentException("Facings must be array of length 3!");
-
-        // validate facings, int division so opposite facings mark the same element
-        boolean[] dirs = new boolean[3];
-        for (int i = 0; i < 3; i++) {
-            dirs[facings[i].ordinal() / 2] = true;
-        }
-
-        if (!(dirs[0] && dirs[1] && dirs[2]))
-            throw new IllegalArgumentException("The 3 facings must use each axis exactly once!");
+        validateFacingsArray(facings);
 
+        // same code as startPos but it has the length thing
         GreggyBlockPos start = new GreggyBlockPos();
         int[] length = new int[3];
 
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index ca0e72e590c..3ade4b6eac3 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -2,6 +2,8 @@
 
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
+import gregtech.api.pattern.pattern.PatternAisle;
+import gregtech.api.pattern.pattern.PreviewBlockPattern;
 import gregtech.api.util.BlockInfo;
 
 import net.minecraft.block.state.IBlockState;
@@ -10,11 +12,13 @@
 
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
+import org.jetbrains.annotations.ApiStatus;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Supplier;
 
+@Deprecated
 public class MultiblockShapeInfo {
 
     /**
@@ -32,6 +36,16 @@ public static Builder builder() {
         return new Builder();
     }
 
+    @ApiStatus.Internal
+    public PatternAisle[] getAisles() {
+        return aisles;
+    }
+
+    @ApiStatus.Internal
+    public Char2ObjectMap getSymbols() {
+        return symbols;
+    }
+
     public static class Builder {
 
         private List shape = new ArrayList<>();
diff --git a/src/main/java/gregtech/api/pattern/PatternMatchContext.java b/src/main/java/gregtech/api/pattern/PatternMatchContext.java
index f0526f43186..e1cfc8f64c2 100644
--- a/src/main/java/gregtech/api/pattern/PatternMatchContext.java
+++ b/src/main/java/gregtech/api/pattern/PatternMatchContext.java
@@ -35,6 +35,7 @@ public void increment(String key, int value) {
     }
 
     public  T getOrDefault(String key, T defaultValue) {
+        //noinspection unchecked
         return (T) data.getOrDefault(key, defaultValue);
     }
 
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index 92f8e28b2f1..fc7ff380008 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -101,6 +101,10 @@ public TraceabilityPredicate setCenter() {
         return this;
     }
 
+    public boolean isCenter() {
+        return isCenter;
+    }
+
     public TraceabilityPredicate sort() {
         limited.sort(Comparator.comparingInt(a -> ((a.minLayerCount + 1) * 100 + a.minGlobalCount)));
         return this;
@@ -342,7 +346,7 @@ public boolean testLimited(BlockWorldState blockWorldState, StructureInfo info,
 
         public boolean testGlobal(BlockWorldState blockWorldState, StructureInfo info,
                                   Object2IntMap cache) {
-            if (minGlobalCount == -1 && maxGlobalCount == -1) return true;
+            if (minGlobalCount == -1 && maxGlobalCount == -1 || cache == null) return true;
 
             boolean base = predicate.test(blockWorldState, info);
             int count = cache.getInt(this);
@@ -355,7 +359,7 @@ public boolean testGlobal(BlockWorldState blockWorldState, StructureInfo info,
 
         public boolean testLayer(BlockWorldState blockWorldState, StructureInfo info,
                                  Object2IntMap cache) {
-            if (minLayerCount == -1 && maxLayerCount == -1) return true;
+            if (minLayerCount == -1 && maxLayerCount == -1 || cache == null) return true;
 
             boolean base = predicate.test(blockWorldState, info);
             int count = cache.getInt(this);
diff --git a/src/main/java/gregtech/api/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
similarity index 94%
rename from src/main/java/gregtech/api/pattern/BlockPattern.java
rename to src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 8a99e1d5745..ac6e54317e1 100644
--- a/src/main/java/gregtech/api/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -1,7 +1,13 @@
-package gregtech.api.pattern;
+package gregtech.api.pattern.pattern;
 
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.pattern.BlockWorldState;
+import gregtech.api.pattern.GreggyBlockPos;
+import gregtech.api.pattern.PatternError;
+import gregtech.api.pattern.PatternMatchContext;
+import gregtech.api.pattern.StructureInfo;
+import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
@@ -21,7 +27,7 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class BlockPattern {
+public class BlockPattern implements IBlockPattern {
 
     /**
      * In the form of [ aisleDir, stringDir, charDir ]
@@ -95,11 +101,13 @@ private void legacyStartOffset(char center) {
         for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
             int[] result = aisles[aisleI].firstInstanceOf(center);
             if (result != null) {
+                // the ordinal() / 2 is so that each relative offset gets put into the correct startOffset
+
                 // when legacyStartOffset() is called, aisles have been reversed, so don't reverse it manually here
-                // the scuffed ternary is so that if the structure dir is the first thing, then don't reverse it
-                startOffset[0] = aisleI * (structureDir[0] == RelativeDirection.VALUES[0] ? 1 : -1);
-                startOffset[1] = (dimensions[1] - 1 - result[0]) * (structureDir[1] == RelativeDirection.VALUES[2] ? 1 : -1);
-                startOffset[2] = (dimensions[2] - 1 - result[1]) * (structureDir[2] == RelativeDirection.VALUES[4] ? 1 : -1);
+                // the scuffed ternary is so that if the structure dir is the second thing, then don't reverse it
+                startOffset[structureDir[0].ordinal() / 2] = aisleI * (structureDir[0].ordinal() % 2 == 0 ? -1 : 1);
+                startOffset[structureDir[1].ordinal() / 2] = (dimensions[1] - 1 - result[0]) * (structureDir[1].ordinal() % 2 == 0 ? -1 : 1);
+                startOffset[structureDir[2].ordinal() / 2] = (dimensions[2] - 1 - result[1]) * (structureDir[2].ordinal() % 2 == 0 ? -1 : 1);
                 return;
             }
         }
@@ -111,6 +119,7 @@ public PatternError getError() {
         return info.getError();
     }
 
+    @Override
     public PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, EnumFacing frontFacing,
                                                   EnumFacing upwardsFacing, boolean allowsFlip) {
         if (!cache.isEmpty()) {
@@ -149,18 +158,20 @@ public PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, E
         return null;
     }
 
+    @Override
     public void clearCache() {
         cache.clear();
     }
 
-    private boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
-                                   EnumFacing upwardsFacing, boolean isFlipped) {
+    @Override
+    public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
+                                  EnumFacing upwardsFacing, boolean isFlipped) {
         this.matchContext.reset();
         this.globalCount.clear();
         this.layerCount.clear();
         cache.clear();
 
-        worldState.world = world;
+        worldState.setWorld(world);
 
         int aisleOffset = -1;
         GreggyBlockPos controllerPos = new GreggyBlockPos(centerPos);
@@ -462,6 +473,7 @@ public void autoBuild(EntityPlayer player, MultiblockControllerBase controllerBa
         // });
     }
 
+    @Override
     public PreviewBlockPattern getDefaultShape() {
         char[][][] pattern = new char[dimensions[2]][dimensions[1]][dimensions[0]];
 
@@ -478,8 +490,9 @@ public PreviewBlockPattern getDefaultShape() {
         return null;
     }
 
-    public boolean hasStartOffset() {
-        return hasStartOffset;
+    @Override
+    public boolean legacyBuilderError() {
+        return !hasStartOffset;
     }
 
     private GreggyBlockPos startPos(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing,
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
new file mode 100644
index 00000000000..5c4c9bedc45
--- /dev/null
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -0,0 +1,164 @@
+package gregtech.api.pattern.pattern;
+
+import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
+import gregtech.api.pattern.BlockWorldState;
+import gregtech.api.pattern.GreggyBlockPos;
+import gregtech.api.pattern.PatternMatchContext;
+import gregtech.api.pattern.StructureInfo;
+import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.util.BlockInfo;
+import gregtech.api.util.RelativeDirection;
+import gregtech.api.util.function.QuadFunction;
+
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.BiFunction;
+
+public class ExpandablePattern implements IBlockPattern {
+    protected final QuadFunction boundsFunction;
+    protected final BiFunction predicateFunction;
+
+    /**
+     * In the form of [ aisleDir, stringDir, charDir ]
+     */
+    protected final RelativeDirection[] directions;
+    protected final PatternMatchContext matchContext = new PatternMatchContext();
+    protected final StructureInfo info;
+    protected final BlockWorldState worldState;
+    protected final Long2ObjectMap cache = new Long2ObjectOpenHashMap<>();
+    protected final Object2IntMap globalCount = new Object2IntOpenHashMap<>();
+
+    public ExpandablePattern(@NotNull QuadFunction boundsFunction, @NotNull BiFunction predicateFunction, @NotNull RelativeDirection[] directions) {
+        this.boundsFunction = boundsFunction;
+        this.predicateFunction = predicateFunction;
+        this.directions = directions;
+
+        this.info = new StructureInfo(matchContext, null);
+        this.worldState = new BlockWorldState(info);
+    }
+
+    @Override
+    public PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, EnumFacing frontFacing,
+                                                  EnumFacing upwardsFacing, boolean allowsFlip) {
+        if (!cache.isEmpty()) {
+            boolean pass = true;
+            GreggyBlockPos gregPos = new GreggyBlockPos();
+            for (Long2ObjectMap.Entry entry : cache.long2ObjectEntrySet()) {
+                BlockPos pos = gregPos.fromLong(entry.getLongKey()).immutable();
+                IBlockState blockState = world.getBlockState(pos);
+
+                if (blockState != entry.getValue().getBlockState()) {
+                    pass = false;
+                    break;
+                }
+
+                TileEntity cachedTileEntity = entry.getValue().getTileEntity();
+
+                if (cachedTileEntity != null) {
+                    TileEntity tileEntity = world.getTileEntity(pos);
+                    if (tileEntity != cachedTileEntity) {
+                        pass = false;
+                        break;
+                    }
+                }
+            }
+            if (pass) return info.hasError() ? null : matchContext;
+        }
+
+        boolean valid = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, false);
+        if (valid) return matchContext;
+
+        clearCache(); // we don't want a random cache of a partially formed multi
+        return null;
+    }
+
+    @Override
+    public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing, EnumFacing upwardsFacing,
+                                  boolean isFlipped) {
+        int[] bounds = boundsFunction.apply(world, new GreggyBlockPos(centerPos), frontFacing, upwardsFacing);
+        if (bounds == null) return false;
+
+        // where the iteration starts, in octant 7
+        GreggyBlockPos negativeCorner = new GreggyBlockPos();
+        // where the iteration ends, in octant 1
+        GreggyBlockPos positiveCorner = new GreggyBlockPos();
+
+        for (int i = 0; i < 3; i++) {
+            // iteration in: [ char, string, aisle ]
+            RelativeDirection selected = directions[2 - i];
+
+            // this is which direction the start goes in relation to the origin
+            // this progresses by the direction
+            int positiveOrdinal = selected.ordinal();
+
+            // opposite of the selected direction
+            RelativeDirection opposite = selected.getOpposite();
+            // this is in which direction the start is in relation to the origin
+            int negativeOrdinal = opposite.ordinal();
+
+            // todo maybe fix this to allow flipping and update the quadfunction
+            negativeCorner.offset(opposite.getRelativeFacing(frontFacing, upwardsFacing, false), bounds[negativeOrdinal]);
+            positiveCorner.offset(selected.getRelativeFacing(frontFacing, upwardsFacing, false), bounds[positiveOrdinal]);
+        }
+
+        // which way each direction progresses absolutely, in [ char, string, aisle ]
+        EnumFacing[] facings = new EnumFacing[3];
+        for (int i = 0; i < 3; i++) {
+            facings[i] = directions[2 - i].getRelativeFacing(frontFacing, upwardsFacing, false);
+        }
+
+
+
+        worldState.setWorld(world);
+        // this translates from the relative coordinates to world coordinates
+        GreggyBlockPos translation = new GreggyBlockPos(centerPos);
+
+        for (GreggyBlockPos pos : GreggyBlockPos.allInBox(negativeCorner, positiveCorner, facings)) {
+
+            // test first before using .add() which mutates the pos
+            TraceabilityPredicate predicate = predicateFunction.apply(pos, bounds);
+            // set the pos with world coordinates
+            worldState.setPos(pos.add(translation));
+
+            if (predicate != TraceabilityPredicate.ANY) {
+                TileEntity te = worldState.getTileEntity();
+                cache.put(pos.toLong(), new BlockInfo(worldState.getBlockState(),
+                        !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
+            }
+
+//            GTLog.logger.info("Checked pos at " + pos);
+
+            boolean result = predicate.test(worldState, info, globalCount, null);
+            if (!result) return false;
+        }
+
+        for (Object2IntMap.Entry entry : globalCount.object2IntEntrySet()) {
+            if (entry.getIntValue() < entry.getKey().minGlobalCount) {
+                info.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 1));
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public PreviewBlockPattern getDefaultShape() {
+        return null;
+    }
+
+    @Override
+    public void clearCache() {
+        cache.clear();
+    }
+}
diff --git a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
similarity index 93%
rename from src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
rename to src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
index f0699f23f55..1d34a84f942 100644
--- a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
@@ -1,5 +1,6 @@
-package gregtech.api.pattern;
+package gregtech.api.pattern.pattern;
 
+import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
@@ -63,17 +64,17 @@ public class FactoryBlockPattern {
     private final Char2ObjectMap symbolMap = new Char2ObjectOpenHashMap<>();
 
     /**
-     * In the form of [ charDir, stringDir, aisleDir ]
+     * In the form of [ aisleDir, stringDir, charDir ]
      */
     private final RelativeDirection[] structureDir = new RelativeDirection[3];
 
     /**
      * @see FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)
      */
-    private FactoryBlockPattern(RelativeDirection charDir, RelativeDirection stringDir, RelativeDirection aisleDir) {
-        structureDir[0] = charDir;
+    private FactoryBlockPattern(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
+        structureDir[0] = aisleDir;
         structureDir[1] = stringDir;
-        structureDir[2] = aisleDir;
+        structureDir[2] = charDir;
         int flags = 0;
         for (int i = 0; i < 3; i++) {
             switch (structureDir[i]) {
@@ -186,7 +187,7 @@ public FactoryBlockPattern startOffset(RelativeDirection dir, int amount) {
      * @see FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)
      */
     public static FactoryBlockPattern start() {
-        return new FactoryBlockPattern(RIGHT, UP, BACK);
+        return new FactoryBlockPattern(BACK, UP, RIGHT);
     }
 
     /**
@@ -200,7 +201,7 @@ public static FactoryBlockPattern start() {
      */
     public static FactoryBlockPattern start(RelativeDirection charDir, RelativeDirection stringDir,
                                             RelativeDirection aisleDir) {
-        return new FactoryBlockPattern(charDir, stringDir, aisleDir);
+        return new FactoryBlockPattern(aisleDir, stringDir, charDir);
     }
 
     /**
@@ -211,7 +212,7 @@ public static FactoryBlockPattern start(RelativeDirection charDir, RelativeDirec
      */
     public FactoryBlockPattern where(char symbol, TraceabilityPredicate blockMatcher) {
         this.symbolMap.put(symbol, new TraceabilityPredicate(blockMatcher).sort());
-        if (blockMatcher.isCenter) centerChar = symbol;
+        if (blockMatcher.isCenter()) centerChar = symbol;
         return this;
     }
 
@@ -227,9 +228,6 @@ public FactoryBlockPattern modern() {
     public BlockPattern build() {
         checkMissingPredicates();
         this.dimensions[0] = aisles.size();
-        RelativeDirection temp = structureDir[0];
-        structureDir[0] = structureDir[2];
-        structureDir[2] = temp;
         PatternAisle[] aisleArray = aisles.toArray(new PatternAisle[0]);
         if (reverse) {
             ArrayUtils.reverse(aisleArray);
diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
new file mode 100644
index 00000000000..4aa2da52193
--- /dev/null
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
@@ -0,0 +1,67 @@
+package gregtech.api.pattern.pattern;
+
+import gregtech.api.pattern.GreggyBlockPos;
+import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.util.RelativeDirection;
+import gregtech.api.util.function.QuadFunction;
+
+import net.minecraft.util.EnumFacing;
+import net.minecraft.world.World;
+
+import java.util.function.BiFunction;
+
+public class FactoryExpandablePattern {
+    protected QuadFunction boundsFunction;
+    protected BiFunction predicateFunction;
+    protected final RelativeDirection[] structureDir = new RelativeDirection[3];
+
+    private FactoryExpandablePattern(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
+        structureDir[0] = aisleDir;
+        structureDir[1] = stringDir;
+        structureDir[2] = charDir;
+        int flags = 0;
+        for (int i = 0; i < 3; i++) {
+            switch (structureDir[i]) {
+                case UP:
+                case DOWN:
+                    flags |= 0x1;
+                    break;
+                case LEFT:
+                case RIGHT:
+                    flags |= 0x2;
+                    break;
+                case FRONT:
+                case BACK:
+                    flags |= 0x4;
+                    break;
+            }
+        }
+        if (flags != 0x7) throw new IllegalArgumentException("Must have 3 different axes!");
+    }
+
+    public static FactoryExpandablePattern start(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
+        return new FactoryExpandablePattern(aisleDir,stringDir, charDir);
+    }
+
+    public static FactoryExpandablePattern start() {
+        return new FactoryExpandablePattern(RelativeDirection.BACK, RelativeDirection.UP, RelativeDirection.RIGHT);
+    }
+
+    public FactoryExpandablePattern boundsFunction(QuadFunction function) {
+        this.boundsFunction = function;
+        return this;
+    }
+
+    public FactoryExpandablePattern predicateFunction(BiFunction function) {
+        this.predicateFunction = function;
+        return this;
+    }
+
+    public ExpandablePattern build() {
+        if (boundsFunction == null) throw new IllegalStateException("Bound function is null! Use .boundsFunction(...) on the builder!");
+        if (predicateFunction == null) throw new IllegalStateException("Predicate function is null! Use .predicateFunction(...) on the builder!");
+
+        return new ExpandablePattern(boundsFunction, predicateFunction, structureDir);
+    }
+
+}
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
new file mode 100644
index 00000000000..90dc86fcb9a
--- /dev/null
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -0,0 +1,51 @@
+package gregtech.api.pattern.pattern;
+
+import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.pattern.PatternMatchContext;
+
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+
+public interface IBlockPattern {
+
+    /**
+     * Checks the pattern fast, this should always be preferred to checkPatternAt(...) for multiblock code.
+     * @param world The world the multiblock is in.
+     * @param centerPos The position of the controller.
+     * @param frontFacing The front facing of the controller, obtained via {@link MultiblockControllerBase#getFrontFacing()}
+     * @param upwardsFacing The up facing of the controller, obtained via {@link MultiblockControllerBase#getUpwardsFacing()}
+     * @param allowsFlip Whether the multiblock allows flipping.
+     * @return A context for the formed structure, or null if the structure check failed.
+     */
+    PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, EnumFacing frontFacing, EnumFacing upwardsFacing, boolean allowsFlip);
+
+    /**
+     * Checks the whole pattern, you should probably use checkPatternFastAt(...) instead.
+     * @param world The world the multiblock is in.
+     * @param centerPos The position of the controller.
+     * @param frontFacing The front facing of the controller, obtained via {@link MultiblockControllerBase#getFrontFacing()}
+     * @param upwardsFacing The up facing of the controller, obtained via {@link MultiblockControllerBase#getUpwardsFacing()}
+     * @param isFlipped Is the multiblock flipped or not.
+     * @return True if the check passed, in which case the context is mutated for returning from checkPatternFastAt(...)
+     */
+    boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped);
+
+    /**
+     * Gets the default shape, if the multiblock does not specify one.
+     */
+    PreviewBlockPattern getDefaultShape();
+
+    /**
+     * Clears the cache for checkPatternFastAt(...) in case something in the pattern is changed.
+     */
+    void clearCache();
+
+    /**
+     * If anything from legacy need to be updated.
+     * @return True if yes, which will throw an error in the multiblock.
+     */
+    default boolean legacyBuilderError() {
+        return false;
+    }
+}
diff --git a/src/main/java/gregtech/api/pattern/PatternAisle.java b/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
similarity index 98%
rename from src/main/java/gregtech/api/pattern/PatternAisle.java
rename to src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
index 189c79a97b7..717e3fb2444 100644
--- a/src/main/java/gregtech/api/pattern/PatternAisle.java
+++ b/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
@@ -1,4 +1,4 @@
-package gregtech.api.pattern;
+package gregtech.api.pattern.pattern;
 
 public class PatternAisle {
 
diff --git a/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/PreviewBlockPattern.java
similarity index 97%
rename from src/main/java/gregtech/api/pattern/PreviewBlockPattern.java
rename to src/main/java/gregtech/api/pattern/pattern/PreviewBlockPattern.java
index 3cabec44d9c..bde18d53626 100644
--- a/src/main/java/gregtech/api/pattern/PreviewBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/PreviewBlockPattern.java
@@ -1,7 +1,8 @@
-package gregtech.api.pattern;
+package gregtech.api.pattern.pattern;
 
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
+import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.RelativeDirection;
 
@@ -18,7 +19,7 @@
 import java.util.List;
 import java.util.function.Supplier;
 
-import static gregtech.api.pattern.FactoryBlockPattern.COMMA_JOIN;
+import static gregtech.api.pattern.pattern.FactoryBlockPattern.COMMA_JOIN;
 
 /**
  * Class holding data for a concrete multiblock. This multiblock can be formed or unformed in this.
@@ -38,7 +39,7 @@ public class PreviewBlockPattern {
      * Legacy compat only, do not use for new code.
      */
     public PreviewBlockPattern(MultiblockShapeInfo info) {
-        this.aisles = info.aisles;
+        this.aisles = info.getAisles();
         this.dimensions = new int[] { aisles.length, aisles[0].getStringCount(), aisles[0].getCharCount() };
         structureDir = new RelativeDirection[] { RelativeDirection.BACK, RelativeDirection.UP,
                 RelativeDirection.RIGHT };
@@ -46,7 +47,7 @@ public PreviewBlockPattern(MultiblockShapeInfo info) {
         // i am lazy so hopefully addons follow the convention of using 'S' for their self predicate(aka center
         // predicate)
         legacyStartOffset('S');
-        this.symbols = info.symbols;
+        this.symbols = info.getSymbols();
     }
 
     public PreviewBlockPattern(PatternAisle[] aisles, int[] dimensions, RelativeDirection[] directions,
diff --git a/src/main/java/gregtech/api/util/RelativeDirection.java b/src/main/java/gregtech/api/util/RelativeDirection.java
index a1381405efa..5f4a946f53a 100644
--- a/src/main/java/gregtech/api/util/RelativeDirection.java
+++ b/src/main/java/gregtech/api/util/RelativeDirection.java
@@ -38,6 +38,21 @@ public Vec3i applyVec3i(EnumFacing facing) {
         return apply(facing).getDirectionVec();
     }
 
+    /**
+     * Gets the opposite RelativeDirection to this. UP <-> DOWN, LEFT <-> RIGHT, FRONT <-> BACK.
+     */
+    public RelativeDirection getOpposite() {
+        return VALUES[oppositeOrdinal()];
+    }
+
+    /**
+     * Gets the ordinal of the RelativeDirection opposite to this. UP <-> DOWN, LEFT <-> RIGHT, FRONT <-> BACK.
+     */
+    public int oppositeOrdinal() {
+        // floor to nearest even + adjustment
+        return (ordinal() / 2) * 2 + (1 - ordinal() % 2);
+    }
+
     public EnumFacing getRelativeFacing(EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped) {
         EnumFacing.Axis frontAxis = frontFacing.getAxis();
         return switch (this) {
diff --git a/src/main/java/gregtech/api/util/function/QuadFunction.java b/src/main/java/gregtech/api/util/function/QuadFunction.java
new file mode 100644
index 00000000000..55b78dcb2a7
--- /dev/null
+++ b/src/main/java/gregtech/api/util/function/QuadFunction.java
@@ -0,0 +1,6 @@
+package gregtech.api.util.function;
+
+@FunctionalInterface
+public interface QuadFunction {
+    R apply(A a, B b, C c, D d);
+}
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index 8a45ebbb1d5..448801e5087 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -108,6 +108,8 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas
         MultiblockControllerBase mte = null;
         // todo fix
         BlockInfo[][][] blocks = null;
+        // yay unreachable statements
+        if(true) return;
         Map blockMap = new HashMap<>();
         int maxY = 0;
         for (int x = 0; x < blocks.length; x++) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOven.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOven.java
index 781ae14b7e3..de93f45bb97 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOven.java
@@ -9,8 +9,8 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.RecipeMapPrimitiveMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.client.particle.VanillaParticleEffects;
 import gregtech.client.renderer.ICubeRenderer;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
index f6e4495b7db..e0dfd0091b3 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
@@ -14,8 +14,8 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.api.util.TextFormattingUtil;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
index eeabc2cd762..ba43b7476a0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
@@ -11,8 +11,8 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.common.blocks.BlockMetalCasing;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
index 90078eaf604..bf6dbb68782 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
@@ -11,8 +11,8 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.RecipeMapPrimitiveMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.util.GTUtility;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
index 39d61ccb58e..7eac78ad838 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
@@ -6,8 +6,8 @@
 import gregtech.api.metatileentity.multiblock.IPrimitivePump;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.unification.material.Materials;
 import gregtech.api.util.LocalizationUtils;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
index d22e681fad3..8afc9009f6e 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
@@ -11,8 +11,8 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.TextComponentUtil;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
index 69b648b41cb..06019d821e9 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
@@ -8,8 +8,8 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMaps;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index f9870eca629..85106db781e 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -10,6 +10,7 @@
 import gregtech.api.capability.IWorkable;
 import gregtech.api.capability.impl.CleanroomLogic;
 import gregtech.api.capability.impl.EnergyContainerList;
+import gregtech.api.gui.Widget;
 import gregtech.api.metatileentity.IDataInfoProvider;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.SimpleGeneratorMetaTileEntity;
@@ -22,15 +23,18 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.PatternStringError;
 import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryExpandablePattern;
+import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.util.BlockInfo;
+import gregtech.api.util.GTLog;
 import gregtech.api.util.GTUtility;
 import gregtech.api.util.Mods;
+import gregtech.api.util.RelativeDirection;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
@@ -59,7 +63,6 @@
 import net.minecraft.util.NonNullList;
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.util.SoundEvent;
-import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.text.ITextComponent;
 import net.minecraft.util.text.TextComponentString;
@@ -79,7 +82,6 @@
 import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -87,6 +89,9 @@
 import java.util.LinkedList;
 import java.util.List;
 
+import static gregtech.api.gui.widgets.AdvancedTextWidget.withButton;
+import static gregtech.common.blocks.BlockCleanroomCasing.CasingType.FILTER_CASING;
+
 public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase
                                      implements ICleanroomProvider, IWorkable, IDataInfoProvider {
 
@@ -95,13 +100,7 @@ public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase
 
     public static final int MIN_RADIUS = 2;
     public static final int MIN_DEPTH = 4;
-
-    private int lDist = 0;
-    private int rDist = 0;
-    private int bDist = 0;
-    private int fDist = 0;
-    private int hDist = 0;
-
+    private final int[] bounds = { 0, 1, 1, 1, 1, 1 };
     private CleanroomType cleanroomType = null;
     private int cleanAmount;
 
@@ -133,14 +132,15 @@ private void resetTileAbilities() {
     protected void formStructure(PatternMatchContext context) {
         super.formStructure(context);
         initializeAbilities();
-        this.cleanroomFilter = context.get("FilterType");
-        this.cleanroomType = cleanroomFilter.getCleanroomType();
+        this.cleanroomFilter = FILTER_CASING;
+        this.cleanroomType = CleanroomType.CLEANROOM;
 
         // max progress is based on the dimensions of the structure: (x^3)-(x^2)
         // taller cleanrooms take longer than wider ones
         // minimum of 100 is a 5x5x5 cleanroom: 125-25=100 ticks
-        this.cleanroomLogic.setMaxProgress(Math.max(100,
-                ((lDist + rDist + 1) * (bDist + fDist + 1) * hDist) - ((lDist + rDist + 1) * (bDist + fDist + 1))));
+//        this.cleanroomLogic.setMaxProgress(Math.max(100,
+//                ((lDist + rDist + 1) * (bDist + fDist + 1) * hDist) - ((lDist + rDist + 1) * (bDist + fDist + 1))));
+        this.cleanroomLogic.setMaxProgress(100);
         this.cleanroomLogic.setMinEnergyTier(cleanroomFilter.getMinTier());
     }
 
@@ -183,203 +183,22 @@ public boolean allowsFlip() {
         return false;
     }
 
-    /**
-     * Scans for blocks around the controller to update the dimensions
-     */
-    public boolean updateStructureDimensions() {
-        World world = getWorld();
-        EnumFacing front = getFrontFacing();
-        EnumFacing back = front.getOpposite();
-        EnumFacing left = front.rotateYCCW();
-        EnumFacing right = left.getOpposite();
-
-        BlockPos.MutableBlockPos lPos = new BlockPos.MutableBlockPos(getPos());
-        BlockPos.MutableBlockPos rPos = new BlockPos.MutableBlockPos(getPos());
-        BlockPos.MutableBlockPos fPos = new BlockPos.MutableBlockPos(getPos());
-        BlockPos.MutableBlockPos bPos = new BlockPos.MutableBlockPos(getPos());
-        BlockPos.MutableBlockPos hPos = new BlockPos.MutableBlockPos(getPos());
-
-        // find the distances from the controller to the plascrete blocks on one horizontal axis and the Y axis
-        // repeatable aisles take care of the second horizontal axis
-        int lDist = 0;
-        int rDist = 0;
-        int bDist = 0;
-        int fDist = 0;
-        int hDist = 0;
-
-        // find the left, right, back, and front distances for the structure pattern
-        // maximum size is 15x15x15 including walls, so check 7 block radius around the controller for blocks
-        for (int i = 1; i < 8; i++) {
-            if (lDist == 0 && isBlockEdge(world, lPos, left)) lDist = i;
-            if (rDist == 0 && isBlockEdge(world, rPos, right)) rDist = i;
-            if (bDist == 0 && isBlockEdge(world, bPos, back)) bDist = i;
-            if (fDist == 0 && isBlockEdge(world, fPos, front)) fDist = i;
-            if (lDist != 0 && rDist != 0 && bDist != 0 && fDist != 0) break;
-        }
-
-        // height is diameter instead of radius, so it needs to be done separately
-        for (int i = 1; i < 15; i++) {
-            if (isBlockFloor(world, hPos, EnumFacing.DOWN)) hDist = i;
-            if (hDist != 0) break;
-        }
-
-        if (lDist < MIN_RADIUS || rDist < MIN_RADIUS || bDist < MIN_RADIUS || fDist < MIN_RADIUS || hDist < MIN_DEPTH) {
-            invalidateStructure();
-            return false;
-        }
-
-        this.lDist = lDist;
-        this.rDist = rDist;
-        this.bDist = bDist;
-        this.fDist = fDist;
-        this.hDist = hDist;
-
-        writeCustomData(GregtechDataCodes.UPDATE_STRUCTURE_SIZE, buf -> {
-            buf.writeInt(this.lDist);
-            buf.writeInt(this.rDist);
-            buf.writeInt(this.bDist);
-            buf.writeInt(this.fDist);
-            buf.writeInt(this.hDist);
-        });
-        return true;
-    }
-
-    /**
-     * @param world     the world to check
-     * @param pos       the pos to check and move
-     * @param direction the direction to move
-     * @return if a block is a valid wall block at pos moved in direction
-     */
-    public boolean isBlockEdge(@NotNull World world, @NotNull BlockPos.MutableBlockPos pos,
-                               @NotNull EnumFacing direction) {
-        return world.getBlockState(pos.move(direction)) ==
-                MetaBlocks.CLEANROOM_CASING.getState(BlockCleanroomCasing.CasingType.PLASCRETE);
-    }
-
-    /**
-     * @param world     the world to check
-     * @param pos       the pos to check and move
-     * @param direction the direction to move
-     * @return if a block is a valid floor block at pos moved in direction
-     */
-    public boolean isBlockFloor(@NotNull World world, @NotNull BlockPos.MutableBlockPos pos,
-                                @NotNull EnumFacing direction) {
-        return isBlockEdge(world, pos, direction) || world.getBlockState(pos) ==
-                MetaBlocks.TRANSPARENT_CASING.getState(BlockGlassCasing.CasingType.CLEANROOM_GLASS);
-    }
-
     @NotNull
     @Override
-    protected BlockPattern createStructurePattern() {
-        // return the default structure, even if there is no valid size found
-        // this means auto-build will still work, and prevents terminal crashes.
-        if (getWorld() != null) updateStructureDimensions();
-
-        // these can sometimes get set to 0 when loading the game, breaking JEI
-        if (lDist < MIN_RADIUS) lDist = MIN_RADIUS;
-        if (rDist < MIN_RADIUS) rDist = MIN_RADIUS;
-        if (bDist < MIN_RADIUS) bDist = MIN_RADIUS;
-        if (fDist < MIN_RADIUS) fDist = MIN_RADIUS;
-        if (hDist < MIN_DEPTH) hDist = MIN_DEPTH;
-
-        if (this.frontFacing == EnumFacing.EAST || this.frontFacing == EnumFacing.WEST) {
-            int tmp = lDist;
-            lDist = rDist;
-            rDist = tmp;
-        }
-
-        // build each row of the structure
-        StringBuilder borderBuilder = new StringBuilder();     // BBBBB
-        StringBuilder wallBuilder = new StringBuilder();       // BXXXB
-        StringBuilder insideBuilder = new StringBuilder();     // X X
-        StringBuilder roofBuilder = new StringBuilder();       // BFFFB
-        StringBuilder controllerBuilder = new StringBuilder(); // BFSFB
-        StringBuilder centerBuilder = new StringBuilder();     // BXKXB
-
-        // everything to the left of the controller
-        for (int i = 0; i < lDist; i++) {
-            borderBuilder.append("B");
-            if (i == 0) {
-                wallBuilder.append("B");
-                insideBuilder.append("X");
-                roofBuilder.append("B");
-                controllerBuilder.append("B");
-                centerBuilder.append("B");
-            } else {
-                insideBuilder.append(" ");
-                wallBuilder.append("X");
-                roofBuilder.append("F");
-                controllerBuilder.append("F");
-                centerBuilder.append("X");
-            }
-        }
-
-        // everything in-line with the controller
-        borderBuilder.append("B");
-        wallBuilder.append("X");
-        insideBuilder.append(" ");
-        roofBuilder.append("F");
-        controllerBuilder.append("S");
-        centerBuilder.append("K");
-
-        // everything to the right of the controller
-        for (int i = 0; i < rDist; i++) {
-            borderBuilder.append("B");
-            if (i == rDist - 1) {
-                wallBuilder.append("B");
-                insideBuilder.append("X");
-                roofBuilder.append("B");
-                controllerBuilder.append("B");
-                centerBuilder.append("B");
-            } else {
-                insideBuilder.append(" ");
-                wallBuilder.append("X");
-                roofBuilder.append("F");
-                controllerBuilder.append("F");
-                centerBuilder.append("X");
-            }
-        }
-
-        // build each slice of the structure
-        String[] wall = new String[hDist + 1]; // "BBBBB", "BXXXB", "BXXXB", "BXXXB", "BBBBB"
-        Arrays.fill(wall, wallBuilder.toString());
-        wall[0] = borderBuilder.toString();
-        wall[wall.length - 1] = borderBuilder.toString();
-
-        String[] slice = new String[hDist + 1]; // "BXXXB", "X X", "X X", "X X", "BFFFB"
-        Arrays.fill(slice, insideBuilder.toString());
-        slice[0] = wallBuilder.toString();
-        slice[slice.length - 1] = roofBuilder.toString();
-
-        String[] center = Arrays.copyOf(slice, slice.length); // "BXKXB", "X X", "X X", "X X", "BFSFB"
-        if (this.frontFacing == EnumFacing.NORTH || this.frontFacing == EnumFacing.SOUTH) {
-            center[0] = centerBuilder.reverse().toString();
-            center[center.length - 1] = controllerBuilder.reverse().toString();
-        } else {
-            center[0] = centerBuilder.toString();
-            center[center.length - 1] = controllerBuilder.toString();
-        }
+    protected IBlockPattern createStructurePattern() {
 
         TraceabilityPredicate wallPredicate = states(getCasingState(), getGlassState());
         TraceabilityPredicate basePredicate = autoAbilities().or(abilities(MultiblockAbility.INPUT_ENERGY)
                 .setMinGlobalLimited(1).setMaxGlobalLimited(3));
 
-        // layer the slices one behind the next
-        return FactoryBlockPattern.start()
-                .aisle(wall)
-                .aisle(slice).setRepeatable(bDist - 1)
-                .aisle(center)
-                .aisle(slice).setRepeatable(fDist - 1)
-                .aisle(wall)
-                .where('S', selfPredicate())
-                .where('B', states(getCasingState()).or(basePredicate))
-                .where('X', wallPredicate.or(basePredicate)
-                        .or(doorPredicate().setMaxGlobalLimited(8))
-                        .or(abilities(MultiblockAbility.PASSTHROUGH_HATCH).setMaxGlobalLimited(30)))
-                .where('K', wallPredicate) // the block beneath the controller must only be a casing for structure
-                                           // dimension checks
-                .where('F', filterPredicate())
-                .where(' ', innerPredicate())
+        return FactoryExpandablePattern.start(RelativeDirection.UP, RelativeDirection.RIGHT, RelativeDirection.FRONT)
+                .boundsFunction((w, c, f, u) -> bounds)
+                .predicateFunction((c, b) -> {
+
+                    if (c.origin()) return selfPredicate();
+
+                    return wallPredicate;
+                })
                 .build();
     }
 
@@ -517,10 +336,39 @@ protected void addDisplayText(List textList) {
                 .addEnergyUsageExactLine(isClean() ? 4 : GTValues.VA[getEnergyTier()])
                 .addWorkingStatusLine()
                 .addProgressLine(getProgressPercent() / 100.0);
+
+        ITextComponent button = new TextComponentString("height: " + bounds[1]);
+        button.appendText(" ");
+        button.appendSibling(withButton(new TextComponentString("[-]"), "subh"));
+        button.appendText(" ");
+        button.appendSibling(withButton(new TextComponentString("[+]"), "addh"));
+        textList.add(button);
+
+        ITextComponent button2 = new TextComponentString("left: " + bounds[2]);
+        button2.appendText(" ");
+        button2.appendSibling(withButton(new TextComponentString("[-]"), "subl"));
+        button2.appendText(" ");
+        button2.appendSibling(withButton(new TextComponentString("[+]"), "addl"));
+        textList.add(button2);
+    }
+
+    @Override
+    protected void handleDisplayClick(String componentData, Widget.ClickData clickData) {
+        super.handleDisplayClick(componentData, clickData);
+
+        switch (componentData) {
+            case "subh" -> bounds[1] = Math.max(bounds[1], 1);
+            case "addh" -> bounds[1] += 1;
+            case "subl" -> bounds[2] = Math.max(bounds[2], 1);
+            case "addl" -> bounds[2] += 1;
+        }
+
+        structurePatterns[0].clearCache();
     }
 
     @Override
     protected void addWarningText(List textList) {
+
         MultiblockDisplayText.builder(textList, isStructureFormed(), false)
                 .addLowPowerLine(!drainEnergy(true))
                 .addCustom(tl -> {
@@ -645,6 +493,8 @@ public long getEnergyInputPerSecond() {
     }
 
     public boolean drainEnergy(boolean simulate) {
+        if(true) return true;
+
         long energyToDrain = isClean() ? 4 :
                 GTValues.VA[getEnergyTier()];
         long resultEnergy = energyContainer.getEnergyStored() - energyToDrain;
@@ -668,13 +518,7 @@ public  T getCapability(Capability capability, EnumFacing side) {
     @Override
     public void receiveCustomData(int dataId, PacketBuffer buf) {
         super.receiveCustomData(dataId, buf);
-        if (dataId == GregtechDataCodes.UPDATE_STRUCTURE_SIZE) {
-            this.lDist = buf.readInt();
-            this.rDist = buf.readInt();
-            this.bDist = buf.readInt();
-            this.fDist = buf.readInt();
-            this.hDist = buf.readInt();
-        } else if (dataId == GregtechDataCodes.WORKABLE_ACTIVE) {
+        if (dataId == GregtechDataCodes.WORKABLE_ACTIVE) {
             this.cleanroomLogic.setActive(buf.readBoolean());
             scheduleRenderUpdate();
         } else if (dataId == GregtechDataCodes.WORKING_ENABLED) {
@@ -686,11 +530,6 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
     @Override
     public NBTTagCompound writeToNBT(@NotNull NBTTagCompound data) {
         super.writeToNBT(data);
-        data.setInteger("lDist", this.lDist);
-        data.setInteger("rDist", this.rDist);
-        data.setInteger("bDist", this.fDist);
-        data.setInteger("fDist", this.bDist);
-        data.setInteger("hDist", this.hDist);
         data.setInteger("cleanAmount", this.cleanAmount);
         return this.cleanroomLogic.writeToNBT(data);
     }
@@ -698,11 +537,6 @@ public NBTTagCompound writeToNBT(@NotNull NBTTagCompound data) {
     @Override
     public void readFromNBT(NBTTagCompound data) {
         super.readFromNBT(data);
-        this.lDist = data.hasKey("lDist") ? data.getInteger("lDist") : this.lDist;
-        this.rDist = data.hasKey("rDist") ? data.getInteger("rDist") : this.rDist;
-        this.hDist = data.hasKey("hDist") ? data.getInteger("hDist") : this.hDist;
-        this.bDist = data.hasKey("bDist") ? data.getInteger("bDist") : this.bDist;
-        this.fDist = data.hasKey("fDist") ? data.getInteger("fDist") : this.fDist;
         reinitializeStructurePattern();
         this.cleanAmount = data.getInteger("cleanAmount");
         this.cleanroomLogic.readFromNBT(data);
@@ -711,11 +545,6 @@ public void readFromNBT(NBTTagCompound data) {
     @Override
     public void writeInitialSyncData(PacketBuffer buf) {
         super.writeInitialSyncData(buf);
-        buf.writeInt(this.lDist);
-        buf.writeInt(this.rDist);
-        buf.writeInt(this.bDist);
-        buf.writeInt(this.fDist);
-        buf.writeInt(this.hDist);
         buf.writeInt(this.cleanAmount);
         this.cleanroomLogic.writeInitialSyncData(buf);
     }
@@ -723,11 +552,6 @@ public void writeInitialSyncData(PacketBuffer buf) {
     @Override
     public void receiveInitialSyncData(PacketBuffer buf) {
         super.receiveInitialSyncData(buf);
-        this.lDist = buf.readInt();
-        this.rDist = buf.readInt();
-        this.bDist = buf.readInt();
-        this.fDist = buf.readInt();
-        this.hDist = buf.readInt();
         this.cleanAmount = buf.readInt();
         this.cleanroomLogic.receiveInitialSyncData(buf);
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
index 2d8c7dfd379..114353d6f5c 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
@@ -7,8 +7,8 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.recipes.logic.OCResult;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
index 5b52829f001..2f5eb291067 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
@@ -9,8 +9,8 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.util.TextFormattingUtil;
 import gregtech.client.renderer.ICubeRenderer;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
index c0a4be09a56..232a083e8fc 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
@@ -9,8 +9,8 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMaps;
@@ -106,7 +106,7 @@ protected void addDisplayText(List textList) {
     protected void formStructure(PatternMatchContext context) {
         super.formStructure(context);
         if (this.handler == null || this.structurePatterns[0] == null) return;
-        handler.determineLayerCount(this.structurePatterns[0]);
+        handler.determineLayerCount((BlockPattern) this.structurePatterns[0]);
         handler.determineOrderedFluidOutputs();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index 3d8e8382cbf..7be85c3b91f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -11,8 +11,8 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.Recipe;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
index 01d1ea49951..b1f2cee5496 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
@@ -14,8 +14,8 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.unification.material.Materials;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
index b9a918639f3..f1394164261 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
@@ -23,8 +23,8 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.Recipe;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
index f1bf2dc9593..579526ad35a 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
@@ -13,8 +13,8 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.unification.material.Materials;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java
index 0180b1414c5..6ac37e3465d 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java
@@ -4,8 +4,8 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
index c1b82c34791..091cf68d638 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
@@ -5,8 +5,8 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.recipes.RecipeMaps;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
index a1b05546da1..8a22303c23a 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
@@ -19,8 +19,8 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.recipes.RecipeMaps;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
index 71fca7ba8d4..7fee84451b3 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
@@ -9,8 +9,8 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.ParallelLogicType;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.RecipeBuilder;
 import gregtech.api.recipes.RecipeMaps;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
index fc49827d51b..f2298cbd3f5 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
@@ -9,8 +9,8 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.api.util.TextFormattingUtil;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index 0ff367ea434..e43a0ccb8cb 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -11,6 +11,8 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
 import gregtech.api.pattern.*;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.api.util.TextFormattingUtil;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
index a32eafb8905..4a1b432688d 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
@@ -7,8 +7,8 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.recipes.logic.OCResult;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
index 50923590e11..70b707b5d78 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
@@ -13,8 +13,8 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.Recipe;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
index cb7273c31e3..32e163bf897 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
@@ -5,8 +5,8 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.util.RelativeDirection;
 import gregtech.client.renderer.ICubeRenderer;
@@ -66,7 +66,7 @@ protected void createStructurePatterns() {
     protected void addDisplayText(List textList) {
         super.addDisplayText(textList);
 
-        ITextComponent button = new TextComponentString("Second structure offset: " + structurePatterns[1].getStartOffset(RelativeDirection.FRONT));
+        ITextComponent button = new TextComponentString("Second structure offset: " + ((BlockPattern) structurePatterns[1]).getStartOffset(RelativeDirection.FRONT));
         button.appendText(" ");
         button.appendSibling(withButton(new TextComponentString("[-]"), "sub"));
         button.appendText(" ");
@@ -80,7 +80,7 @@ protected void addDisplayText(List textList) {
     protected void handleDisplayClick(String componentData, Widget.ClickData clickData) {
         super.handleDisplayClick(componentData, clickData);
         int mod = componentData.equals("add") ? 1 : -1;
-        structurePatterns[1].moveStartOffset(RelativeDirection.FRONT, mod);
+        ((BlockPattern) structurePatterns[1]).moveStartOffset(RelativeDirection.FRONT, mod);
         structurePatterns[1].clearCache();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index 54c1ff724ea..48679349fdc 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -16,8 +16,8 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.pipenet.tile.TileEntityPipeBase;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
index 8318e55d737..414f7aecedf 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
@@ -16,8 +16,8 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.unification.material.Materials;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
index 80539cb7c3b..3c5379600a9 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
@@ -10,8 +10,8 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.RecipeMap;
 import gregtech.api.util.TextComponentUtil;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java
index 3055a63ffd6..0069dded23d 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java
@@ -5,8 +5,8 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.RecipeMapSteamMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.client.particle.VanillaParticleEffects;
 import gregtech.client.renderer.ICubeRenderer;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
index b3a3e5fa039..bf29da1e9e9 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
@@ -6,8 +6,8 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.RecipeMapSteamMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.client.particle.VanillaParticleEffects;
 import gregtech.client.renderer.ICubeRenderer;
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index ecbd0241f1d..da0e63021bf 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -12,6 +12,8 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.*;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.util.Mods;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;

From 9645487ebde85edd635a39035d5f779cbf4dabee Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Thu, 18 Jul 2024 15:22:45 -0700
Subject: [PATCH 16/64] use hashmap pt1 + spotless

---
 .../multiblock/IMultiblockPart.java           |  11 ++
 .../multiblock/MultiblockControllerBase.java  | 158 ++++++++----------
 .../gregtech/api/pattern/GreggyBlockPos.java  |   7 +-
 .../api/pattern/PatternMatchContext.java      |   2 +-
 .../api/pattern/pattern/BlockPattern.java     |  20 ++-
 .../pattern/pattern/ExpandablePattern.java    |  15 +-
 .../pattern/pattern/FactoryBlockPattern.java  |  10 +-
 .../pattern/FactoryExpandablePattern.java     |  16 +-
 .../api/pattern/pattern/IBlockPattern.java    |  33 ++--
 .../api/pattern/pattern/PatternInfo.java      |  41 +++++
 .../api/util/function/QuadFunction.java       |   1 +
 .../handler/MultiblockPreviewRenderer.java    |   2 +-
 .../behaviors/MultiblockBuilderBehavior.java  |  18 +-
 .../AdvancedMonitorPluginBehavior.java        | 124 +++++++-------
 .../monitorplugin/FakeGuiPluginBehavior.java  |  60 ++++---
 .../multi/MetaTileEntityLargeBoiler.java      |   2 +-
 .../MetaTileEntityPrimitiveBlastFurnace.java  |   2 +-
 .../MetaTileEntityPrimitiveWaterPump.java     |   2 +-
 .../MetaTileEntityActiveTransformer.java      |   4 +-
 .../electric/MetaTileEntityAssemblyLine.java  |   2 +-
 .../electric/MetaTileEntityCleanroom.java     |  12 +-
 .../electric/MetaTileEntityCrackingUnit.java  |   2 +-
 .../electric/MetaTileEntityDataBank.java      |   2 +-
 .../MetaTileEntityDistillationTower.java      |   6 +-
 .../MetaTileEntityElectricBlastFurnace.java   |   4 +-
 .../electric/MetaTileEntityFluidDrill.java    |   4 +-
 .../electric/MetaTileEntityFusionReactor.java |   4 +-
 .../multi/electric/MetaTileEntityHPCA.java    |   4 +-
 .../MetaTileEntityLargeChemicalReactor.java   |   4 +-
 .../electric/MetaTileEntityLargeMiner.java    |   4 +-
 .../electric/MetaTileEntityMultiSmelter.java  |   2 +-
 .../electric/MetaTileEntityNetworkSwitch.java |   2 +-
 .../MetaTileEntityProcessingArray.java        |   3 +
 .../electric/MetaTileEntityPyrolyseOven.java  |   2 +-
 .../MetaTileEntityResearchStation.java        |   4 +-
 .../electric/MetaTileEntityVacuumFreezer.java |  14 +-
 .../MetaTileEntityCentralMonitor.java         |   2 +-
 .../MetaTileEntityLargeCombustionEngine.java  |   2 +-
 .../generator/MetaTileEntityLargeTurbine.java |   2 +-
 39 files changed, 335 insertions(+), 274 deletions(-)
 create mode 100644 src/main/java/gregtech/api/pattern/pattern/PatternInfo.java

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
index 520a36dcebb..de819ed2158 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
@@ -8,6 +8,17 @@ public interface IMultiblockPart {
 
     void removeFromMultiBlock(MultiblockControllerBase controllerBase);
 
+    default MultiblockControllerBase getAttachedMultiblock() {return null;}
+
+    /**
+     * Gets how many multiblocks are currently using the part.
+     */
+    default int getWallsharedCount() { return 1;}
+
+    default boolean canPartShare(MultiblockControllerBase target, String substructureName) {
+        return canPartShare();
+    }
+
     default boolean canPartShare() {
         return true;
     }
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index c9b76257604..599b7036dd5 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -8,13 +8,14 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
-import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
-import gregtech.api.pattern.pattern.PreviewBlockPattern;
+import gregtech.api.pattern.StructureInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.IBlockPattern;
+import gregtech.api.pattern.pattern.PatternInfo;
+import gregtech.api.pattern.pattern.PreviewBlockPattern;
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.unification.material.Material;
 import gregtech.api.util.BlockInfo;
@@ -53,9 +54,9 @@
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
 import codechicken.lib.vec.Rotation;
-import it.unimi.dsi.fastutil.ints.IntArrayList;
-import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.jetbrains.annotations.ApiStatus;
@@ -80,12 +81,8 @@
 
 import static gregtech.api.capability.GregtechDataCodes.*;
 
-// todo use bitsets you idiot
 public abstract class MultiblockControllerBase extends MetaTileEntity implements IMultiblockController {
 
-    // array is not null, but elements can be null
-    protected @Nullable IBlockPattern @NotNull [] structurePatterns = new IBlockPattern[64];
-
     /**
      * Null until the first time {@link MultiblockControllerBase#getMatchingShapes()} is called, if it is not overriden
      */
@@ -93,13 +90,9 @@ public abstract class MultiblockControllerBase extends MetaTileEntity implements
 
     private final Map, List> multiblockAbilities = new HashMap<>();
     private final List multiblockParts = new ArrayList<>();
-    // private boolean structureFormed;
 
     protected EnumFacing upwardsFacing = EnumFacing.NORTH;
-    // todo unplaceholder value
-    protected boolean[] isFlipped = new boolean[64];
-    protected boolean[] structuresFormed = new boolean[64];
-    protected long lastStructureFormedState = 0;
+    protected final Object2ObjectMap structures = new Object2ObjectOpenHashMap<>();
 
     public MultiblockControllerBase(ResourceLocation metaTileEntityId) {
         super(metaTileEntityId);
@@ -147,16 +140,15 @@ public void update() {
      * frequent new BlockPatterns from being made.
      */
     protected void createStructurePatterns() {
-        structurePatterns[0] = createStructurePattern();
+        structures.put("MAIN", new PatternInfo(createStructurePattern()));
     }
 
     private void validateStructurePatterns() {
-        IntList failures = new IntArrayList();
+        List failures = new ArrayList<>();
 
-        for (int i = 1; i < structurePatterns.length; i++) {
-            // noinspection DataFlowIssue
-            if (structurePatterns[i] != null && structurePatterns[i].legacyBuilderError()) {
-                failures.add(i);
+        for (Object2ObjectMap.Entry pattern : structures.object2ObjectEntrySet()) {
+            if (pattern.getValue().getPattern().legacyBuilderError()) {
+                failures.add(pattern.getKey());
             }
         }
 
@@ -182,8 +174,8 @@ public void setUpwardsFacing(EnumFacing upwardsFacing) {
                 notifyBlockUpdate();
                 markDirty();
                 writeCustomData(UPDATE_UPWARDS_FACING, buf -> buf.writeByte(upwardsFacing.getIndex()));
-                for (IBlockPattern pattern : structurePatterns) {
-                    if (pattern != null) pattern.clearCache();
+                for (PatternInfo pattern : structures.values()) {
+                    pattern.getPattern().clearCache();
                 }
                 checkStructurePatterns();
             }
@@ -191,19 +183,23 @@ public void setUpwardsFacing(EnumFacing upwardsFacing) {
     }
 
     public boolean isFlipped() {
-        return isFlipped[0];
+        if (getSubstructure("MAIN") == null) return false;
+        return getSubstructure("MAIN").isFlipped();
     }
 
     /** Should not be called outside of structure formation logic! */
     @ApiStatus.Internal
-    protected void setFlipped(boolean flipped, int index) {
-        if (index >= 64) throw new IllegalArgumentException("Max structure count of 64, dont @ me");
-        boolean flip = isFlipped[index];
+    protected void setFlipped(boolean flipped, String name) {
+        PatternInfo structure = getSubstructure(name);
+
+        if (structure == null) return;
+
+        boolean flip = structure.isFlipped();
         if (flip != flipped) {
-            isFlipped[index] = flipped;
+            structure.setFlipped(flipped);
             notifyBlockUpdate();
             markDirty();
-            writeCustomData(UPDATE_FLIP, buf -> buf.writeLong(GTUtility.boolArrToLong(isFlipped)));
+            writeCustomData(UPDATE_FLIP, buf -> buf.writeString(name).writeBoolean(flipped));
         }
     }
 
@@ -258,7 +254,6 @@ public static TraceabilityPredicate metaTileEntities(MetaTileEntity... metaTileE
 
     private static Supplier getCandidates(MetaTileEntity... metaTileEntities) {
         return () -> Arrays.stream(metaTileEntities).filter(Objects::nonNull).map(tile -> {
-            // TODO
             MetaTileEntityHolder holder = new MetaTileEntityHolder();
             holder.setMetaTileEntity(tile);
             holder.getMetaTileEntity().onPlacement();
@@ -373,38 +368,27 @@ protected Function multiblockPartSorter() {
         return BlockPos::hashCode;
     }
 
-    /**
-     * Whether a structure at the index should be checked for correct pattern. The check is only performed if this returns true and the
-     * structure is not null. This helps save on performance if there is a redundant structure.
-     * 
-     * @param index The index, with 0 being the main structure
-     * @return True if the structure should be checked
-     */
-    protected boolean shouldCheckStructure(int index) {
-        return true;
-    }
-
     public void checkStructurePattern() {
-        checkStructurePattern(0);
+        checkStructurePattern("MAIN");
     }
 
     public void checkStructurePatterns() {
-        for (int i = 0; i < structurePatterns.length; i++) {
-            if (shouldCheckStructure(i)) checkStructurePattern(i);
+        for (String name : structures.keySet()) {
+            checkStructurePattern(name);
         }
     }
 
-    public void checkStructurePattern(int index) {
-        IBlockPattern pattern = structurePatterns[index];
-        if (pattern == null || !shouldCheckStructure(index)) return;
+    public void checkStructurePattern(String name) {
+        PatternInfo pattern = getSubstructure(name);
+        if (pattern == null || !pattern.shouldUpdate) return;
 
         long time = System.nanoTime();
-        PatternMatchContext context = pattern.checkPatternFastAt(getWorld(), getPos(),
+        PatternMatchContext context = pattern.getPattern().checkPatternFastAt(getWorld(), getPos(),
                 getFrontFacing(), getUpwardsFacing(), allowsFlip());
         System.out.println(
                 "structure check for " + getClass().getSimpleName() + " took " + (System.nanoTime() - time) + " nanos");
 
-        if (context != null && !structuresFormed[index]) {
+        if (context != null && !pattern.isFormed()) {
             Set rawPartsSet = context.getOrCreate("MultiblockParts", HashSet::new);
             ArrayList parts = new ArrayList<>(rawPartsSet);
             for (IMultiblockPart part : parts) {
@@ -414,7 +398,7 @@ public void checkStructurePattern(int index) {
                     }
                 }
             }
-            this.setFlipped(context.neededFlip(), index);
+            this.setFlipped(context.neededFlip(), name);
 
             parts.sort(Comparator.comparing(it -> multiblockPartSorter().apply(((MetaTileEntity) it).getPos())));
             Map, List> abilities = new HashMap<>();
@@ -431,51 +415,57 @@ public void checkStructurePattern(int index) {
             this.multiblockParts.addAll(parts);
             this.multiblockAbilities.putAll(abilities);
             parts.forEach(part -> part.addToMultiBlock(this));
-            writeCustomData(STRUCTURE_FORMED, buf -> buf.writeVarInt(index));
-            formStructure(context, index);
-        } else if (context == null && structuresFormed[index]) {
-            invalidateStructure(index);
+            writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString(name).writeBoolean(true));
+            formStructure(context, name);
+        } else if (context == null && pattern.isFormed()) {
+            invalidateStructure(name);
         } else if (context != null) {
             // ensure flip is ok, possibly not necessary but good to check just in case
             if (context.neededFlip() != isFlipped()) {
-                setFlipped(context.neededFlip(), index);
+                setFlipped(context.neededFlip(), name);
             }
         }
-
-        GTLog.logger.info("Checked pattern " + index + " verdict: " + structuresFormed[index]);
     }
 
     protected void formStructure(PatternMatchContext context) {}
 
-    protected void formStructure(PatternMatchContext context, int index) {
+    protected void formStructure(PatternMatchContext context, String name) {
         // form the main structure
-        if (index == 0) formStructure(context);
+        if ("MAIN".equals(name)) formStructure(context);
 
-        this.structuresFormed[index] = true;
+        if (getSubstructure(name) != null) getSubstructure(name).setFormed(true);
     }
 
     public void invalidateStructure() {
         this.multiblockParts.forEach(part -> part.removeFromMultiBlock(this));
         this.multiblockAbilities.clear();
         this.multiblockParts.clear();
-        Arrays.fill(structuresFormed, false);
-        Arrays.fill(isFlipped, false);
-        writeCustomData(STRUCTURE_FORMED, buf -> buf.writeVarInt(-1));
+        structures.forEach((s, p) -> {
+            p.setFormed(false);
+            p.setFlipped(false);
+        });
+        writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString("null"));
     }
 
-    public void invalidateStructure(int index) {
+    public void invalidateStructure(String name) {
         // invalidate the main structure
-        if (index == 0) invalidateStructure();
-
-        structuresFormed[index] = false;
+        if ("MAIN".equals(name)) invalidateStructure();
+        else {
+            getSubstructure(name).setFormed(false);
+            writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString(name).writeBoolean(false));
+        }
     }
 
     protected void invalidStructureCaches() {
-        for (IBlockPattern pattern : structurePatterns) {
-            if (pattern != null) pattern.clearCache();
+        for (PatternInfo pattern : structures.values()) {
+            pattern.getPattern().clearCache();
         }
     }
 
+    protected PatternInfo getSubstructure(String name) {
+        return structures.get(name);
+    }
+
     @Override
     public void onRemoval() {
         super.onRemoval();
@@ -500,9 +490,6 @@ public void readFromNBT(NBTTagCompound data) {
         if (data.hasKey("UpwardsFacing")) {
             this.upwardsFacing = EnumFacing.VALUES[data.getByte("UpwardsFacing")];
         }
-        if (data.hasKey("IsFlipped")) {
-            GTUtility.longToBoolArr(data.getLong("IsFlipped"), isFlipped);
-        }
         this.reinitializeStructurePattern();
     }
 
@@ -510,7 +497,6 @@ public void readFromNBT(NBTTagCompound data) {
     public NBTTagCompound writeToNBT(NBTTagCompound data) {
         super.writeToNBT(data);
         data.setByte("UpwardsFacing", (byte) upwardsFacing.getIndex());
-        data.setLong("IsFlipped", GTUtility.boolArrToLong(isFlipped));
         return data;
     }
 
@@ -518,16 +504,15 @@ public NBTTagCompound writeToNBT(NBTTagCompound data) {
     public void writeInitialSyncData(PacketBuffer buf) {
         super.writeInitialSyncData(buf);
         buf.writeByte(upwardsFacing.getIndex());
-        buf.writeLong(GTUtility.boolArrToLong(structuresFormed));
-        buf.writeLong(GTUtility.boolArrToLong(isFlipped));
+        // todo see if necessary to sync this
+        // buf.writeLong(GTUtility.boolArrToLong(s));
     }
 
     @Override
     public void receiveInitialSyncData(PacketBuffer buf) {
         super.receiveInitialSyncData(buf);
         this.upwardsFacing = EnumFacing.VALUES[buf.readByte()];
-        GTUtility.longToBoolArr(buf.readLong(), structuresFormed);
-        GTUtility.longToBoolArr(buf.readLong(), isFlipped);
+        // GTUtility.longToBoolArr(buf.readLong(), structuresFormed);
     }
 
     @Override
@@ -537,17 +522,19 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
             this.upwardsFacing = EnumFacing.VALUES[buf.readByte()];
             scheduleRenderUpdate();
         } else if (dataId == STRUCTURE_FORMED) {
-            int result = buf.readVarInt();
-            if (result == -1) {
-                Arrays.fill(structuresFormed, false);
+            // it forces me so uh yay
+            String name = buf.readString(65536);
+            if ("null".equals(name)) {
+                for (PatternInfo pattern : structures.values()) {
+                    pattern.setFormed(false);
+                }
             } else {
-                structuresFormed[result] = true;
+                getSubstructure(name).setFormed(false);
             }
+
             if (!isStructureFormed()) {
                 GregTechAPI.soundManager.stopTileSound(getPos());
             }
-        } else if (dataId == UPDATE_FLIP) {
-            GTUtility.longToBoolArr(buf.readLong(), isFlipped);
         }
     }
 
@@ -563,7 +550,11 @@ public  T getCapability(Capability capability, EnumFacing side) {
     }
 
     public boolean isStructureFormed() {
-        return structuresFormed[0];
+        return isStructureFormed("MAIN");
+    }
+
+    public boolean isStructureFormed(String name) {
+        return getSubstructure(name).isFormed();
     }
 
     @Override
@@ -665,10 +656,9 @@ public List getBuildableShapes(@Nullable Object2IntMap T getOrDefault(String key, T defaultValue) {
-        //noinspection unchecked
+        // noinspection unchecked
         return (T) data.getOrDefault(key, defaultValue);
     }
 
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index ac6e54317e1..0a4a412ed29 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -106,8 +106,10 @@ private void legacyStartOffset(char center) {
                 // when legacyStartOffset() is called, aisles have been reversed, so don't reverse it manually here
                 // the scuffed ternary is so that if the structure dir is the second thing, then don't reverse it
                 startOffset[structureDir[0].ordinal() / 2] = aisleI * (structureDir[0].ordinal() % 2 == 0 ? -1 : 1);
-                startOffset[structureDir[1].ordinal() / 2] = (dimensions[1] - 1 - result[0]) * (structureDir[1].ordinal() % 2 == 0 ? -1 : 1);
-                startOffset[structureDir[2].ordinal() / 2] = (dimensions[2] - 1 - result[1]) * (structureDir[2].ordinal() % 2 == 0 ? -1 : 1);
+                startOffset[structureDir[1].ordinal() / 2] = (dimensions[1] - 1 - result[0]) *
+                        (structureDir[1].ordinal() % 2 == 0 ? -1 : 1);
+                startOffset[structureDir[2].ordinal() / 2] = (dimensions[2] - 1 - result[1]) *
+                        (structureDir[2].ordinal() % 2 == 0 ? -1 : 1);
                 return;
             }
         }
@@ -499,14 +501,17 @@ private GreggyBlockPos startPos(GreggyBlockPos controllerPos, EnumFacing frontFa
                                     boolean flip) {
         GreggyBlockPos start = controllerPos.copy();
         for (int i = 0; i < 3; i++) {
-            start.offset(RelativeDirection.VALUES[2 * i].getRelativeFacing(frontFacing, upFacing, flip), startOffset[i]);
+            start.offset(RelativeDirection.VALUES[2 * i].getRelativeFacing(frontFacing, upFacing, flip),
+                    startOffset[i]);
         }
         return start;
     }
 
     /**
-     * Moves the start offset in the given direction and amount, use {@link BlockPattern#clearCache()} after to prevent the cache from being stuck in the old offset.
-     * @param dir The direction, relative to controller.
+     * Moves the start offset in the given direction and amount, use {@link BlockPattern#clearCache()} after to prevent
+     * the cache from being stuck in the old offset.
+     * 
+     * @param dir    The direction, relative to controller.
      * @param amount The amount to offset.
      */
     public void moveStartOffset(RelativeDirection dir, int amount) {
@@ -516,7 +521,9 @@ public void moveStartOffset(RelativeDirection dir, int amount) {
     }
 
     /**
-     * Gets the start offset. You probably should use {@link BlockPattern#moveStartOffset(RelativeDirection, int)} instead of mutating the result, but I can't stop you.
+     * Gets the start offset. You probably should use {@link BlockPattern#moveStartOffset(RelativeDirection, int)}
+     * instead of mutating the result, but I can't stop you.
+     * 
      * @return The start offset.
      */
     public int[] getStartOffset() {
@@ -525,6 +532,7 @@ public int[] getStartOffset() {
 
     /**
      * Get the start offset in the given direction.
+     * 
      * @param dir The direction, relative to controller.
      * @return The amount, can be negative.
      */
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index 5c4c9bedc45..eec20e31d77 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -25,6 +25,7 @@
 import java.util.function.BiFunction;
 
 public class ExpandablePattern implements IBlockPattern {
+
     protected final QuadFunction boundsFunction;
     protected final BiFunction predicateFunction;
 
@@ -38,7 +39,9 @@ public class ExpandablePattern implements IBlockPattern {
     protected final Long2ObjectMap cache = new Long2ObjectOpenHashMap<>();
     protected final Object2IntMap globalCount = new Object2IntOpenHashMap<>();
 
-    public ExpandablePattern(@NotNull QuadFunction boundsFunction, @NotNull BiFunction predicateFunction, @NotNull RelativeDirection[] directions) {
+    public ExpandablePattern(@NotNull QuadFunction boundsFunction,
+                             @NotNull BiFunction predicateFunction,
+                             @NotNull RelativeDirection[] directions) {
         this.boundsFunction = boundsFunction;
         this.predicateFunction = predicateFunction;
         this.directions = directions;
@@ -107,8 +110,10 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
             int negativeOrdinal = opposite.ordinal();
 
             // todo maybe fix this to allow flipping and update the quadfunction
-            negativeCorner.offset(opposite.getRelativeFacing(frontFacing, upwardsFacing, false), bounds[negativeOrdinal]);
-            positiveCorner.offset(selected.getRelativeFacing(frontFacing, upwardsFacing, false), bounds[positiveOrdinal]);
+            negativeCorner.offset(opposite.getRelativeFacing(frontFacing, upwardsFacing, false),
+                    bounds[negativeOrdinal]);
+            positiveCorner.offset(selected.getRelativeFacing(frontFacing, upwardsFacing, false),
+                    bounds[positiveOrdinal]);
         }
 
         // which way each direction progresses absolutely, in [ char, string, aisle ]
@@ -117,8 +122,6 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
             facings[i] = directions[2 - i].getRelativeFacing(frontFacing, upwardsFacing, false);
         }
 
-
-
         worldState.setWorld(world);
         // this translates from the relative coordinates to world coordinates
         GreggyBlockPos translation = new GreggyBlockPos(centerPos);
@@ -136,7 +139,7 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
                         !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
             }
 
-//            GTLog.logger.info("Checked pos at " + pos);
+            // GTLog.logger.info("Checked pos at " + pos);
 
             boolean result = predicate.test(worldState, info, globalCount, null);
             if (!result) return false;
diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
index 1d34a84f942..b55d3837902 100644
--- a/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
@@ -5,10 +5,8 @@
 import gregtech.api.util.RelativeDirection;
 
 import com.google.common.base.Joiner;
-
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
-
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.NotNull;
@@ -50,7 +48,7 @@ public class FactoryBlockPattern {
      */
     private final int[] dimensions = { -1, -1, -1 };
     /**
-     * Look at the field with the same name in  {@link BlockPattern} for docs
+     * Look at the field with the same name in {@link BlockPattern} for docs
      */
     private int[] startOffset;
     private char centerChar;
@@ -170,7 +168,8 @@ public FactoryBlockPattern setRepeatable(int repeatCount) {
 
     /**
      * Sets a part of the start offset in the given direction.
-     * @param dir The direction to offset, relative to controller.
+     * 
+     * @param dir    The direction to offset, relative to controller.
      * @param amount The amount to offset.
      */
     public FactoryBlockPattern startOffset(RelativeDirection dir, int amount) {
@@ -232,7 +231,8 @@ public BlockPattern build() {
         if (reverse) {
             ArrayUtils.reverse(aisleArray);
         } else {
-            if (startOffset == null) GTLog.logger.warn("You used .modern() on the builder without using .startOffset()! This will have unintended behavior!");
+            if (startOffset == null) GTLog.logger.warn(
+                    "You used .modern() on the builder without using .startOffset()! This will have unintended behavior!");
         }
         return new BlockPattern(aisleArray, dimensions, structureDir, startOffset, symbolMap, centerChar);
     }
diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
index 4aa2da52193..a73e33755a5 100644
--- a/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
@@ -11,11 +11,13 @@
 import java.util.function.BiFunction;
 
 public class FactoryExpandablePattern {
+
     protected QuadFunction boundsFunction;
     protected BiFunction predicateFunction;
     protected final RelativeDirection[] structureDir = new RelativeDirection[3];
 
-    private FactoryExpandablePattern(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
+    private FactoryExpandablePattern(RelativeDirection aisleDir, RelativeDirection stringDir,
+                                     RelativeDirection charDir) {
         structureDir[0] = aisleDir;
         structureDir[1] = stringDir;
         structureDir[2] = charDir;
@@ -39,8 +41,9 @@ private FactoryExpandablePattern(RelativeDirection aisleDir, RelativeDirection s
         if (flags != 0x7) throw new IllegalArgumentException("Must have 3 different axes!");
     }
 
-    public static FactoryExpandablePattern start(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
-        return new FactoryExpandablePattern(aisleDir,stringDir, charDir);
+    public static FactoryExpandablePattern start(RelativeDirection aisleDir, RelativeDirection stringDir,
+                                                 RelativeDirection charDir) {
+        return new FactoryExpandablePattern(aisleDir, stringDir, charDir);
     }
 
     public static FactoryExpandablePattern start() {
@@ -58,10 +61,11 @@ public FactoryExpandablePattern predicateFunction(BiFunction {
+
     R apply(A a, B b, C c, D d);
 }
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index 448801e5087..e64d7ec31f6 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -109,7 +109,7 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas
         // todo fix
         BlockInfo[][][] blocks = null;
         // yay unreachable statements
-        if(true) return;
+        if (true) return;
         Map blockMap = new HashMap<>();
         int maxY = 0;
         for (int x = 0; x < blocks.length; x++) {
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index b22915acfe0..c16693c4729 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -4,7 +4,6 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.PatternError;
 import gregtech.api.util.GTUtility;
 
 import net.minecraft.client.resources.I18n;
@@ -17,7 +16,6 @@
 import net.minecraft.util.EnumHand;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.text.Style;
-import net.minecraft.util.text.TextComponentString;
 import net.minecraft.util.text.TextComponentTranslation;
 import net.minecraft.util.text.TextFormatting;
 import net.minecraft.world.World;
@@ -44,20 +42,20 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
             if (!multiblock.isStructureFormed()) {
-//                multiblock.structurePatterns[0].autoBuild(player, multiblock);
+                // multiblock.structurePatterns[0].autoBuild(player, multiblock);
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
         } else {
             // If not sneaking, try to show structure debug info (if any) in chat.
             if (!multiblock.isStructureFormed()) {
-//                PatternError error = multiblock.structurePatterns[0].getError();
-//                if (error != null) {
-//                    player.sendMessage(
-//                            new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header"));
-//                    player.sendMessage(new TextComponentString(error.getErrorInfo()));
-//                    return EnumActionResult.SUCCESS;
-//                }
+                // PatternError error = multiblock.structurePatterns[0].getError();
+                // if (error != null) {
+                // player.sendMessage(
+                // new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header"));
+                // player.sendMessage(new TextComponentString(error.getErrorInfo()));
+                // return EnumActionResult.SUCCESS;
+                // }
             }
             player.sendMessage(new TextComponentTranslation("gregtech.multiblock.pattern.no_errors")
                     .setStyle(new Style().setColor(TextFormatting.GREEN)));
diff --git a/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
index 70d835a3730..83fd0ebcec5 100644
--- a/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
@@ -6,17 +6,13 @@
 import gregtech.api.gui.widgets.ToggleButtonWidget;
 import gregtech.api.items.behavior.MonitorPluginBaseBehavior;
 import gregtech.api.items.behavior.ProxyHolderPluginBehavior;
-import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
-import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.client.renderer.scene.FBOWorldSceneRenderer;
 import gregtech.client.renderer.scene.WorldSceneRenderer;
 import gregtech.client.utils.RenderUtil;
 import gregtech.client.utils.TrackedDummyWorld;
 import gregtech.common.gui.widget.WidgetScrollBar;
 import gregtech.common.gui.widget.monitor.WidgetPluginConfig;
-import gregtech.common.metatileentities.multi.electric.centralmonitor.MetaTileEntityCentralMonitor;
 import gregtech.common.metatileentities.multi.electric.centralmonitor.MetaTileEntityMonitorScreen;
 
 import net.minecraft.block.state.IBlockState;
@@ -45,13 +41,11 @@
 import codechicken.lib.render.pipeline.ColourMultiplier;
 import codechicken.lib.vec.Cuboid6;
 import codechicken.lib.vec.Translation;
-import org.apache.commons.lang3.tuple.MutablePair;
 import org.apache.commons.lang3.tuple.Pair;
 import org.lwjgl.input.Mouse;
 import org.lwjgl.opengl.GL11;
 
 import java.util.*;
-import java.util.stream.Collectors;
 
 import javax.vecmath.Vector3f;
 
@@ -219,65 +213,65 @@ public void setConfig(float scale, int rY, int rX, float spin, boolean connect)
 
     @Override
     public void update() {
-//        super.update();
-//        if (this.screen.getOffsetTimer() % 20 == 0) {
-//            if (this.screen.getWorld().isRemote) { // check connections
-//                if (worldSceneRenderer == null && validPos != null && validPos.size() > 0) {
-//                    createWorldScene();
-//                }
-//                if (this.connect && worldSceneRenderer != null &&
-//                        this.screen.getController() instanceof MetaTileEntityCentralMonitor) {
-//                    if (connections == null) connections = new HashMap<>();
-//                    connections.clear();
-//                    for (MetaTileEntityMonitorScreen[] monitorScreens : ((MetaTileEntityCentralMonitor) this.screen
-//                            .getController()).screens) {
-//                        for (MetaTileEntityMonitorScreen screen : monitorScreens) {
-//                            if (screen != null && screen.plugin instanceof FakeGuiPluginBehavior &&
-//                                    ((FakeGuiPluginBehavior) screen.plugin).getHolder() == this.holder) {
-//                                MetaTileEntity met = ((FakeGuiPluginBehavior) screen.plugin).getRealMTE();
-//                                if (met != null) {
-//                                    BlockPos pos = met.getPos();
-//                                    Pair, Vector3f> tuple = connections
-//                                            .getOrDefault(pos, new MutablePair<>(new ArrayList<>(), null));
-//                                    tuple.getLeft().add(screen);
-//                                    connections.put(pos, tuple);
-//                                }
-//                            }
-//                        }
-//                    }
-//                }
-//            } else { // check multi-block valid
-//                if (holder != null && holder.getMetaTileEntity() instanceof MultiblockControllerBase) {
-//                    MultiblockControllerBase entity = (MultiblockControllerBase) holder.getMetaTileEntity();
-//                    if (entity.isStructureFormed()) {
-//                        if (!isValid) {
-//                            PatternMatchContext result = entity.structurePattern.checkPatternFastAt(
-//                                    entity.getWorld(), entity.getPos(), entity.getFrontFacing().getOpposite(),
-//                                    entity.getUpwardsFacing(), entity.allowsFlip());
-//                            if (result != null) {
-//                                validPos = entity.structurePattern.cache.keySet().stream().map(BlockPos::fromLong)
-//                                        .collect(Collectors.toSet());
-//                                writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> {
-//                                    buf.writeVarInt(validPos.size());
-//                                    for (BlockPos pos : validPos) {
-//                                        buf.writeBlockPos(pos);
-//                                    }
-//                                });
-//                                isValid = true;
-//                            } else {
-//                                validPos = Collections.emptySet();
-//                            }
-//                        }
-//                    } else if (isValid) {
-//                        writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> buf.writeVarInt(0));
-//                        isValid = false;
-//                    }
-//                }
-//            }
-//        }
-//        if (this.screen.getWorld().isRemote && spin > 0 && lastMouse == null) {
-//            rotationPitch = (int) ((rotationPitch + spin * 4) % 360);
-//        }
+        // super.update();
+        // if (this.screen.getOffsetTimer() % 20 == 0) {
+        // if (this.screen.getWorld().isRemote) { // check connections
+        // if (worldSceneRenderer == null && validPos != null && validPos.size() > 0) {
+        // createWorldScene();
+        // }
+        // if (this.connect && worldSceneRenderer != null &&
+        // this.screen.getController() instanceof MetaTileEntityCentralMonitor) {
+        // if (connections == null) connections = new HashMap<>();
+        // connections.clear();
+        // for (MetaTileEntityMonitorScreen[] monitorScreens : ((MetaTileEntityCentralMonitor) this.screen
+        // .getController()).screens) {
+        // for (MetaTileEntityMonitorScreen screen : monitorScreens) {
+        // if (screen != null && screen.plugin instanceof FakeGuiPluginBehavior &&
+        // ((FakeGuiPluginBehavior) screen.plugin).getHolder() == this.holder) {
+        // MetaTileEntity met = ((FakeGuiPluginBehavior) screen.plugin).getRealMTE();
+        // if (met != null) {
+        // BlockPos pos = met.getPos();
+        // Pair, Vector3f> tuple = connections
+        // .getOrDefault(pos, new MutablePair<>(new ArrayList<>(), null));
+        // tuple.getLeft().add(screen);
+        // connections.put(pos, tuple);
+        // }
+        // }
+        // }
+        // }
+        // }
+        // } else { // check multi-block valid
+        // if (holder != null && holder.getMetaTileEntity() instanceof MultiblockControllerBase) {
+        // MultiblockControllerBase entity = (MultiblockControllerBase) holder.getMetaTileEntity();
+        // if (entity.isStructureFormed()) {
+        // if (!isValid) {
+        // PatternMatchContext result = entity.structurePattern.checkPatternFastAt(
+        // entity.getWorld(), entity.getPos(), entity.getFrontFacing().getOpposite(),
+        // entity.getUpwardsFacing(), entity.allowsFlip());
+        // if (result != null) {
+        // validPos = entity.structurePattern.cache.keySet().stream().map(BlockPos::fromLong)
+        // .collect(Collectors.toSet());
+        // writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> {
+        // buf.writeVarInt(validPos.size());
+        // for (BlockPos pos : validPos) {
+        // buf.writeBlockPos(pos);
+        // }
+        // });
+        // isValid = true;
+        // } else {
+        // validPos = Collections.emptySet();
+        // }
+        // }
+        // } else if (isValid) {
+        // writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> buf.writeVarInt(0));
+        // isValid = false;
+        // }
+        // }
+        // }
+        // }
+        // if (this.screen.getWorld().isRemote && spin > 0 && lastMouse == null) {
+        // rotationPitch = (int) ((rotationPitch + spin * 4) % 360);
+        // }
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/items/behaviors/monitorplugin/FakeGuiPluginBehavior.java b/src/main/java/gregtech/common/items/behaviors/monitorplugin/FakeGuiPluginBehavior.java
index 012bcbf4570..133755f5334 100644
--- a/src/main/java/gregtech/common/items/behaviors/monitorplugin/FakeGuiPluginBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/monitorplugin/FakeGuiPluginBehavior.java
@@ -13,9 +13,6 @@
 import gregtech.api.items.toolitem.ToolHelper;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
-import gregtech.api.metatileentity.multiblock.IMultiblockPart;
-import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.util.GTLog;
 import gregtech.api.util.GregFakePlayer;
 import gregtech.common.gui.impl.FakeModularUIPluginContainer;
@@ -28,7 +25,6 @@
 import net.minecraft.inventory.IInventory;
 import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.network.PacketBuffer;
-import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.EnumHand;
 import net.minecraft.util.math.BlockPos;
@@ -67,34 +63,34 @@ public void setConfig(int partIndex) {
 
     public MetaTileEntity getRealMTE() {
         return null;
-//        MetaTileEntity target = this.holder.getMetaTileEntity();
-//        if (target instanceof MultiblockControllerBase multi && partIndex > 0) {
-//            if (partPos != null) {
-//                TileEntity entity = this.screen.getWorld().getTileEntity(partPos);
-//                if (entity instanceof IGregTechTileEntity) {
-//                    return ((IGregTechTileEntity) entity).getMetaTileEntity();
-//                } else {
-//                    partPos = null;
-//                    return null;
-//                }
-//            }
-//            PatternMatchContext context = multi.structurePattern.checkPatternFastAt(
-//                    target.getWorld(), target.getPos(), target.getFrontFacing().getOpposite(), multi.getUpwardsFacing(),
-//                    multi.allowsFlip());
-//            if (context == null) {
-//                return null;
-//            }
-//            Set rawPartsSet = context.getOrCreate("MultiblockParts", HashSet::new);
-//            List parts = new ArrayList<>(rawPartsSet);
-//            parts.sort(Comparator.comparing((it) -> ((MetaTileEntity) it).getPos().hashCode()));
-//            if (parts.size() > partIndex - 1 && parts.get(partIndex - 1) instanceof MetaTileEntity) {
-//                target = (MetaTileEntity) parts.get(partIndex - 1);
-//                partPos = target.getPos();
-//            } else {
-//                return null;
-//            }
-//        }
-//        return target;
+        // MetaTileEntity target = this.holder.getMetaTileEntity();
+        // if (target instanceof MultiblockControllerBase multi && partIndex > 0) {
+        // if (partPos != null) {
+        // TileEntity entity = this.screen.getWorld().getTileEntity(partPos);
+        // if (entity instanceof IGregTechTileEntity) {
+        // return ((IGregTechTileEntity) entity).getMetaTileEntity();
+        // } else {
+        // partPos = null;
+        // return null;
+        // }
+        // }
+        // PatternMatchContext context = multi.structurePattern.checkPatternFastAt(
+        // target.getWorld(), target.getPos(), target.getFrontFacing().getOpposite(), multi.getUpwardsFacing(),
+        // multi.allowsFlip());
+        // if (context == null) {
+        // return null;
+        // }
+        // Set rawPartsSet = context.getOrCreate("MultiblockParts", HashSet::new);
+        // List parts = new ArrayList<>(rawPartsSet);
+        // parts.sort(Comparator.comparing((it) -> ((MetaTileEntity) it).getPos().hashCode()));
+        // if (parts.size() > partIndex - 1 && parts.get(partIndex - 1) instanceof MetaTileEntity) {
+        // target = (MetaTileEntity) parts.get(partIndex - 1);
+        // partPos = target.getPos();
+        // } else {
+        // return null;
+        // }
+        // }
+        // return target;
     }
 
     public void createFakeGui() {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
index e0dfd0091b3..55c5ad1cd93 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
@@ -14,9 +14,9 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
+import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.api.util.TextFormattingUtil;
 import gregtech.client.renderer.ICubeRenderer;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
index bf6dbb68782..8915e22f95d 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
@@ -11,9 +11,9 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.RecipeMapPrimitiveMultiblockController;
+import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.util.GTUtility;
 import gregtech.client.particle.VanillaParticleEffects;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
index 7eac78ad838..5fbd95343a4 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
@@ -6,9 +6,9 @@
 import gregtech.api.metatileentity.multiblock.IPrimitivePump;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.unification.material.Materials;
 import gregtech.api.util.LocalizationUtils;
 import gregtech.client.renderer.ICubeRenderer;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
index 8afc9009f6e..c8ff57b536d 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
@@ -11,10 +11,10 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.pattern.BlockPattern;
-import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.api.util.TextFormattingUtil;
 import gregtech.client.renderer.ICubeRenderer;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
index 06019d821e9..f6c17b8df45 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
@@ -8,9 +8,9 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
+import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.recipes.ingredients.GTRecipeInput;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index 85106db781e..03c4f33bbe0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -27,11 +27,9 @@
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.PatternStringError;
 import gregtech.api.pattern.TraceabilityPredicate;
-import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryExpandablePattern;
 import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.util.BlockInfo;
-import gregtech.api.util.GTLog;
 import gregtech.api.util.GTUtility;
 import gregtech.api.util.Mods;
 import gregtech.api.util.RelativeDirection;
@@ -138,8 +136,8 @@ protected void formStructure(PatternMatchContext context) {
         // max progress is based on the dimensions of the structure: (x^3)-(x^2)
         // taller cleanrooms take longer than wider ones
         // minimum of 100 is a 5x5x5 cleanroom: 125-25=100 ticks
-//        this.cleanroomLogic.setMaxProgress(Math.max(100,
-//                ((lDist + rDist + 1) * (bDist + fDist + 1) * hDist) - ((lDist + rDist + 1) * (bDist + fDist + 1))));
+        // this.cleanroomLogic.setMaxProgress(Math.max(100,
+        // ((lDist + rDist + 1) * (bDist + fDist + 1) * hDist) - ((lDist + rDist + 1) * (bDist + fDist + 1))));
         this.cleanroomLogic.setMaxProgress(100);
         this.cleanroomLogic.setMinEnergyTier(cleanroomFilter.getMinTier());
     }
@@ -186,7 +184,6 @@ public boolean allowsFlip() {
     @NotNull
     @Override
     protected IBlockPattern createStructurePattern() {
-
         TraceabilityPredicate wallPredicate = states(getCasingState(), getGlassState());
         TraceabilityPredicate basePredicate = autoAbilities().or(abilities(MultiblockAbility.INPUT_ENERGY)
                 .setMinGlobalLimited(1).setMaxGlobalLimited(3));
@@ -363,12 +360,11 @@ protected void handleDisplayClick(String componentData, Widget.ClickData clickDa
             case "addl" -> bounds[2] += 1;
         }
 
-        structurePatterns[0].clearCache();
+        getSubstructure("MAIN").getPattern().clearCache();
     }
 
     @Override
     protected void addWarningText(List textList) {
-
         MultiblockDisplayText.builder(textList, isStructureFormed(), false)
                 .addLowPowerLine(!drainEnergy(true))
                 .addCustom(tl -> {
@@ -493,7 +489,7 @@ public long getEnergyInputPerSecond() {
     }
 
     public boolean drainEnergy(boolean simulate) {
-        if(true) return true;
+        if (true) return true;
 
         long energyToDrain = isClean() ? 4 :
                 GTValues.VA[getEnergyTier()];
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
index 114353d6f5c..bf8c4b90b61 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
@@ -7,9 +7,9 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
+import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.recipes.logic.OCResult;
 import gregtech.api.recipes.properties.RecipePropertyStorage;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
index 2f5eb291067..4d4c1e0fcbd 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
@@ -9,9 +9,9 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
+import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.util.TextFormattingUtil;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
index 232a083e8fc..ccc99757a49 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
@@ -9,9 +9,9 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
+import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.util.GTTransferUtils;
@@ -105,8 +105,8 @@ protected void addDisplayText(List textList) {
     @Override
     protected void formStructure(PatternMatchContext context) {
         super.formStructure(context);
-        if (this.handler == null || this.structurePatterns[0] == null) return;
-        handler.determineLayerCount((BlockPattern) this.structurePatterns[0]);
+        if (this.handler == null) return;
+        handler.determineLayerCount((BlockPattern) getSubstructure("MAIN").getPattern());
         handler.determineOrderedFluidOutputs();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index 7be85c3b91f..ef63b307dd6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -11,10 +11,10 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.pattern.BlockPattern;
-import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.recipes.properties.impl.TemperatureProperty;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
index b1f2cee5496..d6cebbe388f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
@@ -14,10 +14,10 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
-import gregtech.api.pattern.pattern.BlockPattern;
-import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.unification.material.Materials;
 import gregtech.api.util.GTTransferUtils;
 import gregtech.api.util.GTUtility;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
index f1394164261..708c75e8935 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
@@ -23,10 +23,10 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.pattern.BlockPattern;
-import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.recipes.logic.OCParams;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
index 579526ad35a..13dbea27fc9 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
@@ -13,10 +13,10 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
-import gregtech.api.pattern.pattern.BlockPattern;
-import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.unification.material.Materials;
 import gregtech.api.util.GTUtility;
 import gregtech.api.util.RelativeDirection;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
index 091cf68d638..442040c375f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
@@ -5,10 +5,10 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.pattern.BlockPattern;
-import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
index 8a22303c23a..24d160295a5 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
@@ -19,10 +19,10 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.pattern.BlockPattern;
-import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.unification.material.Material;
 import gregtech.api.unification.material.Materials;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
index 7fee84451b3..ae3f90344e4 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
@@ -9,9 +9,9 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.ParallelLogicType;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
+import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.RecipeBuilder;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.recipes.logic.OCParams;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
index f2298cbd3f5..88b95997957 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
@@ -9,9 +9,9 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
+import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.api.util.TextFormattingUtil;
 import gregtech.client.renderer.ICubeRenderer;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
index 4759bb1dec7..a7964e6efde 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
@@ -9,6 +9,7 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
+import gregtech.api.metatileentity.multiblock.*;
 import gregtech.api.metatileentity.multiblock.DummyCleanroom;
 import gregtech.api.metatileentity.multiblock.ICleanroomProvider;
 import gregtech.api.metatileentity.multiblock.ICleanroomReceiver;
@@ -20,6 +21,8 @@
 import gregtech.api.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMap;
 import gregtech.api.recipes.logic.OCParams;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
index 4a1b432688d..7df70856730 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
@@ -7,9 +7,9 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
+import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.recipes.logic.OCResult;
 import gregtech.api.recipes.properties.RecipePropertyStorage;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
index 70b707b5d78..b04481adfae 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
@@ -13,10 +13,10 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.pattern.BlockPattern;
-import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
+import gregtech.api.pattern.pattern.BlockPattern;
+import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.util.GTUtility;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
index 32e163bf897..fe68cfc06d0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
@@ -7,6 +7,7 @@
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.PatternInfo;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.util.RelativeDirection;
 import gregtech.client.renderer.ICubeRenderer;
@@ -55,33 +56,34 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     protected void createStructurePatterns() {
         super.createStructurePatterns();
-        structurePatterns[1] = FactoryBlockPattern.start()
+        structures.put("SECOND", new PatternInfo(FactoryBlockPattern.start()
                 .aisle("X")
                 .where('X', states(getCasingState()))
                 .startOffset(RelativeDirection.FRONT, 5)
-                .build();
+                .build()));
     }
 
     @Override
     protected void addDisplayText(List textList) {
         super.addDisplayText(textList);
 
-        ITextComponent button = new TextComponentString("Second structure offset: " + ((BlockPattern) structurePatterns[1]).getStartOffset(RelativeDirection.FRONT));
+        ITextComponent button = new TextComponentString("Second structure offset: " +
+                ((BlockPattern) getSubstructure("SECOND").getPattern()).getStartOffset(RelativeDirection.FRONT));
         button.appendText(" ");
         button.appendSibling(withButton(new TextComponentString("[-]"), "sub"));
         button.appendText(" ");
         button.appendSibling(withButton(new TextComponentString("[+]"), "add"));
         textList.add(button);
 
-        textList.add(new TextComponentString("Second structure: " + (structuresFormed[1] ? "FORMED" : "UNFORMED")));
+        textList.add(new TextComponentString("Second structure: " + (isStructureFormed("SECOND") ? "FORMED" : "UNFORMED")));
     }
 
     @Override
     protected void handleDisplayClick(String componentData, Widget.ClickData clickData) {
         super.handleDisplayClick(componentData, clickData);
         int mod = componentData.equals("add") ? 1 : -1;
-        ((BlockPattern) structurePatterns[1]).moveStartOffset(RelativeDirection.FRONT, mod);
-        structurePatterns[1].clearCache();
+        ((BlockPattern) getSubstructure("SECOND").getPattern()).moveStartOffset(RelativeDirection.FRONT, mod);
+        getSubstructure("SECOND").getPattern().clearCache();
     }
 
     @SideOnly(Side.CLIENT)
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index 48679349fdc..5ed7b1afc1e 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -16,9 +16,9 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
+import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.pipenet.tile.TileEntityPipeBase;
 import gregtech.api.util.FacingPos;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
index 414f7aecedf..34148075986 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
@@ -16,9 +16,9 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
+import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.unification.material.Materials;
 import gregtech.api.util.RelativeDirection;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
index 3c5379600a9..5a08134d673 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
@@ -10,9 +10,9 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
+import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.recipes.RecipeMap;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.api.util.TextFormattingUtil;

From cc0e227832a1c0d5e00c2b4158a7cee27595888b Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Thu, 18 Jul 2024 21:43:32 -0700
Subject: [PATCH 17/64] javadocs + default cause lazy + fix yo stuff

---
 .../multiblock/IMultiblockPart.java           |  1 +
 .../multiblock/MultiblockControllerBase.java  | 22 +++++++++++--------
 .../api/pattern/pattern/BlockPattern.java     | 16 +++++---------
 .../pattern/pattern/ExpandablePattern.java    |  7 ++++++
 .../pattern/FactoryExpandablePattern.java     | 18 +++++++++++++++
 5 files changed, 45 insertions(+), 19 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
index de819ed2158..d07e4b3285a 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
@@ -13,6 +13,7 @@ public interface IMultiblockPart {
     /**
      * Gets how many multiblocks are currently using the part.
      */
+    // todo do a concrete impl
     default int getWallsharedCount() { return 1;}
 
     default boolean canPartShare(MultiblockControllerBase target, String substructureName) {
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 599b7036dd5..9eb32c868a7 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -11,7 +11,6 @@
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternMatchContext;
-import gregtech.api.pattern.StructureInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.pattern.pattern.PatternInfo;
@@ -147,6 +146,8 @@ private void validateStructurePatterns() {
         List failures = new ArrayList<>();
 
         for (Object2ObjectMap.Entry pattern : structures.object2ObjectEntrySet()) {
+            if ("MAIN".equals(pattern.getKey())) continue;
+
             if (pattern.getValue().getPattern().legacyBuilderError()) {
                 failures.add(pattern.getKey());
             }
@@ -183,7 +184,6 @@ public void setUpwardsFacing(EnumFacing upwardsFacing) {
     }
 
     public boolean isFlipped() {
-        if (getSubstructure("MAIN") == null) return false;
         return getSubstructure("MAIN").isFlipped();
     }
 
@@ -368,19 +368,19 @@ protected Function multiblockPartSorter() {
         return BlockPos::hashCode;
     }
 
-    public void checkStructurePattern() {
-        checkStructurePattern("MAIN");
-    }
-
     public void checkStructurePatterns() {
         for (String name : structures.keySet()) {
             checkStructurePattern(name);
         }
     }
 
+    public void checkStructurePattern() {
+        checkStructurePattern("MAIN");
+    }
+
     public void checkStructurePattern(String name) {
         PatternInfo pattern = getSubstructure(name);
-        if (pattern == null || !pattern.shouldUpdate) return;
+        if (!pattern.shouldUpdate) return;
 
         long time = System.nanoTime();
         PatternMatchContext context = pattern.getPattern().checkPatternFastAt(getWorld(), getPos(),
@@ -433,7 +433,7 @@ protected void formStructure(PatternMatchContext context, String name) {
         // form the main structure
         if ("MAIN".equals(name)) formStructure(context);
 
-        if (getSubstructure(name) != null) getSubstructure(name).setFormed(true);
+        getSubstructure(name).setFormed(true);
     }
 
     public void invalidateStructure() {
@@ -529,7 +529,7 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
                     pattern.setFormed(false);
                 }
             } else {
-                getSubstructure(name).setFormed(false);
+                getSubstructure(name).setFormed(buf.readBoolean());
             }
 
             if (!isStructureFormed()) {
@@ -554,6 +554,8 @@ public boolean isStructureFormed() {
     }
 
     public boolean isStructureFormed(String name) {
+        if (getWorld() == null) return false;
+
         return getSubstructure(name).isFormed();
     }
 
@@ -659,6 +661,8 @@ public List getBuildableShapes(@Nullable Object2IntMap cache = new Long2ObjectOpenHashMap<>();
     protected final Object2IntMap globalCount = new Object2IntOpenHashMap<>();
 
+    /**
+     * New expandable pattern normally you would use {@link FactoryExpandablePattern} instead.
+     * @param boundsFunction A function to supply bounds, order in the way .values() are ordered in RelativeDirection.
+     * @param predicateFunction Given a pos and bounds(the one you just passed in, not mutated), return a predicate. The pos is offset as explained in the builder method.
+     * @param directions The structure directions, explained in the builder method.
+     */
     public ExpandablePattern(@NotNull QuadFunction boundsFunction,
                              @NotNull BiFunction predicateFunction,
                              @NotNull RelativeDirection[] directions) {
@@ -157,6 +163,7 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
 
     @Override
     public PreviewBlockPattern getDefaultShape() {
+        // todo undo
         return null;
     }
 
diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
index a73e33755a5..d2e156263f5 100644
--- a/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
@@ -41,20 +41,38 @@ private FactoryExpandablePattern(RelativeDirection aisleDir, RelativeDirection s
         if (flags != 0x7) throw new IllegalArgumentException("Must have 3 different axes!");
     }
 
+    /**
+     * Starts a new builder using the provided directions.
+     */
     public static FactoryExpandablePattern start(RelativeDirection aisleDir, RelativeDirection stringDir,
                                                  RelativeDirection charDir) {
         return new FactoryExpandablePattern(aisleDir, stringDir, charDir);
     }
 
+    /**
+     * Same as calling {@link FactoryExpandablePattern#start(RelativeDirection, RelativeDirection, RelativeDirection)} with BACK, UP, RIGHT
+     */
     public static FactoryExpandablePattern start() {
         return new FactoryExpandablePattern(RelativeDirection.BACK, RelativeDirection.UP, RelativeDirection.RIGHT);
     }
 
+    /**
+     * This supplies the bounds function. The inputs are: World, controller pos, front facing, up facing. The returned array
+     * is an int array of length 6, with how much to extend the multiblock in each direction. The order of the directions is the same
+     * as the ordinal of the enum.
+     */
     public FactoryExpandablePattern boundsFunction(QuadFunction function) {
         this.boundsFunction = function;
         return this;
     }
 
+    /**
+     * This supplies the predicate from offset pos and the bounds, which is not mutated. The pos is offset so that the controller
+     * is at the origin(0, 0, 0). The 3 axes are positive towards the way structure direction is handled. The pos starts as usual,
+     * which means it will always be in octant 7. It then ends in octant 1, in the opposite corner to the start corner in the cube specified by the bounding box.
+     * The pos travels as expected from the structure direction, traveling first in charDir, then upon going out of bounds once in stringDir and resetting
+     * its charDir pos. Same happens when stringDir goes out of bounds and reset, then aisleDir is incremented.
+     */
     public FactoryExpandablePattern predicateFunction(BiFunction function) {
         this.predicateFunction = function;
         return this;

From 3de4af542e20ee12d0c9d7c1dab504cfe66b586e Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Fri, 19 Jul 2024 23:10:55 -0700
Subject: [PATCH 18/64] the list thing works + hopefully lbb works

---
 .../multiblock/IMultiblockPart.java           |  15 +-
 .../multiblock/MultiblockControllerBase.java  |   9 +-
 .../multi/MetaTileEntityCokeOvenHatch.java    |   5 +-
 .../multi/MetaTileEntityTankValve.java        |   4 +-
 ...etaTileEntityCleaningMaintenanceHatch.java |   2 +-
 .../MetaTileEntityDataAccessHatch.java        |   2 +-
 .../multiblockpart/MetaTileEntityItemBus.java |   4 +-
 ...etaTileEntityMultiblockNotifiablePart.java |   5 +-
 .../MetaTileEntityMultiblockPart.java         | 161 +++++++++++++-----
 .../MetaTileEntityObjectHolder.java           |   4 +-
 .../appeng/MetaTileEntityMEInputBus.java      |   4 +-
 .../appeng/MetaTileEntityMEOutputBus.java     |   2 +-
 .../appeng/MetaTileEntityMEOutputHatch.java   |   2 +-
 .../appeng/MetaTileEntityMEStockingBus.java   |   4 +-
 .../appeng/MetaTileEntityMEStockingHatch.java |   4 +-
 15 files changed, 155 insertions(+), 72 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
index d07e4b3285a..7630d06bc9f 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
@@ -1,24 +1,21 @@
 package gregtech.api.metatileentity.multiblock;
 
+import org.jetbrains.annotations.NotNull;
+
 public interface IMultiblockPart {
 
     boolean isAttachedToMultiBlock();
 
-    void addToMultiBlock(MultiblockControllerBase controllerBase);
-
-    void removeFromMultiBlock(MultiblockControllerBase controllerBase);
+    void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase);
 
-    default MultiblockControllerBase getAttachedMultiblock() {return null;}
+    void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase);
 
     /**
      * Gets how many multiblocks are currently using the part.
      */
-    // todo do a concrete impl
-    default int getWallsharedCount() { return 1;}
+    int getWallshareCount();
 
-    default boolean canPartShare(MultiblockControllerBase target, String substructureName) {
-        return canPartShare();
-    }
+    boolean canPartShare(MultiblockControllerBase target, String substructureName);
 
     default boolean canPartShare() {
         return true;
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 9eb32c868a7..0e2b861cc82 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -206,6 +206,13 @@ protected void setFlipped(boolean flipped, String name) {
     @SideOnly(Side.CLIENT)
     public abstract ICubeRenderer getBaseTexture(IMultiblockPart sourcePart);
 
+    /**
+     * Gets the inactive texture for this part, used for when the multiblock is unformed and you want the part to keep its overlay. Return null to ignore and make hatches go back to their default textures on unform.
+     */
+    public @Nullable ICubeRenderer getInactiveTexture(IMultiblockPart part) {
+        return null;
+    }
+
     public boolean shouldRenderOverlay(IMultiblockPart sourcePart) {
         return true;
     }
@@ -393,7 +400,7 @@ public void checkStructurePattern(String name) {
             ArrayList parts = new ArrayList<>(rawPartsSet);
             for (IMultiblockPart part : parts) {
                 if (part.isAttachedToMultiBlock()) {
-                    if (!part.canPartShare()) {
+                    if (!part.canPartShare(this, name)) {
                         return;
                     }
                 }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOvenHatch.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOvenHatch.java
index acb83429697..56b2d56e9d6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOvenHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOvenHatch.java
@@ -30,6 +30,7 @@
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
@@ -77,14 +78,14 @@ protected void initializeInventory() {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.addToMultiBlock(controllerBase);
         this.fluidInventory = new FluidHandlerProxy(new FluidTankList(false), controllerBase.getExportFluids());
         this.itemInventory = new ItemHandlerProxy(controllerBase.getImportItems(), controllerBase.getExportItems());
     }
 
     @Override
-    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
+    public void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.removeFromMultiBlock(controllerBase);
         this.fluidInventory = new FluidTankList(false);
         this.itemInventory = new GTItemStackHandler(this, 0);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityTankValve.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityTankValve.java
index 13f9c9657f3..0d86937ee58 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityTankValve.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityTankValve.java
@@ -94,14 +94,14 @@ private void initializeDummyInventory() {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.addToMultiBlock(controllerBase);
         this.fluidInventory = controllerBase.getFluidInventory(); // directly use controllers fluid inventory as there
                                                                   // is no reason to proxy it
     }
 
     @Override
-    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
+    public void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.removeFromMultiBlock(controllerBase);
         initializeDummyInventory();
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java
index c7e24cb6649..79807cea717 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java
@@ -50,7 +50,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.addToMultiBlock(controllerBase);
         if (controllerBase instanceof ICleanroomReceiver &&
                 ((ICleanroomReceiver) controllerBase).getCleanroom() == null) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java
index a50acc06ba0..7ced210904c 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java
@@ -218,7 +218,7 @@ public void registerAbilities(List abilityList) {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         rebuildData(controllerBase instanceof MetaTileEntityDataBank);
         super.addToMultiBlock(controllerBase);
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
index 50435231a3b..f44244f2079 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
@@ -97,7 +97,7 @@ public IItemHandlerModifiable getImportItems() {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.addToMultiBlock(controllerBase);
         if (hasGhostCircuitInventory() && this.actualImportItems instanceof ItemHandlerList) {
             for (IItemHandler handler : ((ItemHandlerList) this.actualImportItems).getBackingHandlers()) {
@@ -110,7 +110,7 @@ public void addToMultiBlock(MultiblockControllerBase controllerBase) {
     }
 
     @Override
-    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
+    public void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.removeFromMultiBlock(controllerBase);
         if (hasGhostCircuitInventory() && this.actualImportItems instanceof ItemHandlerList) {
             for (IItemHandler handler : ((ItemHandlerList) this.actualImportItems).getBackingHandlers()) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockNotifiablePart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockNotifiablePart.java
index 1d3d2be909f..f504d895c3b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockNotifiablePart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockNotifiablePart.java
@@ -8,6 +8,7 @@
 
 import net.minecraft.util.ResourceLocation;
 import net.minecraftforge.fluids.IFluidTank;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -68,7 +69,7 @@ private List getPartHandlers() {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.addToMultiBlock(controllerBase);
         List handlerList = getPartHandlers();
         for (INotifiableHandler handler : handlerList) {
@@ -78,7 +79,7 @@ public void addToMultiBlock(MultiblockControllerBase controllerBase) {
     }
 
     @Override
-    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
+    public void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.removeFromMultiBlock(controllerBase);
         List handlerList = getPartHandlers();
         for (INotifiableHandler handler : handlerList) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
index 0a1b098a0b4..1a7a2efed0c 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
@@ -4,6 +4,7 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.util.GTLog;
 import gregtech.api.util.GTUtility;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
@@ -24,6 +25,11 @@
 import codechicken.lib.vec.Matrix4;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.tuple.Pair;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
 
 import static gregtech.api.capability.GregtechDataCodes.SYNC_CONTROLLER;
 
@@ -32,7 +38,15 @@ public abstract class MetaTileEntityMultiblockPart extends MetaTileEntity
 
     private final int tier;
     private BlockPos controllerPos;
-    private MultiblockControllerBase controllerTile;
+    private Class controllerClass;
+    private List<@NotNull MultiblockControllerBase> controllers;
+
+    /**
+     * Client side, used for rendering.
+     */
+    private MultiblockControllerBase lastController = null;
+    private int wallshareCount = 0;
+    protected String attachedSubstructureName;
     protected ICubeRenderer hatchTexture = null;
 
     public MetaTileEntityMultiblockPart(ResourceLocation metaTileEntityId, int tier) {
@@ -49,6 +63,7 @@ public Pair getParticleTexture() {
 
     @Override
     public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation, IVertexOperation[] pipeline) {
+        if (controllerPos != null) GTLog.logger.info("pos: " + controllerPos);
         ICubeRenderer baseTexture = getBaseTexture();
         pipeline = ArrayUtils.add(pipeline,
                 new ColourMultiplier(GTUtility.convertRGBtoOpaqueRGBA_CL(getPaintingColorForRendering())));
@@ -64,35 +79,45 @@ public int getTier() {
         return tier;
     }
 
-    public MultiblockControllerBase getController() {
-        if (getWorld() != null && getWorld().isRemote) { // check this only clientside
-            if (controllerTile == null && controllerPos != null) {
-                this.controllerTile = (MultiblockControllerBase) GTUtility.getMetaTileEntity(getWorld(), controllerPos);
+    public @Nullable MultiblockControllerBase getController() {
+        tryInitControllers();
+
+        if (getWorld() == null) {
+            this.controllers.clear();
+            lastController = null;
+            return null;
+        }
+
+        if (getWorld().isRemote) { // client check, on client controllers is always empty
+            if (lastController == null) {
+                if (controllerPos != null) {
+                    this.lastController = (MultiblockControllerBase) GTUtility.getMetaTileEntity(getWorld(), controllerPos);
+                }
+            } else if (!lastController.isValid()) {
+                this.lastController = null;
             }
+
+            return lastController;
         }
-        if (controllerTile != null && (controllerTile.getHolder() == null ||
-                !controllerTile.isValid() ||
-                !(getWorld().isRemote || controllerTile.getMultiblockParts().contains(this)))) {
-            // tile can become invalid for many reasons, and can also forgot to remove us once we aren't in structure
-            // anymore
-            // so check it here to prevent bugs with dangling controller reference and wrong texture
-            this.controllerTile = null;
+
+        if (controllers.isEmpty()) return null; // server check, remove controller if it is no longer valid
+
+        MultiblockControllerBase controller = controllers.get(controllers.size() - 1);
+
+        if (!controller.isValid()) {
+            removeController(controller);
         }
-        return controllerTile;
+
+        return controller;
     }
 
     public ICubeRenderer getBaseTexture() {
         MultiblockControllerBase controller = getController();
         if (controller != null) {
-            return this.hatchTexture = controller.getBaseTexture(this);
-        } else if (this.hatchTexture != null) {
-            if (hatchTexture != Textures.getInactiveTexture(hatchTexture)) {
-                return this.hatchTexture = Textures.getInactiveTexture(hatchTexture);
-            }
-            return this.hatchTexture;
-        } else {
-            return Textures.VOLTAGE_CASINGS[tier];
-        }
+            this.hatchTexture = controller.getInactiveTexture(this);
+            return controller.getBaseTexture(this);
+        } else if (hatchTexture != null) return hatchTexture;
+        return Textures.VOLTAGE_CASINGS[tier];
     }
 
     public boolean shouldRenderOverlay() {
@@ -120,7 +145,7 @@ public void receiveInitialSyncData(PacketBuffer buf) {
         super.receiveInitialSyncData(buf);
         if (buf.readBoolean()) {
             this.controllerPos = buf.readBlockPos();
-            this.controllerTile = null;
+            this.lastController = null;
         }
     }
 
@@ -128,27 +153,59 @@ public void receiveInitialSyncData(PacketBuffer buf) {
     public void receiveCustomData(int dataId, PacketBuffer buf) {
         super.receiveCustomData(dataId, buf);
         if (dataId == SYNC_CONTROLLER) {
-            if (buf.readBoolean()) {
-                this.controllerPos = buf.readBlockPos();
-                this.controllerTile = null;
+            long data = buf.readLong();
+            if (data != Long.MAX_VALUE) {
+                this.controllerPos = BlockPos.fromLong(data);
             } else {
-                this.controllerPos = null;
-                this.controllerTile = null;
+                controllerPos = null;
+                GTLog.logger.info("hi");
             }
+            this.lastController = null;
             scheduleRenderUpdate();
         }
     }
 
-    private void setController(MultiblockControllerBase controller1) {
-        this.controllerTile = controller1;
-        if (!getWorld().isRemote) {
-            writeCustomData(SYNC_CONTROLLER, writer -> {
-                writer.writeBoolean(controllerTile != null);
-                if (controllerTile != null) {
-                    writer.writeBlockPos(controllerTile.getPos());
-                }
-            });
+    private void addController(@NotNull MultiblockControllerBase controller) {
+        tryInitControllers();
+
+        // this should be called after canPartShare has already checked the class, just a safeguard
+        if (controllerClass != null && controller.getClass() != controllerClass) {
+            GTLog.logger.error("addController(MultiblockControllerBase) was called on " + getClass().getName() + " with a mismatched name(original: " + controllerClass.getName() + " new: " + controller.getClass().getName() +")! Ignoring the call.");
+            return;
         }
+
+        if (controllers.isEmpty()) controllerClass = controller.getClass();
+
+        this.controllers.add(controller);
+
+        syncLastController();
+    }
+
+    private void removeController(@NotNull MultiblockControllerBase controller) {
+        tryInitControllers();
+
+        if (!controllers.remove(controller)) {
+            GTLog.logger.error("removeController(MultiblockControllerBase) was called on " + getClass().getName() + " while the given controller wasn't in the list!");
+        }
+
+        if (controllers.isEmpty()) {
+            controllerClass = null;
+        }
+
+        syncLastController();
+    }
+
+    private void syncLastController() {
+        if (getWorld().isRemote) return;
+
+        if (controllers.isEmpty()) {
+            writeCustomData(SYNC_CONTROLLER, buf -> buf.writeLong(Long.MAX_VALUE));
+            return;
+        }
+
+        MultiblockControllerBase controller = controllers.get(controllers.size() - 1);
+
+        writeCustomData(SYNC_CONTROLLER, buf -> buf.writeBlockPos(controller.getPos()));
     }
 
     @Override
@@ -161,20 +218,40 @@ public void onRemoval() {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
-        setController(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
+        addController(controllerBase);
         scheduleRenderUpdate();
+        wallshareCount++;
     }
 
     @Override
-    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
-        setController(null);
+    public void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
+        removeController(controllerBase);
         scheduleRenderUpdate();
+        wallshareCount--;
+    }
+
+    private void tryInitControllers() {
+        // can't just init the variable in ctor because we need this init before super ctor finishes
+        if (controllers == null) controllers = new ArrayList<>(1);
+    }
+
+    @Override
+    public boolean canPartShare(MultiblockControllerBase target, String substructureName) {
+        // when this is called normally isAttachedToMultiBlock has already been called and returned true
+        // so we know controllerClass is notnull
+
+        return canPartShare() && target.getClass() == controllerClass && substructureName.equals(attachedSubstructureName);
+    }
+
+    @Override
+    public int getWallshareCount() {
+        return wallshareCount;
     }
 
     @Override
     public boolean isAttachedToMultiBlock() {
-        return getController() != null;
+        return controllers != null && !controllers.isEmpty();
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
index c55fcbfeaa8..336a2d4b9d8 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
@@ -197,14 +197,14 @@ protected boolean shouldSerializeInventories() {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.addToMultiBlock(controllerBase);
         heldItems.addNotifiableMetaTileEntity(controllerBase);
         heldItems.addToNotifiedList(this, heldItems, false);
     }
 
     @Override
-    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
+    public void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.removeFromMultiBlock(controllerBase);
         heldItems.removeNotifiableMetaTileEntity(controllerBase);
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java
index b1e53f22f4a..cdf205cb170 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java
@@ -161,7 +161,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity iGregTechTileEnti
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.addToMultiBlock(controllerBase);
         for (IItemHandler handler : this.actualImportItems.getBackingHandlers()) {
             if (handler instanceof INotifiableHandler notifiable) {
@@ -172,7 +172,7 @@ public void addToMultiBlock(MultiblockControllerBase controllerBase) {
     }
 
     @Override
-    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
+    public void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.removeFromMultiBlock(controllerBase);
         for (IItemHandler handler : this.actualImportItems.getBackingHandlers()) {
             if (handler instanceof INotifiableHandler notifiable) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java
index 9e008b857e3..126e835bbe9 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java
@@ -201,7 +201,7 @@ public void registerAbilities(List abilityList) {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.addToMultiBlock(controllerBase);
         if (controllerBase instanceof MultiblockWithDisplayBase) {
             ((MultiblockWithDisplayBase) controllerBase).enableItemInfSink();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java
index 26a8fcc7947..7bd88726ac8 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java
@@ -203,7 +203,7 @@ public void registerAbilities(List list) {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.addToMultiBlock(controllerBase);
         if (controllerBase instanceof MultiblockWithDisplayBase) {
             ((MultiblockWithDisplayBase) controllerBase).enableFluidInfSink();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingBus.java
index 640f7869617..fc840e0d57d 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingBus.java
@@ -101,7 +101,7 @@ protected void flushInventory() {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.addToMultiBlock(controllerBase);
         // ensure that no other stocking bus on this multiblock is configured to hold the same item.
         // that we have in our own bus.
@@ -111,7 +111,7 @@ public void addToMultiBlock(MultiblockControllerBase controllerBase) {
     }
 
     @Override
-    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
+    public void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         // block auto-pull from working when not in a formed multiblock
         this.autoPullTest = $ -> false;
         if (this.autoPull) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingHatch.java
index 30e28eff54b..b69ae0ba8ef 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingHatch.java
@@ -99,14 +99,14 @@ protected void flushInventory() {
     }
 
     @Override
-    public void addToMultiBlock(MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         super.addToMultiBlock(controllerBase);
         this.autoPullTest = stack -> !this.testConfiguredInOtherHatch(stack);
         validateConfig();
     }
 
     @Override
-    public void removeFromMultiBlock(MultiblockControllerBase controllerBase) {
+    public void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         this.autoPullTest = $ -> false;
         if (this.autoPull) {
             this.getAEFluidHandler().clearConfig();

From 96d472c8c942aacc3f04b23f290edaad08da7559 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Fri, 19 Jul 2024 23:17:17 -0700
Subject: [PATCH 19/64] hopefully it works fr(still didn't test)

---
 .../metatileentities/multi/MetaTileEntityLargeBoiler.java    | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
index 55c5ad1cd93..d3db5e893d5 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
@@ -247,6 +247,11 @@ public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
         return boilerType.casingRenderer;
     }
 
+    @Override
+    public ICubeRenderer getInactiveTexture(IMultiblockPart part) {
+        return isFireboxPart(part) ? boilerType.casingRenderer : boilerType.fireboxIdleRenderer;
+    }
+
     @Override
     public boolean hasMufflerMechanics() {
         return true;

From f0bd6dd2653c0ff8a8117ea376dbc93536b9ec94 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sat, 20 Jul 2024 13:57:35 -0700
Subject: [PATCH 20/64] :reading:

---
 .../client/renderer/texture/Textures.java         |  9 ---------
 .../multi/MetaTileEntityLargeBoiler.java          |  2 +-
 .../MetaTileEntityCleaningMaintenanceHatch.java   | 15 ---------------
 .../MetaTileEntityMultiblockPart.java             |  5 ++---
 4 files changed, 3 insertions(+), 28 deletions(-)

diff --git a/src/main/java/gregtech/client/renderer/texture/Textures.java b/src/main/java/gregtech/client/renderer/texture/Textures.java
index 76f91891093..97bf92f9aec 100644
--- a/src/main/java/gregtech/client/renderer/texture/Textures.java
+++ b/src/main/java/gregtech/client/renderer/texture/Textures.java
@@ -731,13 +731,4 @@ public static void renderFace(CCRenderState renderState, Matrix4 translation, IV
                 ArrayUtils.addAll(ops, new TransformationList(translation), uvList));
         renderState.render();
     }
-
-    // TODO Could maybe be cleaned up?
-    public static ICubeRenderer getInactiveTexture(ICubeRenderer renderer) {
-        if (renderer == BRONZE_FIREBOX_ACTIVE) return BRONZE_FIREBOX;
-        if (renderer == STEEL_FIREBOX_ACTIVE) return STEEL_FIREBOX;
-        if (renderer == TITANIUM_FIREBOX_ACTIVE) return TITANIUM_FIREBOX;
-        if (renderer == TUNGSTENSTEEL_FIREBOX_ACTIVE) return TUNGSTENSTEEL_FIREBOX;
-        return renderer;
-    }
 }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
index d3db5e893d5..c046a525a5d 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
@@ -249,7 +249,7 @@ public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
 
     @Override
     public ICubeRenderer getInactiveTexture(IMultiblockPart part) {
-        return isFireboxPart(part) ? boilerType.casingRenderer : boilerType.fireboxIdleRenderer;
+        return isFireboxPart(part) ? boilerType.fireboxIdleRenderer : boilerType.casingRenderer;
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java
index 79807cea717..70886d69950 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java
@@ -63,21 +63,6 @@ public int getTier() {
         return GTValues.UV;
     }
 
-    @Override
-    public ICubeRenderer getBaseTexture() {
-        MultiblockControllerBase controller = getController();
-        if (controller != null) {
-            return this.hatchTexture = controller.getBaseTexture(this);
-        } else if (this.hatchTexture != null) {
-            if (hatchTexture != Textures.getInactiveTexture(hatchTexture)) {
-                return this.hatchTexture = Textures.getInactiveTexture(hatchTexture);
-            }
-            return this.hatchTexture;
-        } else {
-            return Textures.VOLTAGE_CASINGS[getTier()];
-        }
-    }
-
     @Override
     public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation, IVertexOperation[] pipeline) {
         getBaseTexture().render(renderState, translation, ArrayUtils.add(pipeline,
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
index 1a7a2efed0c..58418f28c88 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
@@ -63,7 +63,6 @@ public Pair getParticleTexture() {
 
     @Override
     public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation, IVertexOperation[] pipeline) {
-        if (controllerPos != null) GTLog.logger.info("pos: " + controllerPos);
         ICubeRenderer baseTexture = getBaseTexture();
         pipeline = ArrayUtils.add(pipeline,
                 new ColourMultiplier(GTUtility.convertRGBtoOpaqueRGBA_CL(getPaintingColorForRendering())));
@@ -79,7 +78,8 @@ public int getTier() {
         return tier;
     }
 
-    public @Nullable MultiblockControllerBase getController() {
+    @Nullable
+    public MultiblockControllerBase getController() {
         tryInitControllers();
 
         if (getWorld() == null) {
@@ -158,7 +158,6 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
                 this.controllerPos = BlockPos.fromLong(data);
             } else {
                 controllerPos = null;
-                GTLog.logger.info("hi");
             }
             this.lastController = null;
             scheduleRenderUpdate();

From 6c82b0ae23ea7979e98805d1cd4bfcedcdce5272 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Fri, 2 Aug 2024 21:04:12 -0700
Subject: [PATCH 21/64] smallest arch commit

---
 .../api/capability/IDistillationTower.java    |   4 +-
 .../multiblock/IMultiblockPart.java           |  15 +-
 .../multiblock/MultiblockControllerBase.java  | 286 +++++++++++------
 .../multiblock/MultiblockWithDisplayBase.java |  11 +-
 .../RecipeMapMultiblockController.java        |   9 +-
 ...ecipeMapPrimitiveMultiblockController.java |   4 +-
 .../RecipeMapSteamMultiblockController.java   |   9 +-
 .../gregtech/api/pattern/BlockWorldState.java |  45 +--
 .../api/pattern/PatternMatchContext.java      |  77 -----
 .../gregtech/api/pattern/StructureInfo.java   |  28 --
 .../api/pattern/TraceabilityPredicate.java    |  46 +--
 .../api/pattern/pattern/BlockPattern.java     | 297 +++---------------
 .../pattern/pattern/ExpandablePattern.java    |  88 +++---
 .../pattern/FactoryExpandablePattern.java     |  21 +-
 .../api/pattern/pattern/IBlockPattern.java    |  32 +-
 .../api/pattern/pattern/PatternInfo.java      |  41 ---
 .../api/pattern/pattern/PatternState.java     | 109 +++++++
 .../java/gregtech/common/EventHandlers.java   |   2 +-
 .../multi/MetaTileEntityCokeOvenHatch.java    |   4 +-
 .../multi/MetaTileEntityLargeBoiler.java      |   9 +-
 .../MetaTileEntityPrimitiveWaterPump.java     |   9 +-
 .../multi/MetaTileEntityTankValve.java        |   4 +-
 .../MetaTileEntityActiveTransformer.java      |  11 +-
 .../electric/MetaTileEntityCleanroom.java     |  53 ++--
 .../electric/MetaTileEntityCrackingUnit.java  |  18 +-
 .../electric/MetaTileEntityDataBank.java      |   9 +-
 .../MetaTileEntityDistillationTower.java      |  11 +-
 .../MetaTileEntityElectricBlastFurnace.java   |  18 +-
 .../electric/MetaTileEntityFluidDrill.java    |   9 +-
 .../electric/MetaTileEntityFusionReactor.java |   9 +-
 .../multi/electric/MetaTileEntityHPCA.java    |   9 +-
 .../electric/MetaTileEntityLargeMiner.java    |   9 +-
 .../electric/MetaTileEntityMultiSmelter.java  |  22 +-
 .../electric/MetaTileEntityNetworkSwitch.java |   9 +-
 .../MetaTileEntityPowerSubstation.java        |  49 ++-
 .../MetaTileEntityProcessingArray.java        |   4 +-
 .../electric/MetaTileEntityPyrolyseOven.java  |  21 +-
 .../MetaTileEntityResearchStation.java        |  13 +-
 .../electric/MetaTileEntityVacuumFreezer.java |  15 +-
 .../MetaTileEntityCentralMonitor.java         |   7 +-
 .../MetaTileEntityLargeCombustionEngine.java  |   1 -
 .../generator/MetaTileEntityLargeTurbine.java |   9 +-
 ...etaTileEntityCleaningMaintenanceHatch.java |   5 +-
 .../MetaTileEntityDataAccessHatch.java        |   4 +-
 .../multiblockpart/MetaTileEntityItemBus.java |   4 +-
 ...etaTileEntityMultiblockNotifiablePart.java |   5 +-
 .../MetaTileEntityMultiblockPart.java         | 107 ++++++-
 .../MetaTileEntityObjectHolder.java           |   4 +-
 .../appeng/MetaTileEntityMEInputBus.java      |   4 +-
 .../appeng/MetaTileEntityMEOutputBus.java     |   4 +-
 .../appeng/MetaTileEntityMEOutputHatch.java   |   4 +-
 .../appeng/MetaTileEntityMEStockingBus.java   |   4 +-
 .../appeng/MetaTileEntityMEStockingHatch.java |   4 +-
 .../MetaTileEntityCharcoalPileIgniter.java    |  10 +-
 .../MultiblockInfoRecipeWrapper.java          |  45 ++-
 55 files changed, 782 insertions(+), 878 deletions(-)
 delete mode 100644 src/main/java/gregtech/api/pattern/PatternMatchContext.java
 delete mode 100644 src/main/java/gregtech/api/pattern/StructureInfo.java
 delete mode 100644 src/main/java/gregtech/api/pattern/pattern/PatternInfo.java
 create mode 100644 src/main/java/gregtech/api/pattern/pattern/PatternState.java

diff --git a/src/main/java/gregtech/api/capability/IDistillationTower.java b/src/main/java/gregtech/api/capability/IDistillationTower.java
index e7bf447518b..54ac45df57c 100644
--- a/src/main/java/gregtech/api/capability/IDistillationTower.java
+++ b/src/main/java/gregtech/api/capability/IDistillationTower.java
@@ -4,7 +4,7 @@
 
 import net.minecraft.util.math.BlockPos;
 
-import java.util.List;
+import java.util.NavigableSet;
 
 /**
  * intended for use in conjunction with {@link gregtech.api.capability.impl.DistillationTowerLogicHandler}
@@ -12,7 +12,7 @@
  */
 public interface IDistillationTower {
 
-    List getMultiblockParts();
+    NavigableSet getMultiblockParts();
 
     BlockPos getPos();
 
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
index 7630d06bc9f..02283b0969b 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
@@ -6,7 +6,15 @@ public interface IMultiblockPart {
 
     boolean isAttachedToMultiBlock();
 
-    void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase);
+    /**
+     * Use {@link IMultiblockPart#addToMultiBlock(MultiblockControllerBase, String)} insead!
+     */
+    @Deprecated
+    default void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
+        addToMultiBlock(controllerBase, "MAIN");
+    }
+
+    void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String substructureName);
 
     void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase);
 
@@ -15,6 +23,11 @@ public interface IMultiblockPart {
      */
     int getWallshareCount();
 
+    /**
+     * Gets the name of the substructure the part is attached to.
+     */
+    String getSubstructureName();
+
     boolean canPartShare(MultiblockControllerBase target, String substructureName);
 
     default boolean canPartShare() {
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 0e2b861cc82..9436f5ea51d 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -9,11 +9,11 @@
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
+import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.MultiblockShapeInfo;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.IBlockPattern;
-import gregtech.api.pattern.pattern.PatternInfo;
+import gregtech.api.pattern.pattern.PatternState;
 import gregtech.api.pattern.pattern.PreviewBlockPattern;
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.unification.material.Material;
@@ -53,6 +53,7 @@
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
 import codechicken.lib.vec.Rotation;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
@@ -67,14 +68,14 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.NavigableSet;
 import java.util.Objects;
-import java.util.Set;
+import java.util.TreeSet;
 import java.util.function.BiFunction;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
@@ -88,10 +89,16 @@ public abstract class MultiblockControllerBase extends MetaTileEntity implements
     protected PreviewBlockPattern defaultPattern;
 
     private final Map, List> multiblockAbilities = new HashMap<>();
-    private final List multiblockParts = new ArrayList<>();
+
+    // treeset here to get logn time for contains, and for automatically sorting itself
+    // prioritize the manually specified sorter first, defaulting to the hashcode for tiebreakers
+    private final NavigableSet multiblockParts = new TreeSet<>(Comparator.comparingLong(part -> {
+        MetaTileEntity mte = (MetaTileEntity) part;
+        return ((long) multiblockPartSorter().apply(mte.getPos()) << 32) | mte.getPos().hashCode();
+    }));
 
     protected EnumFacing upwardsFacing = EnumFacing.NORTH;
-    protected final Object2ObjectMap structures = new Object2ObjectOpenHashMap<>();
+    protected final Object2ObjectMap structures = new Object2ObjectOpenHashMap<>();
 
     public MultiblockControllerBase(ResourceLocation metaTileEntityId) {
         super(metaTileEntityId);
@@ -139,16 +146,16 @@ public void update() {
      * frequent new BlockPatterns from being made.
      */
     protected void createStructurePatterns() {
-        structures.put("MAIN", new PatternInfo(createStructurePattern()));
+        structures.put("MAIN", createStructurePattern());
     }
 
     private void validateStructurePatterns() {
         List failures = new ArrayList<>();
 
-        for (Object2ObjectMap.Entry pattern : structures.object2ObjectEntrySet()) {
+        for (Object2ObjectMap.Entry pattern : structures.object2ObjectEntrySet()) {
             if ("MAIN".equals(pattern.getKey())) continue;
 
-            if (pattern.getValue().getPattern().legacyBuilderError()) {
+            if (pattern.getValue().legacyBuilderError()) {
                 failures.add(pattern.getKey());
             }
         }
@@ -175,8 +182,8 @@ public void setUpwardsFacing(EnumFacing upwardsFacing) {
                 notifyBlockUpdate();
                 markDirty();
                 writeCustomData(UPDATE_UPWARDS_FACING, buf -> buf.writeByte(upwardsFacing.getIndex()));
-                for (PatternInfo pattern : structures.values()) {
-                    pattern.getPattern().clearCache();
+                for (IBlockPattern pattern : structures.values()) {
+                    pattern.clearCache();
                 }
                 checkStructurePatterns();
             }
@@ -184,19 +191,17 @@ public void setUpwardsFacing(EnumFacing upwardsFacing) {
     }
 
     public boolean isFlipped() {
-        return getSubstructure("MAIN").isFlipped();
+        return getSubstructure("MAIN").getPatternState().isFlipped();
     }
 
     /** Should not be called outside of structure formation logic! */
     @ApiStatus.Internal
     protected void setFlipped(boolean flipped, String name) {
-        PatternInfo structure = getSubstructure(name);
-
-        if (structure == null) return;
+        PatternState structure = getSubstructure(name).getPatternState();
 
-        boolean flip = structure.isFlipped();
+        boolean flip = structure.isActualFlipped();
         if (flip != flipped) {
-            structure.setFlipped(flipped);
+            structure.setActualFlipped(flipped);
             notifyBlockUpdate();
             markDirty();
             writeCustomData(UPDATE_FLIP, buf -> buf.writeString(name).writeBoolean(flipped));
@@ -207,7 +212,8 @@ protected void setFlipped(boolean flipped, String name) {
     public abstract ICubeRenderer getBaseTexture(IMultiblockPart sourcePart);
 
     /**
-     * Gets the inactive texture for this part, used for when the multiblock is unformed and you want the part to keep its overlay. Return null to ignore and make hatches go back to their default textures on unform.
+     * Gets the inactive texture for this part, used for when the multiblock is unformed and you want the part to keep
+     * its overlay. Return null to ignore and make hatches go back to their default textures on unform.
      */
     public @Nullable ICubeRenderer getInactiveTexture(IMultiblockPart part) {
         return null;
@@ -235,20 +241,12 @@ public TextureAtlasSprite getFrontDefaultTexture() {
 
     public static TraceabilityPredicate tilePredicate(@NotNull BiFunction predicate,
                                                       @Nullable Supplier candidates) {
-        return new TraceabilityPredicate((blockWorldState, info) -> {
-            TileEntity tileEntity = blockWorldState.getTileEntity();
+        return new TraceabilityPredicate((worldState, patternState) -> {
+            TileEntity tileEntity = worldState.getTileEntity();
             if (!(tileEntity instanceof IGregTechTileEntity))
                 return false;
             MetaTileEntity metaTileEntity = ((IGregTechTileEntity) tileEntity).getMetaTileEntity();
-            if (predicate.apply(blockWorldState, metaTileEntity)) {
-                if (metaTileEntity instanceof IMultiblockPart) {
-                    Set partsFound = info.getContext().getOrCreate("MultiblockParts",
-                            HashSet::new);
-                    partsFound.add((IMultiblockPart) metaTileEntity);
-                }
-                return true;
-            }
-            return false;
+            return predicate.apply(worldState, metaTileEntity);
         }, candidates);
     }
 
@@ -282,13 +280,9 @@ public static TraceabilityPredicate abilities(MultiblockAbility... allowedAbi
     }
 
     public static TraceabilityPredicate states(IBlockState... allowedStates) {
-        return new TraceabilityPredicate((blockWorldState, info) -> {
-            IBlockState state = blockWorldState.getBlockState();
-            if (state.getBlock() instanceof VariantActiveBlock) {
-                info.getContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos());
-            }
-            return ArrayUtils.contains(allowedStates, state);
-        }, getCandidates(allowedStates));
+        return new TraceabilityPredicate(
+                (worldState, patternState) -> ArrayUtils.contains(allowedStates, worldState.getBlockState()),
+                getCandidates(allowedStates));
     }
 
     /**
@@ -308,7 +302,7 @@ public static TraceabilityPredicate frames(Material... frameMaterials) {
 
     public static TraceabilityPredicate blocks(Block... block) {
         return new TraceabilityPredicate(
-                (blockWorldState, info) -> ArrayUtils.contains(block, blockWorldState.getBlockState().getBlock()),
+                (worldState, patternState) -> ArrayUtils.contains(block, worldState.getBlockState().getBlock()),
                 getCandidates(Arrays.stream(block).map(Block::getDefaultState).toArray(IBlockState[]::new)));
     }
 
@@ -324,6 +318,29 @@ public static TraceabilityPredicate heatingCoils() {
         return TraceabilityPredicate.HEATING_COILS.get();
     }
 
+    /**
+     * Ensures that all the blockstates that are in the map are the same type. Returns the type if all match, or null if
+     * they don't.
+     * Example: {@code allSameType(GregTechAPI.HEATING_COILS, getSubstructure("MAIN").getCache())}
+     * 
+     * @param info  The info, such as GregTechAPI.HEATING_COILS
+     * @param cache The cache for the pattern.
+     */
+    public static  V allSameType(Object2ObjectMap info, Long2ObjectMap cache) {
+        V type = null;
+        for (BlockInfo blockInfo : cache.values()) {
+            V state = info.get(blockInfo.getBlockState());
+            if (state != null) {
+                if (type != state) {
+                    if (type == null) type = state;
+                    else return null;
+                }
+            }
+        }
+
+        return type;
+    }
+
     public TraceabilityPredicate selfPredicate() {
         return metaTileEntities(this).setCenter();
     }
@@ -386,90 +403,160 @@ public void checkStructurePattern() {
     }
 
     public void checkStructurePattern(String name) {
-        PatternInfo pattern = getSubstructure(name);
-        if (!pattern.shouldUpdate) return;
+        IBlockPattern pattern = getSubstructure(name);
+        if (!pattern.getPatternState().shouldUpdate()) return;
 
         long time = System.nanoTime();
-        PatternMatchContext context = pattern.getPattern().checkPatternFastAt(getWorld(), getPos(),
+        PatternState result = pattern.checkPatternFastAt(getWorld(), getPos(),
                 getFrontFacing(), getUpwardsFacing(), allowsFlip());
         System.out.println(
                 "structure check for " + getClass().getSimpleName() + " took " + (System.nanoTime() - time) + " nanos");
 
-        if (context != null && !pattern.isFormed()) {
-            Set rawPartsSet = context.getOrCreate("MultiblockParts", HashSet::new);
-            ArrayList parts = new ArrayList<>(rawPartsSet);
-            for (IMultiblockPart part : parts) {
-                if (part.isAttachedToMultiBlock()) {
-                    if (!part.canPartShare(this, name)) {
-                        return;
-                    }
+        if (result.getState().isValid()) { // structure check succeeds
+            // if structure isn't formed or cache fails
+            if (result.isFormed()) {
+                // fast rebuild parts
+                if (result.getState() == PatternState.EnumCheckState.VALID_UNCACHED) {
+                    // add any new parts, because removal of parts is impossible
+                    // it is possible for old parts to persist, so check that
+                    forEachMultiblockPart(name, part -> {
+                        if (part.isAttachedToMultiBlock() && !part.canPartShare(this, name)) {
+                            invalidateStructure(name);
+                            return false;
+                        } ;
+                        if (multiblockParts.add(part)) {
+                            part.addToMultiBlock(this, name);
+                        }
+                        if (part instanceof IMultiblockAbilityPartabilityPart) {
+                            // noinspection unchecked
+                            registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
+                        }
+                        return true;
+                    });
+                    formStructure(name);
                 }
+                return;
             }
-            this.setFlipped(context.neededFlip(), name);
-
-            parts.sort(Comparator.comparing(it -> multiblockPartSorter().apply(((MetaTileEntity) it).getPos())));
-            Map, List> abilities = new HashMap<>();
-            for (IMultiblockPart multiblockPart : parts) {
-                if (multiblockPart instanceof IMultiblockAbilityPart) {
-                    @SuppressWarnings("unchecked")
-                    IMultiblockAbilityPart abilityPart = (IMultiblockAbilityPart) multiblockPart;
-                    List abilityInstancesList = abilities.computeIfAbsent(abilityPart.getAbility(),
-                            k -> new ArrayList<>());
-                    abilityPart.registerAbilities(abilityInstancesList);
+
+            // normal rebuild parts
+            forEachMultiblockPart(name, part -> {
+                // structure is already invalidated at this point so don't bother
+                if (part.isAttachedToMultiBlock() && !part.canPartShare(this, name)) {
+                    return false;
+                }
+                // parts *should* not have this controller added
+                multiblockParts.add(part);
+                if (part instanceof IMultiblockAbilityPartabilityPart) {
+                    // noinspection unchecked
+                    registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
                 }
+                return true;
+            });
+            formStructure(name);
+        } else { // structure check fails
+            if (result.isFormed()) { // invalidate if not already
+                invalidateStructure(name);
             }
+        }
+    }
 
-            this.multiblockParts.addAll(parts);
-            this.multiblockAbilities.putAll(abilities);
-            parts.forEach(part -> part.addToMultiBlock(this));
-            writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString(name).writeBoolean(true));
-            formStructure(context, name);
-        } else if (context == null && pattern.isFormed()) {
-            invalidateStructure(name);
-        } else if (context != null) {
-            // ensure flip is ok, possibly not necessary but good to check just in case
-            if (context.neededFlip() != isFlipped()) {
-                setFlipped(context.neededFlip(), name);
+    /**
+     * Perform an action for each multiblock part in the substructure. This uses the pattern's cache, which is always
+     * accurate if the structure is valid(and has undefined behavior(probably empty) if not). Using the cache means
+     * you can clear the multi's multiblock parts during this without causing a CME(which would happen if this iteratoes
+     * over multiblockParts instead)
+     * 
+     * @param name   The name of the substructure.
+     * @param action The action to perform. Return true if the iteration should keep going, or false if it should stop.
+     *               This is for stuff like non-wallshareable hatches which instantly invalidate a multiblock.
+     */
+    protected void forEachMultiblockPart(String name, Predicate action) {
+        Long2ObjectMap cache = getSubstructure(name).getCache();
+        for (BlockInfo info : cache.values()) {
+            TileEntity te = info.getTileEntity();
+            if (!(te instanceof IGregTechTileEntity gtte)) return;
+            MetaTileEntity mte = gtte.getMetaTileEntity();
+            if (mte instanceof IMultiblockPart part) {
+                if (!action.test(part)) return;
+            }
+        }
+    }
+
+    protected List getVABlocks(Long2ObjectMap cache) {
+        List pos = new ArrayList<>();
+        for (Long2ObjectMap.Entry entry : cache.long2ObjectEntrySet()) {
+            if (entry.getValue().getBlockState().getBlock() instanceof VariantActiveBlock) {
+                pos.add(BlockPos.fromLong(entry.getLongKey()));
             }
         }
+        return pos;
     }
 
-    protected void formStructure(PatternMatchContext context) {}
+    protected void registerMultiblockAbility(IMultiblockAbilityPart part) {
+        List abilityList = multiblockAbilities.computeIfAbsent(part.getAbility(), k -> new ArrayList<>());
+        part.registerAbilities(abilityList);
+    }
+
+    protected void forEachFormed(GreggyBlockPos pos) {
+        IBlockState state = getWorld().getBlockState(pos.immutable());
+    }
 
-    protected void formStructure(PatternMatchContext context, String name) {
+    protected void formStructure(String name) {
         // form the main structure
-        if ("MAIN".equals(name)) formStructure(context);
+        if ("MAIN".equals(name)) formStructure();
 
-        getSubstructure(name).setFormed(true);
+        setFlipped(getSubstructure(name).getPatternState().isFlipped(), name);
+        writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString(name).writeBoolean(true));
     }
 
+    /**
+     * Use {@link MultiblockControllerBase#formStructure(String)} instead!
+     */
+    @Deprecated
+    protected void formStructure() {
+        formStructure("MAIN");
+    }
+
+    /**
+     * Use {@link MultiblockControllerBase#invalidateStructure(String)} instead!
+     */
+    @Deprecated
     public void invalidateStructure() {
-        this.multiblockParts.forEach(part -> part.removeFromMultiBlock(this));
-        this.multiblockAbilities.clear();
-        this.multiblockParts.clear();
-        structures.forEach((s, p) -> {
-            p.setFormed(false);
-            p.setFlipped(false);
-        });
-        writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString("null"));
+        invalidateStructure("MAIN");
     }
 
     public void invalidateStructure(String name) {
         // invalidate the main structure
-        if ("MAIN".equals(name)) invalidateStructure();
-        else {
-            getSubstructure(name).setFormed(false);
+        if ("MAIN".equals(name)) {
+            this.multiblockParts.forEach(part -> part.removeFromMultiBlock(this));
+            this.multiblockAbilities.clear();
+            this.multiblockParts.clear();
+            structures.forEach((s, p) -> {
+                p.getPatternState().setFormed(false);
+                p.getPatternState().setFlipped(false);
+            });
+            writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString("null"));
+        } else {
+            getSubstructure(name).getPatternState().setFormed(false);
             writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString(name).writeBoolean(false));
+
+            multiblockParts.removeIf(part -> {
+                if (name.equals(part.getSubstructureName())) {
+                    part.removeFromMultiBlock(this);
+                    return true;
+                }
+                return false;
+            });
         }
     }
 
     protected void invalidStructureCaches() {
-        for (PatternInfo pattern : structures.values()) {
-            pattern.getPattern().clearCache();
+        for (IBlockPattern pattern : structures.values()) {
+            pattern.clearCache();
         }
     }
 
-    protected PatternInfo getSubstructure(String name) {
+    protected IBlockPattern getSubstructure(String name) {
         return structures.get(name);
     }
 
@@ -477,7 +564,7 @@ protected PatternInfo getSubstructure(String name) {
     public void onRemoval() {
         super.onRemoval();
         if (!getWorld().isRemote) {
-            invalidateStructure();
+            invalidateStructure("MAIN");
         }
     }
 
@@ -487,8 +574,9 @@ public  List getAbilities(MultiblockAbility ability) {
         return Collections.unmodifiableList(rawList);
     }
 
-    public List getMultiblockParts() {
-        return Collections.unmodifiableList(multiblockParts);
+    // todo fix/update usages of this
+    public NavigableSet getMultiblockParts() {
+        return Collections.unmodifiableNavigableSet(multiblockParts);
     }
 
     @Override
@@ -532,11 +620,11 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
             // it forces me so uh yay
             String name = buf.readString(65536);
             if ("null".equals(name)) {
-                for (PatternInfo pattern : structures.values()) {
-                    pattern.setFormed(false);
+                for (IBlockPattern pattern : structures.values()) {
+                    pattern.getPatternState().setFormed(false);
                 }
             } else {
-                getSubstructure(name).setFormed(buf.readBoolean());
+                getSubstructure(name).getPatternState().setFormed(buf.readBoolean());
             }
 
             if (!isStructureFormed()) {
@@ -563,7 +651,7 @@ public boolean isStructureFormed() {
     public boolean isStructureFormed(String name) {
         if (getWorld() == null) return false;
 
-        return getSubstructure(name).isFormed();
+        return getSubstructure(name).getPatternState().isFlipped();
     }
 
     @Override
@@ -667,7 +755,7 @@ public List getBuildableShapes(@Nullable Object2IntMap parts = new ArrayList<>(getMultiblockParts());
-        for (IMultiblockPart part : parts) {
+        ;
+        for (IMultiblockPart part : multiblockParts) {
             part.removeFromMultiBlock(this);
             ((MetaTileEntity) part).doExplosion(explosionPower);
         }
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java
index 55723ef456d..b1a26a7d4ee 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java
@@ -13,7 +13,6 @@
 import gregtech.api.gui.widgets.ImageWidget;
 import gregtech.api.gui.widgets.IndicatorImageWidget;
 import gregtech.api.gui.widgets.ProgressWidget;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.unification.OreDictUnifier;
 import gregtech.api.unification.material.Materials;
@@ -181,8 +180,8 @@ public boolean isStructureObstructed() {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         if (this.hasMaintenanceMechanics() && ConfigHolder.machines.enableMaintenance) { // nothing extra if no
                                                                                          // maintenance
             if (getAbilities(MultiblockAbility.MAINTENANCE_HATCH).isEmpty())
@@ -199,7 +198,7 @@ protected void formStructure(PatternMatchContext context) {
                 storeTaped(false);
             }
         }
-        this.variantActiveBlocks = context.getOrDefault("VABlock", new LinkedList<>());
+        this.variantActiveBlocks = getVABlocks(getSubstructure(name).getCache());
         replaceVariantBlocksActive(false);
     }
 
@@ -307,7 +306,7 @@ public boolean isActive() {
     }
 
     @Override
-    public void invalidateStructure() {
+    public void invalidateStructure(String name) {
         if (hasMaintenanceMechanics() && ConfigHolder.machines.enableMaintenance) { // nothing extra if no maintenance
             if (!getAbilities(MultiblockAbility.MAINTENANCE_HATCH).isEmpty())
                 getAbilities(MultiblockAbility.MAINTENANCE_HATCH).get(0)
@@ -318,7 +317,7 @@ public void invalidateStructure() {
         this.fluidInfSink = false;
         this.itemInfSink = false;
         this.maintenanceHatch = null;
-        super.invalidateStructure();
+        super.invalidateStructure(name);
     }
 
     protected void replaceVariantBlocksActive(boolean isActive) {
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java
index 4949074c91a..c1885f1056b 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java
@@ -10,7 +10,6 @@
 import gregtech.api.capability.impl.MultiblockRecipeLogic;
 import gregtech.api.items.itemhandlers.GTItemStackHandler;
 import gregtech.api.metatileentity.IDataInfoProvider;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMap;
@@ -93,14 +92,14 @@ public boolean checkRecipe(@NotNull Recipe recipe, boolean consumeIfSuccess) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         initializeAbilities();
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         resetTileAbilities();
         this.recipeMapWorkable.invalidate();
     }
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java
index da874e0bdb8..c7cbe408016 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java
@@ -73,8 +73,8 @@ public boolean isActive() {
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         recipeMapWorkable.invalidate();
     }
 
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java
index fd396609419..668434b31f6 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java
@@ -10,7 +10,6 @@
 import gregtech.api.gui.widgets.IndicatorImageWidget;
 import gregtech.api.items.itemhandlers.GTItemStackHandler;
 import gregtech.api.metatileentity.MTETrait;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.recipes.Recipe;
 import gregtech.api.recipes.RecipeMap;
@@ -72,14 +71,14 @@ public boolean checkRecipe(Recipe recipe, boolean consumeIfProcess) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         initializeAbilities();
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         resetTileAbilities();
     }
 
diff --git a/src/main/java/gregtech/api/pattern/BlockWorldState.java b/src/main/java/gregtech/api/pattern/BlockWorldState.java
index 60453626510..2f4fb431c08 100644
--- a/src/main/java/gregtech/api/pattern/BlockWorldState.java
+++ b/src/main/java/gregtech/api/pattern/BlockWorldState.java
@@ -1,7 +1,5 @@
 package gregtech.api.pattern;
 
-import gregtech.api.util.GTLog;
-
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.math.BlockPos;
@@ -10,21 +8,15 @@
 import org.jetbrains.annotations.Nullable;
 
 /**
- * Class allowing access to a block at a certain pos for structure checks and contains structure information for legacy
+ * Class allowing access to a block at a certain pos for structure checks and contains structure information.
  */
 public class BlockWorldState {
 
-    protected static boolean warned = false;
     protected World world;
     protected BlockPos pos;
     protected IBlockState state;
     protected TileEntity tileEntity;
     protected boolean tileEntityInitialized;
-    protected final StructureInfo info;
-
-    public BlockWorldState(StructureInfo info) {
-        this.info = info;
-    }
 
     public void update(World worldIn, GreggyBlockPos pos) {
         this.world = worldIn;
@@ -35,7 +27,10 @@ public void update(World worldIn, GreggyBlockPos pos) {
     }
 
     public void setPos(GreggyBlockPos pos) {
-        setPos(pos.immutable());
+        this.pos = pos.immutable();
+        this.state = null;
+        this.tileEntity = null;
+        this.tileEntityInitialized = false;
     }
 
     public void setPos(BlockPos pos) {
@@ -45,28 +40,6 @@ public void setPos(BlockPos pos) {
         this.tileEntityInitialized = false;
     }
 
-    @Deprecated
-    public boolean hasError() {
-        warn("hasError()");
-        return info.getError() != null;
-    }
-
-    @Deprecated
-    public void setError(PatternError error) {
-        warn("setError(PatternError)");
-        info.setError(error);
-    }
-
-    @Deprecated
-    public PatternMatchContext getMatchContext() {
-        warn("getMatchContext()");
-        return info.getContext();
-    }
-
-    public StructureInfo getStructureInfo() {
-        return this.info;
-    }
-
     public IBlockState getBlockState() {
         if (this.state == null) {
             this.state = this.world.getBlockState(this.pos);
@@ -96,12 +69,4 @@ public World getWorld() {
     public void setWorld(World world) {
         this.world = world;
     }
-
-    protected void warn(String name) {
-        if (warned) return;
-
-        GTLog.logger.warn("Calling " + name +
-                " on BlockWorldState is deprecated! Use the method on StructureInfo, obtained via BlockWorldState#getStructureInfo() !");
-        warned = true;
-    }
 }
diff --git a/src/main/java/gregtech/api/pattern/PatternMatchContext.java b/src/main/java/gregtech/api/pattern/PatternMatchContext.java
deleted file mode 100644
index 7945a31fb57..00000000000
--- a/src/main/java/gregtech/api/pattern/PatternMatchContext.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package gregtech.api.pattern;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Supplier;
-
-/**
- * Contains an context used for storing temporary data
- * related to current check and shared between all predicates doing it
- */
-public class PatternMatchContext {
-
-    private final Map data = new HashMap<>();
-
-    private boolean neededFlip = false;
-
-    public void reset() {
-        this.data.clear();
-        this.neededFlip = false;
-    }
-
-    public void set(String key, Object value) {
-        this.data.put(key, value);
-    }
-
-    public int getInt(String key) {
-        return data.containsKey(key) ? (int) data.get(key) : 0;
-    }
-
-    public void increment(String key, int value) {
-        set(key, getOrDefault(key, 0) + value);
-    }
-
-    public  T getOrDefault(String key, T defaultValue) {
-        // noinspection unchecked
-        return (T) data.getOrDefault(key, defaultValue);
-    }
-
-    @SuppressWarnings("unchecked")
-    public  T get(String key) {
-        return (T) data.get(key);
-    }
-
-    public  T getOrCreate(String key, Supplier creator) {
-        T result = get(key);
-        if (result == null) {
-            result = creator.get();
-            set(key, result);
-        }
-        return result;
-    }
-
-    public  T getOrPut(String key, T initialValue) {
-        T result = get(key);
-        if (result == null) {
-            result = initialValue;
-            set(key, result);
-        }
-        return result;
-    }
-
-    @NotNull
-    public Set> entrySet() {
-        return data.entrySet();
-    }
-
-    public boolean neededFlip() {
-        return neededFlip;
-    }
-
-    public void setNeededFlip(boolean neededFlip) {
-        this.neededFlip = neededFlip;
-    }
-}
diff --git a/src/main/java/gregtech/api/pattern/StructureInfo.java b/src/main/java/gregtech/api/pattern/StructureInfo.java
deleted file mode 100644
index 11d75fd5767..00000000000
--- a/src/main/java/gregtech/api/pattern/StructureInfo.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package gregtech.api.pattern;
-
-public class StructureInfo {
-
-    protected final PatternMatchContext context;
-    protected PatternError error;
-
-    public StructureInfo(PatternMatchContext context, PatternError error) {
-        this.context = context;
-        this.error = error;
-    }
-
-    public boolean hasError() {
-        return error != null;
-    }
-
-    public PatternError getError() {
-        return error;
-    }
-
-    public PatternMatchContext getContext() {
-        return context;
-    }
-
-    public void setError(PatternError error) {
-        this.error = error;
-    }
-}
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index fc7ff380008..1d86fd7602b 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -1,10 +1,10 @@
 package gregtech.api.pattern;
 
 import gregtech.api.GregTechAPI;
-import gregtech.api.block.IHeatingCoilBlockStats;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.pattern.pattern.PatternState;
 import gregtech.api.util.BlockInfo;
 
 import net.minecraft.block.state.IBlockState;
@@ -34,20 +34,8 @@ public class TraceabilityPredicate {
                     blockWorldState.getWorld(), blockWorldState.getPos()));
     // Allow all heating coils, and require them to have the same type.
     public static Supplier HEATING_COILS = () -> new TraceabilityPredicate(
-            (blockWorldState, info) -> {
-                IBlockState blockState = blockWorldState.getBlockState();
-                if (GregTechAPI.HEATING_COILS.containsKey(blockState)) {
-                    IHeatingCoilBlockStats stats = GregTechAPI.HEATING_COILS.get(blockState);
-                    Object currentCoil = info.getContext().getOrPut("CoilType", stats);
-                    if (!currentCoil.equals(stats)) {
-                        info.setError(new PatternStringError("gregtech.multiblock.pattern.error.coils"));
-                        return false;
-                    }
-                    info.getContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos());
-                    return true;
-                }
-                return false;
-            }, () -> GregTechAPI.HEATING_COILS.entrySet().stream()
+            (worldState, patternState) -> GregTechAPI.HEATING_COILS.containsKey(worldState.getBlockState()),
+            () -> GregTechAPI.HEATING_COILS.entrySet().stream()
                     // sort to make autogenerated jei previews not pick random coils each game load
                     .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
                     .map(entry -> new BlockInfo(entry.getKey(), null))
@@ -79,12 +67,12 @@ public TraceabilityPredicate(Predicate predicate) {
         this(predicate, null);
     }
 
-    public TraceabilityPredicate(BiPredicate predicate,
+    public TraceabilityPredicate(BiPredicate predicate,
                                  Supplier candidates) {
         common.add(new SimplePredicate(predicate, candidates));
     }
 
-    public TraceabilityPredicate(BiPredicate predicate) {
+    public TraceabilityPredicate(BiPredicate predicate) {
         this(predicate, null);
     }
 
@@ -244,7 +232,7 @@ public TraceabilityPredicate setPreviewCount(int count) {
         return this;
     }
 
-    public boolean test(BlockWorldState blockWorldState, StructureInfo info, Object2IntMap globalCache,
+    public boolean test(BlockWorldState blockWorldState, PatternState info, Object2IntMap globalCache,
                         Object2IntMap layerCache) {
         for (SimplePredicate predicate : limited) {
             if (predicate.testLimited(blockWorldState, info, globalCache, layerCache)) {
@@ -274,7 +262,7 @@ public static class SimplePredicate {
 
         public final Supplier candidates;
 
-        public final BiPredicate predicate;
+        public final BiPredicate predicate;
 
         @SideOnly(Side.CLIENT)
         private List toolTips;
@@ -293,7 +281,7 @@ public SimplePredicate(Predicate predicate, Supplier predicate,
+        public SimplePredicate(BiPredicate predicate,
                                Supplier candidates) {
             this.predicate = predicate;
             this.candidates = candidates;
@@ -334,39 +322,39 @@ public List getToolTips(TraceabilityPredicate predicates) {
             return result;
         }
 
-        public boolean test(BlockWorldState state, StructureInfo info) {
+        public boolean test(BlockWorldState state, PatternState info) {
             return predicate.test(state, info);
         }
 
-        public boolean testLimited(BlockWorldState blockWorldState, StructureInfo info,
+        public boolean testLimited(BlockWorldState worldState, PatternState patternState,
                                    Object2IntMap globalCache,
                                    Object2IntMap layerCache) {
-            return testGlobal(blockWorldState, info, globalCache) && testLayer(blockWorldState, info, layerCache);
+            return testGlobal(worldState, patternState, globalCache) && testLayer(worldState, patternState, layerCache);
         }
 
-        public boolean testGlobal(BlockWorldState blockWorldState, StructureInfo info,
+        public boolean testGlobal(BlockWorldState worldState, PatternState patternState,
                                   Object2IntMap cache) {
             if (minGlobalCount == -1 && maxGlobalCount == -1 || cache == null) return true;
 
-            boolean base = predicate.test(blockWorldState, info);
+            boolean base = predicate.test(worldState, patternState);
             int count = cache.getInt(this);
             count += base ? 1 : 0;
             cache.put(this, count);
             if (maxGlobalCount == -1 || count <= maxGlobalCount) return base;
-            info.setError(new SinglePredicateError(this, 0));
+            patternState.setError(new SinglePredicateError(this, 0));
             return false;
         }
 
-        public boolean testLayer(BlockWorldState blockWorldState, StructureInfo info,
+        public boolean testLayer(BlockWorldState worldState, PatternState patternState,
                                  Object2IntMap cache) {
             if (minLayerCount == -1 && maxLayerCount == -1 || cache == null) return true;
 
-            boolean base = predicate.test(blockWorldState, info);
+            boolean base = predicate.test(worldState, patternState);
             int count = cache.getInt(this);
             count += base ? 1 : 0;
             cache.put(this, count);
             if (maxLayerCount == -1 || count <= maxLayerCount) return base;
-            info.setError(new SinglePredicateError(this, 2));
+            patternState.setError(new SinglePredicateError(this, 2));
             return false;
         }
 
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index eb54072a2d7..4eb15f17c9f 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -1,19 +1,13 @@
 package gregtech.api.pattern.pattern;
 
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
-import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
-import gregtech.api.pattern.PatternError;
-import gregtech.api.pattern.PatternMatchContext;
-import gregtech.api.pattern.StructureInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
-import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
 import net.minecraft.block.state.IBlockState;
-import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
@@ -32,7 +26,7 @@ public class BlockPattern implements IBlockPattern {
     /**
      * In the form of [ aisleDir, stringDir, charDir ]
      */
-    public final RelativeDirection[] structureDir;
+    protected final RelativeDirection[] directions;
 
     /**
      * In the form of [ num aisles, num string per aisle, num char per string ]
@@ -53,13 +47,11 @@ public class BlockPattern implements IBlockPattern {
     protected final boolean hasStartOffset;
     protected final PatternAisle[] aisles;
     protected final Char2ObjectMap predicates;
-    protected final StructureInfo info;
     protected final BlockWorldState worldState;
-    protected final PatternMatchContext matchContext = new PatternMatchContext();
     protected final Object2IntMap globalCount = new Object2IntOpenHashMap<>();
     protected final Object2IntMap layerCount = new Object2IntOpenHashMap<>();
-
-    public Long2ObjectMap cache = new Long2ObjectOpenHashMap<>();
+    protected final PatternState state = new PatternState();
+    protected final Long2ObjectMap cache = new Long2ObjectOpenHashMap<>();
 
     /**
      * The repetitions per aisle along the axis of repetition
@@ -73,7 +65,7 @@ public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] di
                         char centerChar) {
         this.aisles = aisles;
         this.dimensions = dimensions;
-        this.structureDir = directions;
+        this.directions = directions;
         this.predicates = predicates;
         hasStartOffset = startOffset != null;
 
@@ -84,8 +76,7 @@ public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] di
             this.startOffset = startOffset;
         }
 
-        this.info = new StructureInfo(matchContext, null);
-        this.worldState = new BlockWorldState(info);
+        this.worldState = new BlockWorldState();
     }
 
     /**
@@ -103,9 +94,9 @@ private void legacyStartOffset(char center) {
             if (result != null) {
                 // structure starts at aisle 0, string 0, char 0, think about it
                 // so relative to the controller we need to offset by this to get to the start
-                moveStartOffset(structureDir[0], -aisleI);
-                moveStartOffset(structureDir[1], -result[0]);
-                moveStartOffset(structureDir[2], -result[1]);
+                moveStartOffset(directions[0], -aisleI);
+                moveStartOffset(directions[1], -result[0]);
+                moveStartOffset(directions[2], -result[1]);
                 return;
             }
         }
@@ -113,13 +104,10 @@ private void legacyStartOffset(char center) {
         throw new IllegalStateException("Failed to find center char: '" + center + "'");
     }
 
-    public PatternError getError() {
-        return info.getError();
-    }
-
+    @NotNull
     @Override
-    public PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, EnumFacing frontFacing,
-                                                  EnumFacing upwardsFacing, boolean allowsFlip) {
+    public PatternState checkPatternFastAt(World world, BlockPos centerPos, EnumFacing frontFacing,
+                                           EnumFacing upwardsFacing, boolean allowsFlip) {
         if (!cache.isEmpty()) {
             boolean pass = true;
             GreggyBlockPos gregPos = new GreggyBlockPos();
@@ -142,29 +130,48 @@ public PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, E
                     }
                 }
             }
-            if (pass) return info.hasError() ? null : matchContext;
+            if (pass) {
+                if (state.hasError()) {
+                    state.setState(PatternState.EnumCheckState.INVALID_CACHED);
+                } else {
+                    state.setState(PatternState.EnumCheckState.VALID_CACHED);
+                }
+
+                return state;
+            }
         }
 
         // First try normal pattern, and if it fails, try flipped (if allowed).
         boolean valid = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, false);
-        if (valid) return matchContext;
+        if (valid) {
+            // reaching here means the cache failed/empty
+            state.setState(PatternState.EnumCheckState.VALID_UNCACHED);
+            state.setFlipped(false);
+            return state;
+        }
 
         if (allowsFlip) {
             valid = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, true);
         }
-        if (!valid) clearCache(); // we don't want a random cache of a partially formed multi
-        return null;
+        if (!valid) { // we don't want a random cache of a partially formed multi
+            clearCache();
+            state.setState(PatternState.EnumCheckState.INVALID_UNCACHED);
+            return state;
+        }
+
+        state.setState(PatternState.EnumCheckState.VALID_UNCACHED);
+        state.setFlipped(true);
+        return state;
     }
 
     @Override
-    public void clearCache() {
-        cache.clear();
+    public Long2ObjectMap getCache() {
+        return cache;
     }
 
     @Override
     public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
                                   EnumFacing upwardsFacing, boolean isFlipped) {
-        this.matchContext.reset();
         this.globalCount.clear();
         this.layerCount.clear();
         cache.clear();
@@ -200,13 +207,12 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
         // global minimum checks
         for (Object2IntMap.Entry entry : globalCount.object2IntEntrySet()) {
             if (entry.getIntValue() < entry.getKey().minGlobalCount) {
-                info.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 1));
+                state.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 1));
                 return false;
             }
         }
 
-        info.setError(null);
-        matchContext.setNeededFlip(isFlipped);
+        state.setError(null);
         return true;
     }
 
@@ -221,14 +227,14 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
      * @param aisleOffset   The offset of the aisle, how much offset in aisleDir to check the blocks in world, for
      *                      example, if the first aisle is repeated 2 times, aisleIndex is 1 while this is 2
      * @param flip          Whether to flip or not
-     * @return True if the check passed, otherwise the {@link StructureInfo} would have been updated with an error
+     * @return True if the check passed
      */
     public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing, int aisleIndex,
                               int aisleOffset, boolean flip) {
         // absolute facings from the relative facings
-        EnumFacing absoluteAisle = structureDir[0].getRelativeFacing(frontFacing, upFacing, flip);
-        EnumFacing absoluteString = structureDir[1].getRelativeFacing(frontFacing, upFacing, flip);
-        EnumFacing absoluteChar = structureDir[2].getRelativeFacing(frontFacing, upFacing, flip);
+        EnumFacing absoluteAisle = directions[0].getRelativeFacing(frontFacing, upFacing, flip);
+        EnumFacing absoluteString = directions[1].getRelativeFacing(frontFacing, upFacing, flip);
+        EnumFacing absoluteChar = directions[2].getRelativeFacing(frontFacing, upFacing, flip);
 
         // where the aisle would start in world
         GreggyBlockPos aisleStart = startPos(controllerPos, frontFacing, upFacing, flip)
@@ -252,9 +258,9 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
                             !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
                 }
 
-//                GTLog.logger.info("Checked pos at " + charPos + " with flip " + flip);
+                // GTLog.logger.info("Checked pos at " + charPos + " with flip " + flip);
 
-                boolean result = predicate.test(worldState, info, globalCount, layerCount);
+                boolean result = predicate.test(worldState, state, globalCount, layerCount);
                 if (!result) return false;
 
                 charPos.offset(absoluteChar);
@@ -267,7 +273,7 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
             // layer minimum checks
             for (Object2IntMap.Entry entry : layerCount.object2IntEntrySet()) {
                 if (entry.getIntValue() < entry.getKey().minLayerCount) {
-                    info.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 3));
+                    state.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 3));
                     return false;
                 }
             }
@@ -275,202 +281,6 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
         return true;
     }
 
-    public void autoBuild(EntityPlayer player, MultiblockControllerBase controllerBase) {
-        // World world = player.world;
-        // BlockWorldState worldState = new BlockWorldState();
-        // int minZ = -centerOffset[4];
-        // EnumFacing facing = controllerBase.getFrontFacing().getOpposite();
-        // BlockPos centerPos = controllerBase.getPos();
-        // Map cacheInfos = new HashMap<>();
-        // Map cacheGlobal = new HashMap<>();
-        // Map blocks = new HashMap<>();
-        // blocks.put(controllerBase.getPos(), controllerBase);
-        // for (int c = 0, z = minZ++, r; c < this.fingerLength; c++) {
-        // for (r = 0; r < aisleRepetitions[c][0]; r++) {
-        // Map cacheLayer = new HashMap<>();
-        // for (int b = 0, y = -centerOffset[1]; b < this.thumbLength; b++, y++) {
-        // for (int a = 0, x = -centerOffset[0]; a < this.palmLength; a++, x++) {
-        // TraceabilityPredicate predicate = this.blockMatches[c][b][a];
-        // BlockPos pos = setActualRelativeOffset(x, y, z, facing, controllerBase.getUpwardsFacing(),
-        // controllerBase.isFlipped())
-        // .add(centerPos.getX(), centerPos.getY(), centerPos.getZ());
-        // worldState.update(world, pos, matchContext, globalCount, layerCount, predicate);
-        // if (!world.getBlockState(pos).getMaterial().isReplaceable()) {
-        // blocks.put(pos, world.getBlockState(pos));
-        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-        // limit.testLimited(worldState);
-        // }
-        // } else {
-        // boolean find = false;
-        // BlockInfo[] infos = new BlockInfo[0];
-        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-        // if (limit.minLayerCount > 0) {
-        // if (!cacheLayer.containsKey(limit)) {
-        // cacheLayer.put(limit, 1);
-        // } else
-        // if (cacheLayer.get(limit) < limit.minLayerCount && (limit.maxLayerCount == -1 ||
-        // cacheLayer.get(limit) < limit.maxLayerCount)) {
-        // cacheLayer.put(limit, cacheLayer.get(limit) + 1);
-        // } else {
-        // continue;
-        // }
-        // } else {
-        // continue;
-        // }
-        // if (!cacheInfos.containsKey(limit)) {
-        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-        // }
-        // infos = cacheInfos.get(limit);
-        // find = true;
-        // break;
-        // }
-        // if (!find) {
-        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-        // if (limit.minGlobalCount > 0) {
-        // if (!cacheGlobal.containsKey(limit)) {
-        // cacheGlobal.put(limit, 1);
-        // } else if (cacheGlobal.get(limit) < limit.minGlobalCount &&
-        // (limit.maxGlobalCount == -1 ||
-        // cacheGlobal.get(limit) < limit.maxGlobalCount)) {
-        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-        // } else {
-        // continue;
-        // }
-        // } else {
-        // continue;
-        // }
-        // if (!cacheInfos.containsKey(limit)) {
-        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-        // }
-        // infos = cacheInfos.get(limit);
-        // find = true;
-        // break;
-        // }
-        // }
-        // if (!find) { // no limited
-        // for (TraceabilityPredicate.SimplePredicate limit : predicate.limited) {
-        // if (limit.maxLayerCount != -1 &&
-        // cacheLayer.getOrDefault(limit, Integer.MAX_VALUE) == limit.maxLayerCount)
-        // continue;
-        // if (limit.maxGlobalCount != -1 &&
-        // cacheGlobal.getOrDefault(limit, Integer.MAX_VALUE) == limit.maxGlobalCount)
-        // continue;
-        // if (!cacheInfos.containsKey(limit)) {
-        // cacheInfos.put(limit, limit.candidates == null ? null : limit.candidates.get());
-        // }
-        // if (cacheLayer.containsKey(limit)) {
-        // cacheLayer.put(limit, cacheLayer.get(limit) + 1);
-        // } else {
-        // cacheLayer.put(limit, 1);
-        // }
-        // if (cacheGlobal.containsKey(limit)) {
-        // cacheGlobal.put(limit, cacheGlobal.get(limit) + 1);
-        // } else {
-        // cacheGlobal.put(limit, 1);
-        // }
-        // infos = ArrayUtils.addAll(infos, cacheInfos.get(limit));
-        // }
-        // for (TraceabilityPredicate.SimplePredicate common : predicate.common) {
-        // if (!cacheInfos.containsKey(common)) {
-        // cacheInfos.put(common,
-        // common.candidates == null ? null : common.candidates.get());
-        // }
-        // infos = ArrayUtils.addAll(infos, cacheInfos.get(common));
-        // }
-        // }
-        //
-        // List candidates = Arrays.stream(infos)
-        // .filter(info -> info.getBlockState().getBlock() != Blocks.AIR).map(info -> {
-        // IBlockState blockState = info.getBlockState();
-        // MetaTileEntity metaTileEntity = info
-        // .getTileEntity() instanceof IGregTechTileEntity ?
-        // ((IGregTechTileEntity) info.getTileEntity())
-        // .getMetaTileEntity() :
-        // null;
-        // if (metaTileEntity != null) {
-        // return metaTileEntity.getStackForm();
-        // } else {
-        // return new ItemStack(Item.getItemFromBlock(blockState.getBlock()), 1,
-        // blockState.getBlock().damageDropped(blockState));
-        // }
-        // }).collect(Collectors.toList());
-        // if (candidates.isEmpty()) continue;
-        // // check inventory
-        // ItemStack found = null;
-        // if (!player.isCreative()) {
-        // for (ItemStack itemStack : player.inventory.mainInventory) {
-        // if (candidates.stream().anyMatch(candidate -> candidate.isItemEqual(itemStack)) &&
-        // !itemStack.isEmpty() && itemStack.getItem() instanceof ItemBlock) {
-        // found = itemStack.copy();
-        // itemStack.setCount(itemStack.getCount() - 1);
-        // break;
-        // }
-        // }
-        // if (found == null) continue;
-        // } else {
-        // for (int i = candidates.size() - 1; i >= 0; i--) {
-        // found = candidates.get(i).copy();
-        // if (!found.isEmpty() && found.getItem() instanceof ItemBlock) {
-        // break;
-        // }
-        // found = null;
-        // }
-        // if (found == null) continue;
-        // }
-        // ItemBlock itemBlock = (ItemBlock) found.getItem();
-        // IBlockState state = itemBlock.getBlock()
-        // .getStateFromMeta(itemBlock.getMetadata(found.getMetadata()));
-        // blocks.put(pos, state);
-        // world.setBlockState(pos, state);
-        // TileEntity holder = world.getTileEntity(pos);
-        // if (holder instanceof IGregTechTileEntity igtte) {
-        // MTERegistry registry = GregTechAPI.mteManager
-        // .getRegistry(found.getItem().getRegistryName().getNamespace());
-        // MetaTileEntity sampleMetaTileEntity = registry.getObjectById(found.getItemDamage());
-        // if (sampleMetaTileEntity != null) {
-        // MetaTileEntity metaTileEntity = igtte.setMetaTileEntity(sampleMetaTileEntity);
-        // metaTileEntity.onPlacement();
-        // blocks.put(pos, metaTileEntity);
-        // if (found.getTagCompound() != null) {
-        // metaTileEntity.initFromItemStackData(found.getTagCompound());
-        // }
-        // }
-        // }
-        // }
-        // }
-        // }
-        // z++;
-        // }
-        // }
-        // EnumFacing[] facings = ArrayUtils.addAll(new EnumFacing[] { controllerBase.getFrontFacing() }, FACINGS); //
-        // follow
-        // // controller
-        // // first
-        // blocks.forEach((pos, block) -> { // adjust facing
-        // if (block instanceof MetaTileEntity) {
-        // MetaTileEntity metaTileEntity = (MetaTileEntity) block;
-        // boolean find = false;
-        // for (EnumFacing enumFacing : facings) {
-        // if (metaTileEntity.isValidFrontFacing(enumFacing)) {
-        // if (!blocks.containsKey(pos.offset(enumFacing))) {
-        // metaTileEntity.setFrontFacing(enumFacing);
-        // find = true;
-        // break;
-        // }
-        // }
-        // }
-        // if (!find) {
-        // for (EnumFacing enumFacing : FACINGS) {
-        // if (world.isAirBlock(pos.offset(enumFacing)) && metaTileEntity.isValidFrontFacing(enumFacing)) {
-        // metaTileEntity.setFrontFacing(enumFacing);
-        // break;
-        // }
-        // }
-        // }
-        // }
-        // });
-    }
-
     @Override
     public PreviewBlockPattern getDefaultShape() {
         char[][][] pattern = new char[dimensions[2]][dimensions[1]][dimensions[0]];
@@ -488,6 +298,11 @@ public PreviewBlockPattern getDefaultShape() {
         return null;
     }
 
+    @Override
+    public PatternState getPatternState() {
+        return state;
+    }
+
     @Override
     public boolean legacyBuilderError() {
         return !hasStartOffset;
@@ -516,16 +331,6 @@ public void moveStartOffset(RelativeDirection dir, int amount) {
         startOffset[dir.ordinal() / 2] += amount;
     }
 
-    /**
-     * Gets the start offset. You probably should use {@link BlockPattern#moveStartOffset(RelativeDirection, int)}
-     * instead of mutating the result, but I can't stop you.
-     * 
-     * @return The start offset.
-     */
-    public int[] getStartOffset() {
-        return startOffset;
-    }
-
     /**
      * Get the start offset in the given direction.
      * 
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index 2527be82aa6..239b5af7f68 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -3,8 +3,6 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
-import gregtech.api.pattern.PatternMatchContext;
-import gregtech.api.pattern.StructureInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.RelativeDirection;
@@ -33,17 +31,19 @@ public class ExpandablePattern implements IBlockPattern {
      * In the form of [ aisleDir, stringDir, charDir ]
      */
     protected final RelativeDirection[] directions;
-    protected final PatternMatchContext matchContext = new PatternMatchContext();
-    protected final StructureInfo info;
     protected final BlockWorldState worldState;
-    protected final Long2ObjectMap cache = new Long2ObjectOpenHashMap<>();
     protected final Object2IntMap globalCount = new Object2IntOpenHashMap<>();
+    protected final PatternState state = new PatternState();
+    protected final Long2ObjectMap cache = new Long2ObjectOpenHashMap<>();
 
     /**
      * New expandable pattern normally you would use {@link FactoryExpandablePattern} instead.
-     * @param boundsFunction A function to supply bounds, order in the way .values() are ordered in RelativeDirection.
-     * @param predicateFunction Given a pos and bounds(the one you just passed in, not mutated), return a predicate. The pos is offset as explained in the builder method.
-     * @param directions The structure directions, explained in the builder method.
+     * 
+     * @param boundsFunction    A function to supply bounds, order in the way .values() are ordered in
+     *                          RelativeDirection.
+     * @param predicateFunction Given a pos and bounds(the one you just passed in, not mutated), return a predicate. The
+     *                          pos is offset as explained in the builder method.
+     * @param directions        The structure directions, explained in the builder method.
      */
     public ExpandablePattern(@NotNull QuadFunction boundsFunction,
                              @NotNull BiFunction predicateFunction,
@@ -52,13 +52,13 @@ public ExpandablePattern(@NotNull QuadFunction entry : globalCount.object2IntEntrySet()) {
             if (entry.getIntValue() < entry.getKey().minGlobalCount) {
-                info.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 1));
+                state.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 1));
                 return false;
             }
         }
-
         return true;
     }
 
@@ -167,6 +171,16 @@ public PreviewBlockPattern getDefaultShape() {
         return null;
     }
 
+    @Override
+    public PatternState getPatternState() {
+        return state;
+    }
+
+    @Override
+    public Long2ObjectMap getCache() {
+        return this.cache;
+    }
+
     @Override
     public void clearCache() {
         cache.clear();
diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
index d2e156263f5..19bbe672e1c 100644
--- a/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
@@ -50,15 +50,18 @@ public static FactoryExpandablePattern start(RelativeDirection aisleDir, Relativ
     }
 
     /**
-     * Same as calling {@link FactoryExpandablePattern#start(RelativeDirection, RelativeDirection, RelativeDirection)} with BACK, UP, RIGHT
+     * Same as calling {@link FactoryExpandablePattern#start(RelativeDirection, RelativeDirection, RelativeDirection)}
+     * with BACK, UP, RIGHT
      */
     public static FactoryExpandablePattern start() {
         return new FactoryExpandablePattern(RelativeDirection.BACK, RelativeDirection.UP, RelativeDirection.RIGHT);
     }
 
     /**
-     * This supplies the bounds function. The inputs are: World, controller pos, front facing, up facing. The returned array
-     * is an int array of length 6, with how much to extend the multiblock in each direction. The order of the directions is the same
+     * This supplies the bounds function. The inputs are: World, controller pos, front facing, up facing. The returned
+     * array
+     * is an int array of length 6, with how much to extend the multiblock in each direction. The order of the
+     * directions is the same
      * as the ordinal of the enum.
      */
     public FactoryExpandablePattern boundsFunction(QuadFunction function) {
@@ -67,10 +70,14 @@ public FactoryExpandablePattern boundsFunction(QuadFunction function) {
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index fdecca3eea4..b05761d9a20 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -1,12 +1,15 @@
 package gregtech.api.pattern.pattern;
 
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.PatternMatchContext;
+import gregtech.api.util.BlockInfo;
 
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
 
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import org.jetbrains.annotations.NotNull;
+
 public interface IBlockPattern {
 
     /**
@@ -19,10 +22,11 @@ public interface IBlockPattern {
      * @param upwardsFacing The up facing of the controller, obtained via
      *                      {@link MultiblockControllerBase#getUpwardsFacing()}
      * @param allowsFlip    Whether the multiblock allows flipping.
-     * @return A context for the formed structure, or null if the structure check failed.
+     * @return The internal state of the pattern. Check whether its valid first before using other fields.
      */
-    PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, EnumFacing frontFacing,
-                                           EnumFacing upwardsFacing, boolean allowsFlip);
+    @NotNull
+    PatternState checkPatternFastAt(World world, BlockPos centerPos, EnumFacing frontFacing,
+                                    EnumFacing upwardsFacing, boolean allowsFlip);
 
     /**
      * Checks the whole pattern, you should probably use checkPatternFastAt(...) instead.
@@ -44,10 +48,28 @@ boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
      */
     PreviewBlockPattern getDefaultShape();
 
+    /**
+     * Gets the internal pattern state, you should use the one returned from
+     * {@link IBlockPattern#checkPatternFastAt(World, BlockPos, EnumFacing, EnumFacing, boolean)} always
+     * except for the shouldUpdate field.
+     */
+    PatternState getPatternState();
+
     /**
      * Clears the cache for checkPatternFastAt(...) in case something in the pattern is changed.
      */
-    void clearCache();
+    default void clearCache() {
+        getCache().clear();
+    }
+
+    /**
+     * Gets the cache, if you modify literally anything in the cache except clearing it(in which case you should use
+     * clearCache())
+     * then GTCEu is now licensed under ARR just for you, so close your IDE or else.
+     * 
+     * @return The cache for rapid pattern checking.
+     */
+    Long2ObjectMap getCache();
 
     /**
      * If anything from legacy need to be updated.
diff --git a/src/main/java/gregtech/api/pattern/pattern/PatternInfo.java b/src/main/java/gregtech/api/pattern/pattern/PatternInfo.java
deleted file mode 100644
index 732b398b07d..00000000000
--- a/src/main/java/gregtech/api/pattern/pattern/PatternInfo.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package gregtech.api.pattern.pattern;
-
-import org.jetbrains.annotations.ApiStatus;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Simple wrapper class for a single substructure
- */
-public class PatternInfo {
-
-    @NotNull
-    protected final IBlockPattern pattern;
-    protected boolean isFormed = false;
-    protected boolean isFlipped = false;
-    public boolean shouldUpdate = true;
-
-    public PatternInfo(@NotNull IBlockPattern pattern) {
-        this.pattern = pattern;
-    }
-
-    public boolean isFormed() {
-        return isFormed;
-    }
-
-    public void setFormed(boolean val) {
-        this.isFormed = val;
-    }
-
-    public boolean isFlipped() {
-        return isFlipped;
-    }
-
-    @ApiStatus.Internal
-    public void setFlipped(boolean val) {
-        isFlipped = val;
-    }
-
-    public @NotNull IBlockPattern getPattern() {
-        return pattern;
-    }
-}
diff --git a/src/main/java/gregtech/api/pattern/pattern/PatternState.java b/src/main/java/gregtech/api/pattern/pattern/PatternState.java
new file mode 100644
index 00000000000..bcf47a3f73e
--- /dev/null
+++ b/src/main/java/gregtech/api/pattern/pattern/PatternState.java
@@ -0,0 +1,109 @@
+package gregtech.api.pattern.pattern;
+
+import gregtech.api.pattern.PatternError;
+
+import org.jetbrains.annotations.ApiStatus;
+
+public class PatternState {
+
+    protected boolean isFormed = false;
+
+    /**
+     * For use by the pattern to set its flipped state. This value has no meaning if the state is invalid.
+     */
+    protected boolean isFlipped = false;
+
+    /**
+     * For use by the multiblock to update its flipped state.
+     */
+    protected boolean actualFlipped = false;
+    protected boolean shouldUpdate = false;
+    /**
+     * For if the multiblock manually invalidates its state(like coil mismatch). This means wait until the cache is
+     * no longer valid and the raw check passes to reform the structure.
+     */
+    protected boolean isWaiting = false;
+    protected PatternError error;
+    protected EnumCheckState state;
+
+    public boolean isFlipped() {
+        return isFlipped;
+    }
+
+    @ApiStatus.Internal
+    public void setFlipped(boolean flipped) {
+        isFlipped = flipped;
+    }
+
+    public boolean isFormed() {
+        return isFormed;
+    }
+
+    public void setFormed(boolean formed) {
+        isFormed = formed;
+    }
+
+    public boolean shouldUpdate() {
+        return shouldUpdate;
+    }
+
+    protected void shouldUpdate(boolean shouldUpdate) {
+        this.shouldUpdate = shouldUpdate;
+    }
+
+    public void setError(PatternError error) {
+        this.error = error;
+    }
+
+    public boolean hasError() {
+        return error != null;
+    }
+
+    protected void setState(EnumCheckState state) {
+        this.state = state;
+    }
+
+    public EnumCheckState getState() {
+        return state;
+    }
+
+    public void setActualFlipped(boolean actualFlipped) {
+        this.actualFlipped = actualFlipped;
+    }
+
+    public boolean isActualFlipped() {
+        return actualFlipped;
+    }
+
+    /**
+     * Scuffed enum representing the result of the structure check.
+     */
+    public enum EnumCheckState {
+
+        /**
+         * The cache doesn't match with the structure's data. The structure has been rechecked from scratch, is valid,
+         * and the cache is now populated.
+         */
+        VALID_UNCACHED,
+
+        /**
+         * The cache matches the structure's data.
+         */
+        VALID_CACHED,
+
+        /**
+         * The cache doesn't match with the structure's data. The structure has been rechecked from scratch, is invalid,
+         * and the cache is now empty.
+         */
+        INVALID_CACHED,
+
+        /**
+         * The cache is empty. The structure has been rechecked from scratch and is invalid, the cache remains empty.
+         */
+        INVALID_UNCACHED;
+
+        public boolean isValid() {
+            return ordinal() < 2;
+        }
+    }
+}
diff --git a/src/main/java/gregtech/common/EventHandlers.java b/src/main/java/gregtech/common/EventHandlers.java
index ecbe6b16097..e1c9dca6972 100644
--- a/src/main/java/gregtech/common/EventHandlers.java
+++ b/src/main/java/gregtech/common/EventHandlers.java
@@ -125,7 +125,7 @@ public static void onPlayerInteractionLeftClickBlock(PlayerInteractEvent.LeftCli
             if (holder instanceof IGregTechTileEntity &&
                     ((IGregTechTileEntity) holder).getMetaTileEntity() instanceof MetaTileEntityCentralMonitor) {
                 ((MetaTileEntityCentralMonitor) ((IGregTechTileEntity) holder).getMetaTileEntity())
-                        .invalidateStructure();
+                        .invalidateStructure("MAIN");
             }
         }
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOvenHatch.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOvenHatch.java
index 56b2d56e9d6..c0e207ea306 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOvenHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOvenHatch.java
@@ -78,8 +78,8 @@ protected void initializeInventory() {
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        super.addToMultiBlock(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
+        super.addToMultiBlock(controllerBase, name);
         this.fluidInventory = new FluidHandlerProxy(new FluidTankList(false), controllerBase.getExportFluids());
         this.itemInventory = new ItemHandlerProxy(controllerBase.getImportItems(), controllerBase.getExportItems());
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
index c046a525a5d..9aac6264a83 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
@@ -14,7 +14,6 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.util.TextComponentUtil;
@@ -70,14 +69,14 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         initializeAbilities();
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         resetTileAbilities();
         this.throttlePercentage = 100;
         this.recipeLogic.invalidate();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
index 5fbd95343a4..d092f74a738 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
@@ -6,7 +6,6 @@
 import gregtech.api.metatileentity.multiblock.IPrimitivePump;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.unification.material.Materials;
@@ -104,14 +103,14 @@ protected boolean openGUIOnRightClick() {
     protected void updateFormedValid() {}
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         initializeAbilities();
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         resetTileAbilities();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityTankValve.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityTankValve.java
index 0d86937ee58..b7eafff84f0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityTankValve.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityTankValve.java
@@ -94,8 +94,8 @@ private void initializeDummyInventory() {
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        super.addToMultiBlock(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
+        super.addToMultiBlock(controllerBase, name);
         this.fluidInventory = controllerBase.getFluidInventory(); // directly use controllers fluid inventory as there
                                                                   // is no reason to proxy it
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
index c8ff57b536d..3274886e4c6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
@@ -11,7 +11,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
@@ -85,8 +84,8 @@ protected void updateFormedValid() {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         List powerInput = new ArrayList<>(getAbilities(MultiblockAbility.INPUT_ENERGY));
         powerInput.addAll(getAbilities(MultiblockAbility.SUBSTATION_INPUT_ENERGY));
 
@@ -98,7 +97,7 @@ protected void formStructure(PatternMatchContext context) {
 
         // Invalidate the structure if there is not at least one output and one input
         if (powerInput.isEmpty() || powerOutput.isEmpty()) {
-            this.invalidateStructure();
+            this.invalidateStructure("MAIN");
         }
 
         this.powerInput = new EnergyContainerList(powerInput);
@@ -106,8 +105,8 @@ protected void formStructure(PatternMatchContext context) {
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         this.powerOutput = new EnergyContainerList(new ArrayList<>());
         this.powerInput = new EnergyContainerList(new ArrayList<>());
         setActive(false);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index 03c4f33bbe0..c2930db5c7e 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -24,8 +24,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
 import gregtech.api.pattern.MultiblockShapeInfo;
-import gregtech.api.pattern.PatternMatchContext;
-import gregtech.api.pattern.PatternStringError;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.FactoryExpandablePattern;
 import gregtech.api.pattern.pattern.IBlockPattern;
@@ -84,11 +82,9 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 
 import static gregtech.api.gui.widgets.AdvancedTextWidget.withButton;
-import static gregtech.common.blocks.BlockCleanroomCasing.CasingType.FILTER_CASING;
 
 public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase
                                      implements ICleanroomProvider, IWorkable, IDataInfoProvider {
@@ -127,11 +123,16 @@ private void resetTileAbilities() {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         initializeAbilities();
-        this.cleanroomFilter = FILTER_CASING;
-        this.cleanroomType = CleanroomType.CLEANROOM;
+        ICleanroomFilter type = allSameType(GregTechAPI.CLEANROOM_FILTERS, getSubstructure(name).getCache());
+        if (type == null) {
+            invalidateStructure(name);
+        } else {
+            this.cleanroomFilter = type;
+            this.cleanroomType = type.getCleanroomType();
+        }
 
         // max progress is based on the dimensions of the structure: (x^3)-(x^2)
         // taller cleanrooms take longer than wider ones
@@ -143,8 +144,8 @@ protected void formStructure(PatternMatchContext context) {
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         resetTileAbilities();
         this.cleanroomLogic.invalidate();
         this.cleanAmount = MIN_CLEAN_AMOUNT;
@@ -201,28 +202,14 @@ protected IBlockPattern createStructurePattern() {
 
     @NotNull
     protected TraceabilityPredicate filterPredicate() {
-        return new TraceabilityPredicate((blockWorldState, info) -> {
-            IBlockState blockState = blockWorldState.getBlockState();
-            if (GregTechAPI.CLEANROOM_FILTERS.containsKey(blockState)) {
-                ICleanroomFilter cleanroomFilter = GregTechAPI.CLEANROOM_FILTERS.get(blockState);
-                if (cleanroomFilter.getCleanroomType() == null) return false;
-
-                ICleanroomFilter currentFilter = info.getContext().getOrPut("FilterType",
-                        cleanroomFilter);
-                if (!currentFilter.getCleanroomType().equals(cleanroomFilter.getCleanroomType())) {
-                    info.setError(new PatternStringError("gregtech.multiblock.pattern.error.filters"));
-                    return false;
-                }
-                info.getContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos());
-                return true;
-            }
-            return false;
-        }, () -> GregTechAPI.CLEANROOM_FILTERS.entrySet().stream()
-                .filter(entry -> entry.getValue().getCleanroomType() != null)
-                .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
-                .map(entry -> new BlockInfo(entry.getKey(), null))
-                .toArray(BlockInfo[]::new))
-                        .addTooltips("gregtech.multiblock.pattern.error.filters");
+        return new TraceabilityPredicate(
+                (worldState, patternState) -> GregTechAPI.CLEANROOM_FILTERS.containsKey(worldState.getBlockState()),
+                () -> GregTechAPI.CLEANROOM_FILTERS.entrySet().stream()
+                        .filter(entry -> entry.getValue().getCleanroomType() != null)
+                        .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
+                        .map(entry -> new BlockInfo(entry.getKey(), null))
+                        .toArray(BlockInfo[]::new))
+                                .addTooltips("gregtech.multiblock.pattern.error.filters");
     }
 
     @SideOnly(Side.CLIENT)
@@ -360,7 +347,7 @@ protected void handleDisplayClick(String componentData, Widget.ClickData clickDa
             case "addl" -> bounds[2] += 1;
         }
 
-        getSubstructure("MAIN").getPattern().clearCache();
+        getSubstructure("MAIN").clearCache();
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
index bf8c4b90b61..f546a752c0a 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
@@ -1,5 +1,6 @@
 package gregtech.common.metatileentities.multi.electric;
 
+import gregtech.api.GregTechAPI;
 import gregtech.api.block.IHeatingCoilBlockStats;
 import gregtech.api.capability.impl.MultiblockRecipeLogic;
 import gregtech.api.metatileentity.MetaTileEntity;
@@ -7,7 +8,6 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
@@ -122,19 +122,19 @@ protected ICubeRenderer getFrontOverlay() {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
-        Object type = context.get("CoilType");
-        if (type instanceof IHeatingCoilBlockStats) {
-            this.coilTier = ((IHeatingCoilBlockStats) type).getTier();
+    protected void formStructure(String name) {
+        super.formStructure(name);
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name).getCache());
+        if (type == null) {
+            invalidateStructure(name);
         } else {
-            this.coilTier = 0;
+            this.coilTier = type.getTier();
         }
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         this.coilTier = -1;
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
index 4d4c1e0fcbd..b65bb4cad13 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
@@ -9,7 +9,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.util.TextFormattingUtil;
@@ -67,8 +66,8 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         this.energyContainer = new EnergyContainerList(getAbilities(MultiblockAbility.INPUT_ENERGY));
         this.energyUsage = calculateEnergyUsage();
     }
@@ -84,8 +83,8 @@ protected int calculateEnergyUsage() {
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         this.energyContainer = new EnergyContainerList(new ArrayList<>());
         this.energyUsage = 0;
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
index ccc99757a49..79c68ed7d83 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
@@ -9,7 +9,6 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;
@@ -103,16 +102,16 @@ protected void addDisplayText(List textList) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         if (this.handler == null) return;
-        handler.determineLayerCount((BlockPattern) getSubstructure("MAIN").getPattern());
+        handler.determineLayerCount((BlockPattern) getSubstructure("MAIN"));
         handler.determineOrderedFluidOutputs();
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         if (this.handler != null) handler.invalidate();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index ef63b307dd6..4fe649f7669 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -12,7 +12,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
 import gregtech.api.pattern.MultiblockShapeInfo;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;
@@ -25,7 +24,6 @@
 import gregtech.client.renderer.texture.Textures;
 import gregtech.common.ConfigHolder;
 import gregtech.common.blocks.BlockMetalCasing.MetalCasingType;
-import gregtech.common.blocks.BlockWireCoil.CoilType;
 import gregtech.common.blocks.MetaBlocks;
 import gregtech.common.metatileentities.MetaTileEntities;
 import gregtech.core.sound.GTSoundEvents;
@@ -93,13 +91,13 @@ protected void addDisplayText(List textList) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
-        Object type = context.get("CoilType");
-        if (type instanceof IHeatingCoilBlockStats) {
-            this.blastFurnaceTemperature = ((IHeatingCoilBlockStats) type).getCoilTemperature();
+    protected void formStructure(String name) {
+        super.formStructure(name);
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name).getCache());
+        if (type == null) {
+            invalidateStructure(name);
         } else {
-            this.blastFurnaceTemperature = CoilType.CUPRONICKEL.getCoilTemperature();
+            this.blastFurnaceTemperature = type.getCoilTemperature();
         }
         // the subtracted tier gives the starting level (exclusive) of the +100K heat bonus
         this.blastFurnaceTemperature += 100 *
@@ -107,8 +105,8 @@ protected void formStructure(PatternMatchContext context) {
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         this.blastFurnaceTemperature = 0;
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
index d6cebbe388f..69ff9d5cb0b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
@@ -14,7 +14,6 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
@@ -89,14 +88,14 @@ private void resetTileAbilities() {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         initializeAbilities();
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         resetTileAbilities();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
index 708c75e8935..f4a847434ed 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
@@ -24,7 +24,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
 import gregtech.api.pattern.MultiblockShapeInfo;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;
@@ -249,16 +248,16 @@ protected void setFusionRingColor(int fusionRingColor) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
+    protected void formStructure(String name) {
         long energyStored = this.energyContainer.getEnergyStored();
-        super.formStructure(context);
+        super.formStructure(name);
         this.initializeAbilities();
         ((EnergyContainerHandler) this.energyContainer).setEnergyStored(energyStored);
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         this.energyContainer = new EnergyContainerHandler(this, 0, 0, 0, 0, 0) {
 
             @NotNull
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
index 13dbea27fc9..73e9082614d 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
@@ -14,7 +14,6 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
 import gregtech.api.pattern.MultiblockShapeInfo;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.unification.material.Materials;
@@ -97,16 +96,16 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         this.energyContainer = new EnergyContainerList(getAbilities(MultiblockAbility.INPUT_ENERGY));
         this.coolantHandler = new FluidTankList(false, getAbilities(MultiblockAbility.IMPORT_FLUIDS));
         this.hpcaHandler.onStructureForm(getAbilities(MultiblockAbility.HPCA_COMPONENT));
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         this.energyContainer = new EnergyContainerList(new ArrayList<>());
         this.hpcaHandler.onStructureInvalidate();
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
index 24d160295a5..a3996dfdd4b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
@@ -19,7 +19,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
@@ -105,16 +104,16 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         resetTileAbilities();
         if (this.minerLogic.isActive())
             this.minerLogic.setActive(false);
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         initializeAbilities();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
index ae3f90344e4..4c3619ce8d8 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
@@ -1,5 +1,6 @@
 package gregtech.common.metatileentities.multi.electric;
 
+import gregtech.api.GregTechAPI;
 import gregtech.api.block.IHeatingCoilBlockStats;
 import gregtech.api.capability.impl.MultiblockRecipeLogic;
 import gregtech.api.metatileentity.MetaTileEntity;
@@ -9,7 +10,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.ParallelLogicType;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeBuilder;
@@ -24,7 +24,6 @@
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.common.blocks.BlockMetalCasing.MetalCasingType;
-import gregtech.common.blocks.BlockWireCoil.CoilType;
 import gregtech.common.blocks.MetaBlocks;
 import gregtech.core.sound.GTSoundEvents;
 
@@ -105,21 +104,20 @@ protected void addDisplayText(List textList) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
-        Object coilType = context.get("CoilType");
-        if (coilType instanceof IHeatingCoilBlockStats) {
-            this.heatingCoilLevel = ((IHeatingCoilBlockStats) coilType).getLevel();
-            this.heatingCoilDiscount = ((IHeatingCoilBlockStats) coilType).getEnergyDiscount();
+    protected void formStructure(String name) {
+        super.formStructure(name);
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name).getCache());
+        if (type == null) {
+            invalidateStructure(name);
         } else {
-            this.heatingCoilLevel = CoilType.CUPRONICKEL.getLevel();
-            this.heatingCoilDiscount = CoilType.CUPRONICKEL.getEnergyDiscount();
+            this.heatingCoilLevel = type.getLevel();
+            this.heatingCoilDiscount = type.getEnergyDiscount();
         }
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         this.heatingCoilLevel = 0;
         this.heatingCoilDiscount = 0;
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
index 88b95997957..6c318bb429b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
@@ -9,7 +9,6 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.util.TextComponentUtil;
@@ -61,16 +60,16 @@ protected int calculateEnergyUsage() {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         computationHandler.onStructureForm(
                 getAbilities(MultiblockAbility.COMPUTATION_DATA_RECEPTION),
                 getAbilities(MultiblockAbility.COMPUTATION_DATA_TRANSMISSION));
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         computationHandler.reset();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index e43a0ccb8cb..ec8f49af778 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -98,8 +98,8 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         List inputs = new ArrayList<>();
         inputs.addAll(getAbilities(MultiblockAbility.INPUT_ENERGY));
         inputs.addAll(getAbilities(MultiblockAbility.SUBSTATION_INPUT_ENERGY));
@@ -112,18 +112,11 @@ protected void formStructure(PatternMatchContext context) {
         outputs.addAll(getAbilities(MultiblockAbility.OUTPUT_LASER));
         this.outputHatches = new EnergyContainerList(outputs);
 
-        List parts = new ArrayList<>();
-        for (Map.Entry battery : context.entrySet()) {
-            if (battery.getKey().startsWith(PMC_BATTERY_HEADER) &&
-                    battery.getValue() instanceof BatteryMatchWrapper wrapper) {
-                for (int i = 0; i < wrapper.amount; i++) {
-                    parts.add(wrapper.partType);
-                }
-            }
-        }
+        List parts = determineBatteryParts();
+
         if (parts.isEmpty()) {
             // only empty batteries found in the structure
-            invalidateStructure();
+            invalidateStructure("MAIN");
             return;
         }
         if (this.energyBank == null) {
@@ -134,8 +127,18 @@ protected void formStructure(PatternMatchContext context) {
         this.passiveDrain = this.energyBank.getPassiveDrainPerTick();
     }
 
+    protected List determineBatteryParts() {
+        List data = new ArrayList<>();
+        for (BlockInfo info : getSubstructure("MAIN").getCache().values()) {
+            if (GregTechAPI.PSS_BATTERIES.containsKey(info.getBlockState())) {
+                data.add(GregTechAPI.PSS_BATTERIES.get(info.getBlockState()));
+            }
+        }
+        return data;
+    }
+
     @Override
-    public void invalidateStructure() {
+    public void invalidateStructure(String name) {
         // don't null out energyBank since it holds the stored energy, which
         // we need to hold on to across rebuilds to not void all energy if a
         // multiblock part or block other than the controller is broken.
@@ -146,7 +149,7 @@ public void invalidateStructure() {
         averageInLastSec = 0;
         netOutLastSec = 0;
         averageOutLastSec = 0;
-        super.invalidateStructure();
+        super.invalidateStructure(name);
     }
 
     @Override
@@ -286,22 +289,8 @@ protected IBlockState getGlassState() {
     }
 
     protected static final Supplier BATTERY_PREDICATE = () -> new TraceabilityPredicate(
-            (blockWorldState, info) -> {
-                IBlockState state = blockWorldState.getBlockState();
-                if (GregTechAPI.PSS_BATTERIES.containsKey(state)) {
-                    IBatteryData battery = GregTechAPI.PSS_BATTERIES.get(state);
-                    // Allow unfilled batteries in the structure, but do not add them to match context.
-                    // This lets you use empty batteries as "filler slots" for convenience if desired.
-                    if (battery.getTier() != -1 && battery.getCapacity() > 0) {
-                        String key = PMC_BATTERY_HEADER + battery.getBatteryName();
-                        BatteryMatchWrapper wrapper = info.getContext().get(key);
-                        if (wrapper == null) wrapper = new BatteryMatchWrapper(battery);
-                        info.getContext().set(key, wrapper.increment());
-                    }
-                    return true;
-                }
-                return false;
-            }, () -> GregTechAPI.PSS_BATTERIES.entrySet().stream()
+            (worldState, patternState) -> GregTechAPI.PSS_BATTERIES.containsKey(worldState.getBlockState()),
+            () -> GregTechAPI.PSS_BATTERIES.entrySet().stream()
                     .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
                     .map(entry -> new BlockInfo(entry.getKey(), null))
                     .toArray(BlockInfo[]::new))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
index a7964e6efde..faa3847ff5f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
@@ -78,8 +78,8 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         ((ProcessingArrayWorkable) this.recipeMapWorkable).findMachineStack();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
index 7df70856730..09426c6b35e 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
@@ -1,5 +1,6 @@
 package gregtech.common.metatileentities.multi.electric;
 
+import gregtech.api.GregTechAPI;
 import gregtech.api.block.IHeatingCoilBlockStats;
 import gregtech.api.capability.impl.MultiblockRecipeLogic;
 import gregtech.api.metatileentity.MetaTileEntity;
@@ -7,7 +8,6 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
@@ -88,13 +88,14 @@ protected ICubeRenderer getFrontOverlay() {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
-        Object type = context.get("CoilType");
-        if (type instanceof IHeatingCoilBlockStats)
-            this.coilTier = ((IHeatingCoilBlockStats) type).getTier();
-        else
-            this.coilTier = 0;
+    protected void formStructure(String name) {
+        super.formStructure(name);
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name).getCache());
+        if (type == null) {
+            invalidateStructure(name);
+        } else {
+            this.coilTier = type.getTier();
+        }
     }
 
     @Override
@@ -146,8 +147,8 @@ public void addInformation(ItemStack stack, @Nullable World player, List
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         this.coilTier = -1;
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
index b04481adfae..84c199a9115 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
@@ -14,7 +14,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
 import gregtech.api.pattern.MultiblockShapeInfo;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;
@@ -64,8 +63,8 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         List providers = getAbilities(MultiblockAbility.COMPUTATION_DATA_RECEPTION);
         if (providers != null && providers.size() >= 1) {
             computationProvider = providers.get(0);
@@ -79,7 +78,7 @@ protected void formStructure(PatternMatchContext context) {
 
         // should never happen, but would rather do this than have an obscure NPE
         if (computationProvider == null || objectHolder == null) {
-            invalidateStructure();
+            invalidateStructure("MAIN");
         }
     }
 
@@ -88,7 +87,7 @@ protected void formStructure(PatternMatchContext context) {
     public void checkStructurePattern() {
         super.checkStructurePattern();
         if (isStructureFormed() && objectHolder.getFrontFacing() != getFrontFacing().getOpposite()) {
-            invalidateStructure();
+            invalidateStructure("MAIN");
         }
     }
 
@@ -98,7 +97,7 @@ public ComputationRecipeLogic getRecipeMapWorkable() {
     }
 
     @Override
-    public void invalidateStructure() {
+    public void invalidateStructure(String name) {
         computationProvider = null;
         // recheck the ability to make sure it wasn't the one broken
         List holders = getAbilities(MultiblockAbility.OBJECT_HOLDER);
@@ -106,7 +105,7 @@ public void invalidateStructure() {
             objectHolder.setLocked(false);
         }
         objectHolder = null;
-        super.invalidateStructure();
+        super.invalidateStructure(name);
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
index fe68cfc06d0..02aefada883 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
@@ -7,7 +7,6 @@
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.pattern.PatternInfo;
 import gregtech.api.recipes.RecipeMaps;
 import gregtech.api.util.RelativeDirection;
 import gregtech.client.renderer.ICubeRenderer;
@@ -30,6 +29,7 @@
 
 import static gregtech.api.gui.widgets.AdvancedTextWidget.withButton;
 
+// todo fix this stupid multiblock back to its original form
 public class MetaTileEntityVacuumFreezer extends RecipeMapMultiblockController {
 
     public MetaTileEntityVacuumFreezer(ResourceLocation metaTileEntityId) {
@@ -56,11 +56,11 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     protected void createStructurePatterns() {
         super.createStructurePatterns();
-        structures.put("SECOND", new PatternInfo(FactoryBlockPattern.start()
+        structures.put("SECOND", FactoryBlockPattern.start()
                 .aisle("X")
                 .where('X', states(getCasingState()))
                 .startOffset(RelativeDirection.FRONT, 5)
-                .build()));
+                .build());
     }
 
     @Override
@@ -68,22 +68,23 @@ protected void addDisplayText(List textList) {
         super.addDisplayText(textList);
 
         ITextComponent button = new TextComponentString("Second structure offset: " +
-                ((BlockPattern) getSubstructure("SECOND").getPattern()).getStartOffset(RelativeDirection.FRONT));
+                ((BlockPattern) getSubstructure("SECOND")).getStartOffset(RelativeDirection.FRONT));
         button.appendText(" ");
         button.appendSibling(withButton(new TextComponentString("[-]"), "sub"));
         button.appendText(" ");
         button.appendSibling(withButton(new TextComponentString("[+]"), "add"));
         textList.add(button);
 
-        textList.add(new TextComponentString("Second structure: " + (isStructureFormed("SECOND") ? "FORMED" : "UNFORMED")));
+        textList.add(
+                new TextComponentString("Second structure: " + (isStructureFormed("SECOND") ? "FORMED" : "UNFORMED")));
     }
 
     @Override
     protected void handleDisplayClick(String componentData, Widget.ClickData clickData) {
         super.handleDisplayClick(componentData, clickData);
         int mod = componentData.equals("add") ? 1 : -1;
-        ((BlockPattern) getSubstructure("SECOND").getPattern()).moveStartOffset(RelativeDirection.FRONT, mod);
-        getSubstructure("SECOND").getPattern().clearCache();
+        // ((BlockPattern) getSubstructure("SECOND").moveStartOffset(RelativeDirection.FRONT, mod);
+        // getSubstructure("SECOND").clearCache();
     }
 
     @SideOnly(Side.CLIENT)
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index 5ed7b1afc1e..a71972b8326 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -16,7 +16,6 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pipenet.tile.IPipeTile;
@@ -424,8 +423,8 @@ public String[] getDescription() {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         lastUpdate = 0;
         currentEnergyNet = new WeakReference<>(null);
         activeNodes = new ArrayList<>();
@@ -533,7 +532,7 @@ public void renderMetaTileEntity(double x, double y, double z, float partialTick
                                 .getMetaTileEntity() instanceof MetaTileEntityMonitorScreen) {
                             MetaTileEntityMonitorScreen screen = (MetaTileEntityMonitorScreen) ((IGregTechTileEntity) tileEntity)
                                     .getMetaTileEntity();
-                            screen.addToMultiBlock(this);
+                            screen.addToMultiBlock(this, "MAIN");
                             int sx = screen.getX(), sy = screen.getY();
                             if (sx < 0 || sx >= width || sy < 0 || sy >= height) {
                                 parts.clear();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
index 34148075986..6bd0d7bb4d0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
@@ -16,7 +16,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
index 5a08134d673..a408ded4cc4 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
@@ -10,7 +10,6 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMap;
@@ -78,8 +77,8 @@ public IRotorHolder getRotorHolder() {
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         this.exportFluidHandler = null;
     }
 
@@ -96,8 +95,8 @@ public boolean isRotorFaceFree() {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         this.exportFluidHandler = new FluidTankList(true, getAbilities(MultiblockAbility.EXPORT_FLUIDS));
         ((LargeTurbineWorkableHandler) this.recipeMapWorkable).updateTanks();
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java
index 70886d69950..81bd9e3a2ba 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java
@@ -5,7 +5,6 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.*;
 import gregtech.api.util.GTUtility;
-import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
 
 import net.minecraft.client.resources.I18n;
@@ -50,8 +49,8 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        super.addToMultiBlock(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
+        super.addToMultiBlock(controllerBase, name);
         if (controllerBase instanceof ICleanroomReceiver &&
                 ((ICleanroomReceiver) controllerBase).getCleanroom() == null) {
             ((ICleanroomReceiver) controllerBase).setCleanroom(DUMMY_CLEANROOM);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java
index 7ced210904c..91549e65b93 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java
@@ -218,8 +218,8 @@ public void registerAbilities(List abilityList) {
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
         rebuildData(controllerBase instanceof MetaTileEntityDataBank);
-        super.addToMultiBlock(controllerBase);
+        super.addToMultiBlock(controllerBase, name);
     }
 }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
index f44244f2079..831d21f5204 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityItemBus.java
@@ -97,8 +97,8 @@ public IItemHandlerModifiable getImportItems() {
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        super.addToMultiBlock(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
+        super.addToMultiBlock(controllerBase, name);
         if (hasGhostCircuitInventory() && this.actualImportItems instanceof ItemHandlerList) {
             for (IItemHandler handler : ((ItemHandlerList) this.actualImportItems).getBackingHandlers()) {
                 if (handler instanceof INotifiableHandler notifiable) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockNotifiablePart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockNotifiablePart.java
index f504d895c3b..e1b49d4ddfe 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockNotifiablePart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockNotifiablePart.java
@@ -8,6 +8,7 @@
 
 import net.minecraft.util.ResourceLocation;
 import net.minecraftforge.fluids.IFluidTank;
+
 import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
@@ -69,8 +70,8 @@ private List getPartHandlers() {
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        super.addToMultiBlock(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
+        super.addToMultiBlock(controllerBase, name);
         List handlerList = getPartHandlers();
         for (INotifiableHandler handler : handlerList) {
             handler.addNotifiableMetaTileEntity(controllerBase);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
index 58418f28c88..0d57ede47e7 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
@@ -29,6 +29,7 @@
 import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import static gregtech.api.capability.GregtechDataCodes.SYNC_CONTROLLER;
@@ -78,6 +79,15 @@ public int getTier() {
         return tier;
     }
 
+    /**
+     * Returns the last controller, which is the one that controls the texture of the part. If the world is null, return
+     * null.
+     * If this is called client side, the controller is fetched if not already and returned, this can lead to null. If
+     * this is called on server,
+     * returns the last controller if it is valid, or null if it is not. Note that calling this multiple times in
+     * succession can
+     * have different results, due to invalid controllers being removed after detected in this method.
+     */
     @Nullable
     public MultiblockControllerBase getController() {
         tryInitControllers();
@@ -91,7 +101,8 @@ public MultiblockControllerBase getController() {
         if (getWorld().isRemote) { // client check, on client controllers is always empty
             if (lastController == null) {
                 if (controllerPos != null) {
-                    this.lastController = (MultiblockControllerBase) GTUtility.getMetaTileEntity(getWorld(), controllerPos);
+                    this.lastController = (MultiblockControllerBase) GTUtility.getMetaTileEntity(getWorld(),
+                            controllerPos);
                 }
             } else if (!lastController.isValid()) {
                 this.lastController = null;
@@ -106,11 +117,55 @@ public MultiblockControllerBase getController() {
 
         if (!controller.isValid()) {
             removeController(controller);
+            return null;
         }
 
         return controller;
     }
 
+    /**
+     * Gets a list of all the controllers owning the part. Calling this when the world is null returns an empty list.
+     * Calling this on client side is unsupported(as the client has no knowledge of this) and logs an error(and returns
+     * an empty list).
+     * Calling this on server side returns the controllers list, with all invalid controllers removed.
+     */
+    @NotNull
+    public List getControllers() {
+        tryInitControllers();
+
+        if (getWorld() == null) {
+            this.controllers.clear();
+            lastController = null;
+            return Collections.emptyList();
+        }
+
+        if (getWorld().isRemote) {
+            GTLog.logger.error("getControllers() was called on client side on " + getClass().getName() +
+                    ", the author probably intended to use getController()! Ignoring and returning empty list.");
+            return Collections.emptyList();
+        }
+
+        // empty list check
+        if (controllers.isEmpty()) return controllers;
+
+        // last controller in list
+        MultiblockControllerBase last = controllers.get(controllers.size() - 1);
+
+        // remove all invalid controllers
+        controllers.removeIf(controller -> !controller.isValid());
+
+        // check again
+        if (controllers.isEmpty()) {
+            syncLastController();
+            return controllers;
+        }
+
+        // only sync if last controller changed
+        if (last != controllers.get(controllers.size() - 1)) syncLastController();
+
+        return controllers;
+    }
+
     public ICubeRenderer getBaseTexture() {
         MultiblockControllerBase controller = getController();
         if (controller != null) {
@@ -169,7 +224,9 @@ private void addController(@NotNull MultiblockControllerBase controller) {
 
         // this should be called after canPartShare has already checked the class, just a safeguard
         if (controllerClass != null && controller.getClass() != controllerClass) {
-            GTLog.logger.error("addController(MultiblockControllerBase) was called on " + getClass().getName() + " with a mismatched name(original: " + controllerClass.getName() + " new: " + controller.getClass().getName() +")! Ignoring the call.");
+            GTLog.logger.error("addContr oller(MultiblockControllerBase) was called on " + getClass().getName() +
+                    " with a mismatched class(original: " + controllerClass.getName() + ", new: " +
+                    controller.getClass().getName() + ")! Ignoring the call.");
             return;
         }
 
@@ -177,21 +234,32 @@ private void addController(@NotNull MultiblockControllerBase controller) {
 
         this.controllers.add(controller);
 
+        // controllers always add at end of list, so always sync
         syncLastController();
     }
 
     private void removeController(@NotNull MultiblockControllerBase controller) {
         tryInitControllers();
 
-        if (!controllers.remove(controller)) {
-            GTLog.logger.error("removeController(MultiblockControllerBase) was called on " + getClass().getName() + " while the given controller wasn't in the list!");
+        int index = controllers.indexOf(controller);
+
+        if (index == -1) {
+            GTLog.logger.error("removeController(MultiblockControllerBase) was called on " + getClass().getName() +
+                    " while the given controller wasn't in the list!");
+            return;
+        }
+
+        controllers.remove(index);
+
+        // if the last controller changed, sync it
+        if (index == (controllers.size() - 1)) {
+            syncLastController();
         }
 
         if (controllers.isEmpty()) {
             controllerClass = null;
+            attachedSubstructureName = null;
         }
-
-        syncLastController();
     }
 
     private void syncLastController() {
@@ -210,14 +278,25 @@ private void syncLastController() {
     @Override
     public void onRemoval() {
         super.onRemoval();
-        MultiblockControllerBase controller = getController();
-        if (!getWorld().isRemote && controller != null) {
-            controller.invalidateStructure();
+        if (getWorld().isRemote) return;
+
+        for (MultiblockControllerBase controller : getControllers()) {
+            controller.invalidateStructure("MAIN");
         }
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String substructureName) {
+        // canPartShare() should
+        if (!substructureName.equals(attachedSubstructureName) && attachedSubstructureName != null) {
+            GTLog.logger.error("addToMultiBlock(MultiblockControllerBase, String) was called on " +
+                    getClass().getName() + " with a mismatched name(original: " + attachedSubstructureName + ", new: " +
+                    substructureName + ")! Ignoring the call.");
+            return;
+        }
+
+        attachedSubstructureName = substructureName;
+
         addController(controllerBase);
         scheduleRenderUpdate();
         wallshareCount++;
@@ -240,7 +319,8 @@ public boolean canPartShare(MultiblockControllerBase target, String substructure
         // when this is called normally isAttachedToMultiBlock has already been called and returned true
         // so we know controllerClass is notnull
 
-        return canPartShare() && target.getClass() == controllerClass && substructureName.equals(attachedSubstructureName);
+        return canPartShare() && target.getClass() == controllerClass &&
+                (substructureName.equals(attachedSubstructureName) || attachedSubstructureName == null);
     }
 
     @Override
@@ -248,6 +328,11 @@ public int getWallshareCount() {
         return wallshareCount;
     }
 
+    @Override
+    public String getSubstructureName() {
+        return attachedSubstructureName;
+    }
+
     @Override
     public boolean isAttachedToMultiBlock() {
         return controllers != null && !controllers.isEmpty();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
index 336a2d4b9d8..b0827af76d1 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
@@ -197,8 +197,8 @@ protected boolean shouldSerializeInventories() {
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        super.addToMultiBlock(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
+        super.addToMultiBlock(controllerBase, name);
         heldItems.addNotifiableMetaTileEntity(controllerBase);
         heldItems.addToNotifiedList(this, heldItems, false);
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java
index cdf205cb170..c8425c35c41 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEInputBus.java
@@ -161,8 +161,8 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity iGregTechTileEnti
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        super.addToMultiBlock(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
+        super.addToMultiBlock(controllerBase, name);
         for (IItemHandler handler : this.actualImportItems.getBackingHandlers()) {
             if (handler instanceof INotifiableHandler notifiable) {
                 notifiable.addNotifiableMetaTileEntity(controllerBase);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java
index 126e835bbe9..d2b4a589fd8 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputBus.java
@@ -201,8 +201,8 @@ public void registerAbilities(List abilityList) {
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        super.addToMultiBlock(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
+        super.addToMultiBlock(controllerBase, name);
         if (controllerBase instanceof MultiblockWithDisplayBase) {
             ((MultiblockWithDisplayBase) controllerBase).enableItemInfSink();
         }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java
index 7bd88726ac8..b8871b48235 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEOutputHatch.java
@@ -203,8 +203,8 @@ public void registerAbilities(List list) {
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        super.addToMultiBlock(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
+        super.addToMultiBlock(controllerBase, name);
         if (controllerBase instanceof MultiblockWithDisplayBase) {
             ((MultiblockWithDisplayBase) controllerBase).enableFluidInfSink();
         }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingBus.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingBus.java
index fc840e0d57d..18fa48c9d09 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingBus.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingBus.java
@@ -101,8 +101,8 @@ protected void flushInventory() {
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        super.addToMultiBlock(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
+        super.addToMultiBlock(controllerBase, name);
         // ensure that no other stocking bus on this multiblock is configured to hold the same item.
         // that we have in our own bus.
         this.autoPullTest = stack -> !this.testConfiguredInOtherBus(stack);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingHatch.java
index b69ae0ba8ef..47a713215f2 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/appeng/MetaTileEntityMEStockingHatch.java
@@ -99,8 +99,8 @@ protected void flushInventory() {
     }
 
     @Override
-    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        super.addToMultiBlock(controllerBase);
+    public void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String name) {
+        super.addToMultiBlock(controllerBase, name);
         this.autoPullTest = stack -> !this.testConfiguredInOtherHatch(stack);
         validateConfig();
     }
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index da0e63021bf..0dd2d7b39a9 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -105,16 +105,16 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
     }
 
     @Override
-    public void invalidateStructure() {
-        super.invalidateStructure();
+    public void invalidateStructure(String name) {
+        super.invalidateStructure(name);
         setActive(false);
         this.progressTime = 0;
         this.maxProgress = 0;
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         // calculate the duration upon formation
         updateMaxProgressTime();
     }
@@ -258,7 +258,7 @@ private boolean updateStructureDimensions() {
         }
 
         if (lDist < MIN_RADIUS || rDist < MIN_RADIUS || hDist < MIN_DEPTH) {
-            invalidateStructure();
+            invalidateStructure("MAIN");
             return false;
         }
 
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index dfb837cbda9..ee0a1de18a1 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -4,10 +4,7 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.MultiblockShapeInfo;
-import gregtech.api.pattern.PatternMatchContext;
-import gregtech.api.pattern.StructureInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTUtility;
@@ -353,27 +350,27 @@ public void drawInfo(@NotNull Minecraft minecraft, int recipeWidth, int recipeHe
             TraceabilityPredicate predicates = patterns[currentRendererPage].predicateMap
                     .get(rayTraceResult.getBlockPos());
             if (predicates != null) {
-
-                StructureInfo info = new StructureInfo(new PatternMatchContext(), null);
-                BlockWorldState worldState = new BlockWorldState(info);
-
-                worldState.setWorld(renderer.world);
-                worldState.setPos(rayTraceResult.getBlockPos());
-
-                for (TraceabilityPredicate.SimplePredicate common : predicates.common) {
-                    if (common.test(worldState, info)) {
-                        predicateTips = common.getToolTips(predicates);
-                        break;
-                    }
-                }
-                if (predicateTips == null) {
-                    for (TraceabilityPredicate.SimplePredicate limit : predicates.limited) {
-                        if (limit.test(worldState, info)) {
-                            predicateTips = limit.getToolTips(predicates);
-                            break;
-                        }
-                    }
-                }
+                //
+                // StructureInfo info = new StructureInfo(new PatternMatchContext(), null);
+                // BlockWorldState worldState = new BlockWorldState(info);
+                //
+                // worldState.setWorld(renderer.world);
+                // worldState.setPos(rayTraceResult.getBlockPos());
+                //
+                // for (TraceabilityPredicate.SimplePredicate common : predicates.common) {
+                // if (common.test(worldState, info)) {
+                // predicateTips = common.getToolTips(predicates);
+                // break;
+                // }
+                // }
+                // if (predicateTips == null) {
+                // for (TraceabilityPredicate.SimplePredicate limit : predicates.limited) {
+                // if (limit.test(worldState, info)) {
+                // predicateTips = limit.getToolTips(predicates);
+                // break;
+                // }
+                // }
+                // }
             }
             if (!itemStack.isEmpty()) {
                 tooltipBlockStack = itemStack;

From 8f7f1c130688be303ce3d20d13f0088aac6eb1e4 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Fri, 2 Aug 2024 22:11:17 -0700
Subject: [PATCH 22/64] misc stuff

---
 .../multiblock/MultiblockControllerBase.java               | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 9436f5ea51d..c24e93dd8a9 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -141,10 +141,6 @@ public void update() {
     @NotNull
     protected abstract IBlockPattern createStructurePattern();
 
-    /**
-     * Populate the structurePatterns array with structure patterns, values can be null. Doing this prevents
-     * frequent new BlockPatterns from being made.
-     */
     protected void createStructurePatterns() {
         structures.put("MAIN", createStructurePattern());
     }
@@ -463,7 +459,7 @@ public void checkStructurePattern(String name) {
     /**
      * Perform an action for each multiblock part in the substructure. This uses the pattern's cache, which is always
      * accurate if the structure is valid(and has undefined behavior(probably empty) if not). Using the cache means
-     * you can clear the multi's multiblock parts during this without causing a CME(which would happen if this iteratoes
+     * you can clear the multi's multiblock parts during this without causing a CME(which would happen if this iterates
      * over multiblockParts instead)
      * 
      * @param name   The name of the substructure.
@@ -526,6 +522,7 @@ public void invalidateStructure() {
     }
 
     public void invalidateStructure(String name) {
+        if (!getSubstructure(name).getPatternState().isFormed()) return;
         // invalidate the main structure
         if ("MAIN".equals(name)) {
             this.multiblockParts.forEach(part -> part.removeFromMultiBlock(this));

From fc20c37ede38126654e224e1ee7b46cb2f4f844f Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 11 Aug 2024 14:38:05 -0700
Subject: [PATCH 23/64] reversed patterns

---
 .../multiblock/MultiblockControllerBase.java  | 27 +++++---
 .../gregtech/api/pattern/OriginOffset.java    | 37 +++++++++++
 .../api/pattern/pattern/BlockPattern.java     | 61 ++++++-------------
 .../pattern/pattern/ExpandablePattern.java    |  7 +++
 .../pattern/pattern/FactoryBlockPattern.java  | 50 ++++++---------
 .../api/pattern/pattern/IBlockPattern.java    | 12 ++++
 .../api/pattern/pattern/PatternState.java     |  7 +--
 .../multi/MetaTileEntityCokeOven.java         |  4 +-
 .../multi/MetaTileEntityLargeBoiler.java      |  4 +-
 .../multi/MetaTileEntityMultiblockTank.java   |  4 +-
 .../MetaTileEntityPrimitiveBlastFurnace.java  |  4 +-
 .../MetaTileEntityPrimitiveWaterPump.java     |  6 +-
 .../MetaTileEntityActiveTransformer.java      |  4 +-
 .../electric/MetaTileEntityAssemblyLine.java  |  8 +--
 .../electric/MetaTileEntityCrackingUnit.java  |  4 +-
 .../electric/MetaTileEntityDataBank.java      |  4 +-
 .../MetaTileEntityDistillationTower.java      |  4 +-
 .../MetaTileEntityElectricBlastFurnace.java   |  4 +-
 .../electric/MetaTileEntityFluidDrill.java    |  4 +-
 .../electric/MetaTileEntityFusionReactor.java |  4 +-
 .../multi/electric/MetaTileEntityHPCA.java    |  8 +--
 .../MetaTileEntityImplosionCompressor.java    |  4 +-
 .../MetaTileEntityLargeChemicalReactor.java   |  4 +-
 .../electric/MetaTileEntityLargeMiner.java    |  4 +-
 .../electric/MetaTileEntityMultiSmelter.java  |  4 +-
 .../electric/MetaTileEntityNetworkSwitch.java |  4 +-
 .../MetaTileEntityPowerSubstation.java        |  4 +-
 .../MetaTileEntityProcessingArray.java        |  4 +-
 .../electric/MetaTileEntityPyrolyseOven.java  |  4 +-
 .../MetaTileEntityResearchStation.java        | 12 ++--
 .../electric/MetaTileEntityVacuumFreezer.java | 13 ++--
 .../MetaTileEntityLargeCombustionEngine.java  |  4 +-
 .../generator/MetaTileEntityLargeTurbine.java |  4 +-
 .../steam/MetaTileEntitySteamGrinder.java     |  4 +-
 .../multi/steam/MetaTileEntitySteamOven.java  |  4 +-
 35 files changed, 181 insertions(+), 159 deletions(-)
 create mode 100644 src/main/java/gregtech/api/pattern/OriginOffset.java

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index c24e93dd8a9..952d086b48f 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -73,6 +73,7 @@
 import java.util.NavigableSet;
 import java.util.Objects;
 import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -138,6 +139,7 @@ public void update() {
     /**
      * @return structure pattern of this multiblock
      */
+    // todo fix central monitor, charcoal pile igniter, and cleanroom(and vacuum freezer)
     @NotNull
     protected abstract IBlockPattern createStructurePattern();
 
@@ -419,7 +421,7 @@ public void checkStructurePattern(String name) {
                         if (part.isAttachedToMultiBlock() && !part.canPartShare(this, name)) {
                             invalidateStructure(name);
                             return false;
-                        } ;
+                        }
                         if (multiblockParts.add(part)) {
                             part.addToMultiBlock(this, name);
                         }
@@ -434,14 +436,24 @@ public void checkStructurePattern(String name) {
                 return;
             }
 
-            // normal rebuild parts
+            AtomicBoolean valid = new AtomicBoolean(true);
+
             forEachMultiblockPart(name, part -> {
-                // structure is already invalidated at this point so don't bother
                 if (part.isAttachedToMultiBlock() && !part.canPartShare(this, name)) {
+                    valid.set(false);
                     return false;
                 }
+                return true;
+            });
+
+            // since the structure isn't formed, don't invalidate, instead just don't form it
+            if (!valid.get()) return;
+
+            // normal rebuild parts
+            forEachMultiblockPart(name, part -> {
                 // parts *should* not have this controller added
                 multiblockParts.add(part);
+                part.addToMultiBlock(this, name);
                 if (part instanceof IMultiblockAbilityPartabilityPart) {
                     // noinspection unchecked
                     registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
@@ -470,7 +482,7 @@ protected void forEachMultiblockPart(String name, Predicate act
         Long2ObjectMap cache = getSubstructure(name).getCache();
         for (BlockInfo info : cache.values()) {
             TileEntity te = info.getTileEntity();
-            if (!(te instanceof IGregTechTileEntity gtte)) return;
+            if (!(te instanceof IGregTechTileEntity gtte)) continue;
             MetaTileEntity mte = gtte.getMetaTileEntity();
             if (mte instanceof IMultiblockPart part) {
                 if (!action.test(part)) return;
@@ -493,14 +505,13 @@ protected void registerMultiblockAbility(IMultiblockAbilityPart part) {
         part.registerAbilities(abilityList);
     }
 
+    // todo do
     protected void forEachFormed(GreggyBlockPos pos) {
         IBlockState state = getWorld().getBlockState(pos.immutable());
     }
 
     protected void formStructure(String name) {
-        // form the main structure
-        if ("MAIN".equals(name)) formStructure();
-
+        getSubstructure(name).getPatternState().setFormed(true);
         setFlipped(getSubstructure(name).getPatternState().isFlipped(), name);
         writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString(name).writeBoolean(true));
     }
@@ -648,7 +659,7 @@ public boolean isStructureFormed() {
     public boolean isStructureFormed(String name) {
         if (getWorld() == null) return false;
 
-        return getSubstructure(name).getPatternState().isFlipped();
+        return getSubstructure(name).getPatternState().isFormed();
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/pattern/OriginOffset.java b/src/main/java/gregtech/api/pattern/OriginOffset.java
new file mode 100644
index 00000000000..e35b3bf7b01
--- /dev/null
+++ b/src/main/java/gregtech/api/pattern/OriginOffset.java
@@ -0,0 +1,37 @@
+package gregtech.api.pattern;
+
+import gregtech.api.util.RelativeDirection;
+
+import net.minecraft.util.EnumFacing;
+
+/**
+ * Simple class for a relative offset from a position.
+ */
+public class OriginOffset {
+
+    /**
+     * 3 long because the opposite directions cancel out, it stores amount with the first direction in each pair(an even
+     * ordinal).
+     */
+    protected final int[] offset = new int[3];
+
+    public OriginOffset move(RelativeDirection dir, int amount) {
+        amount *= (dir.ordinal() % 2 == 0) ? 1 : -1;
+        offset[dir.ordinal() / 2] += amount;
+        return this;
+    }
+
+    public OriginOffset move(RelativeDirection dir) {
+        return move(dir, 1);
+    }
+
+    public int get(RelativeDirection dir) {
+        return offset[dir.ordinal() / 2] * ((dir.ordinal() % 2 == 0) ? 1 : -1);
+    }
+
+    public void apply(GreggyBlockPos pos, EnumFacing frontFacing, EnumFacing upFacing, boolean flip) {
+        for (int i = 0; i < 3; i++) {
+            pos.offset(RelativeDirection.VALUES[2 * i].getRelativeFacing(frontFacing, upFacing, flip), offset[i]);
+        }
+    }
+}
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 4eb15f17c9f..68c3bcb8b79 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -3,6 +3,7 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
+import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.RelativeDirection;
@@ -32,14 +33,7 @@ public class BlockPattern implements IBlockPattern {
      * In the form of [ num aisles, num string per aisle, num char per string ]
      */
     protected final int[] dimensions;
-
-    /**
-     * In the form of { pair 1 -> amount, pair 2 -> amount, pair 3 -> amount }.
-     * Each pair is relative directions, such as FRONT, BACK, or RIGHT, LEFT.
-     * The amount is the offset in the first of each pair, can be negative.
-     * {@link RelativeDirection} of structure directions, stored as ordinals
-     */
-    protected final int[] startOffset;
+    protected final OriginOffset offset;
 
     /**
      * True if startOffset was passed in, false if it was null and automatically detected
@@ -61,19 +55,19 @@ public class BlockPattern implements IBlockPattern {
     // how many not nulls to keep someone from not passing in null?
     public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] dimensions,
                         @NotNull RelativeDirection @NotNull [] directions,
-                        int @Nullable [] startOffset, @NotNull Char2ObjectMap predicates,
+                        @Nullable OriginOffset offset, @NotNull Char2ObjectMap predicates,
                         char centerChar) {
         this.aisles = aisles;
         this.dimensions = dimensions;
         this.directions = directions;
         this.predicates = predicates;
-        hasStartOffset = startOffset != null;
+        hasStartOffset = offset != null;
 
-        if (startOffset == null) {
-            this.startOffset = new int[3];
+        if (offset == null) {
+            this.offset = new OriginOffset();
             legacyStartOffset(centerChar);
         } else {
-            this.startOffset = startOffset;
+            this.offset = offset;
         }
 
         this.worldState = new BlockWorldState();
@@ -94,9 +88,9 @@ private void legacyStartOffset(char center) {
             if (result != null) {
                 // structure starts at aisle 0, string 0, char 0, think about it
                 // so relative to the controller we need to offset by this to get to the start
-                moveStartOffset(directions[0], -aisleI);
-                moveStartOffset(directions[1], -result[0]);
-                moveStartOffset(directions[2], -result[1]);
+                moveOffset(directions[0], -aisleI);
+                moveOffset(directions[1], -result[0]);
+                moveOffset(directions[2], -result[1]);
                 return;
             }
         }
@@ -198,6 +192,7 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
                     }
                     // otherwise this is the max repeats
                     actualRepeats = repeats - 1;
+                    break;
                 }
             }
 
@@ -282,6 +277,7 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
     }
 
     @Override
+    // todo get real
     public PreviewBlockPattern getDefaultShape() {
         char[][][] pattern = new char[dimensions[2]][dimensions[1]][dimensions[0]];
 
@@ -308,36 +304,15 @@ public boolean legacyBuilderError() {
         return !hasStartOffset;
     }
 
+    @Override
+    public OriginOffset getOffset() {
+        return offset;
+    }
+
     private GreggyBlockPos startPos(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing,
                                     boolean flip) {
         GreggyBlockPos start = controllerPos.copy();
-        for (int i = 0; i < 3; i++) {
-            start.offset(RelativeDirection.VALUES[2 * i].getRelativeFacing(frontFacing, upFacing, flip),
-                    startOffset[i]);
-        }
+        offset.apply(start, frontFacing, upFacing, flip);
         return start;
     }
-
-    /**
-     * Moves the start offset in the given direction and amount, use {@link BlockPattern#clearCache()} after to prevent
-     * the cache from being stuck in the old offset.
-     * 
-     * @param dir    The direction, relative to controller.
-     * @param amount The amount to offset.
-     */
-    public void moveStartOffset(RelativeDirection dir, int amount) {
-        // reverse amount if its in the opposite direction
-        amount *= (dir.ordinal() % 2 == 0) ? 1 : -1;
-        startOffset[dir.ordinal() / 2] += amount;
-    }
-
-    /**
-     * Get the start offset in the given direction.
-     * 
-     * @param dir The direction, relative to controller.
-     * @return The amount, can be negative.
-     */
-    public int getStartOffset(RelativeDirection dir) {
-        return startOffset[dir.ordinal() / 2] * (dir.ordinal() % 2 == 0 ? 1 : -1);
-    }
 }
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index 239b5af7f68..2990c38ed45 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -3,6 +3,7 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
+import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.RelativeDirection;
@@ -26,6 +27,7 @@ public class ExpandablePattern implements IBlockPattern {
 
     protected final QuadFunction boundsFunction;
     protected final BiFunction predicateFunction;
+    protected final OriginOffset offset = new OriginOffset();
 
     /**
      * In the form of [ aisleDir, stringDir, charDir ]
@@ -185,4 +187,9 @@ public Long2ObjectMap getCache() {
     public void clearCache() {
         cache.clear();
     }
+
+    @Override
+    public OriginOffset getOffset() {
+        return offset;
+    }
 }
diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
index b55d3837902..6c896f6e038 100644
--- a/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
@@ -1,5 +1,6 @@
 package gregtech.api.pattern.pattern;
 
+import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
@@ -13,7 +14,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map.Entry;
 
 import static gregtech.api.util.RelativeDirection.*;
 
@@ -22,9 +22,9 @@
  * When the multiblock is placed, its facings are concrete. Then, the {@link RelativeDirection}s passed into
  * {@link FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)} are ways in which the
  * pattern progresses. It can be thought like this, where startPos() is either defined via
- * {@link FactoryBlockPattern#startOffset(RelativeDirection, int)}
+ * {@link FactoryBlockPattern#startOffset(OriginOffset)}
  * , or automatically detected(for legacy compat only, you should use
- * {@link FactoryBlockPattern#startOffset(RelativeDirection, int)} always for new code):
+ * {@link FactoryBlockPattern#startOffset(OriginOffset)} always for new code):
  * 
  * 
  * {@code
@@ -50,9 +50,8 @@ public class FactoryBlockPattern {
     /**
      * Look at the field with the same name in {@link BlockPattern} for docs
      */
-    private int[] startOffset;
+    private OriginOffset offset;
     private char centerChar;
-    private boolean reverse = true;
 
     private final List aisles = new ArrayList<>();
 
@@ -168,14 +167,9 @@ public FactoryBlockPattern setRepeatable(int repeatCount) {
 
     /**
      * Sets a part of the start offset in the given direction.
-     * 
-     * @param dir    The direction to offset, relative to controller.
-     * @param amount The amount to offset.
      */
-    public FactoryBlockPattern startOffset(RelativeDirection dir, int amount) {
-        if (startOffset == null) startOffset = new int[3];
-
-        startOffset[dir.ordinal() / 2] = amount * (dir.ordinal() % 2 == 0 ? 1 : -1);
+    public FactoryBlockPattern startOffset(OriginOffset offset) {
+        this.offset = offset;
         return this;
     }
 
@@ -198,8 +192,8 @@ public static FactoryBlockPattern start() {
      * @param aisleDir  The direction aisles progress in, each successive {@link FactoryBlockPattern#aisle(String...)}
      *                  progresses in this direction
      */
-    public static FactoryBlockPattern start(RelativeDirection charDir, RelativeDirection stringDir,
-                                            RelativeDirection aisleDir) {
+    public static FactoryBlockPattern start(RelativeDirection aisleDir, RelativeDirection stringDir,
+                                            RelativeDirection charDir) {
         return new FactoryBlockPattern(aisleDir, stringDir, charDir);
     }
 
@@ -215,34 +209,24 @@ public FactoryBlockPattern where(char symbol, TraceabilityPredicate blockMatcher
         return this;
     }
 
-    /**
-     * Calling this stops the reversal of aisles, you should call this on all new patterns
-     */
-    // todo remove this stuff
-    public FactoryBlockPattern modern() {
-        reverse = false;
-        return this;
-    }
-
     public BlockPattern build() {
         checkMissingPredicates();
         this.dimensions[0] = aisles.size();
-        PatternAisle[] aisleArray = aisles.toArray(new PatternAisle[0]);
-        if (reverse) {
-            ArrayUtils.reverse(aisleArray);
-        } else {
-            if (startOffset == null) GTLog.logger.warn(
-                    "You used .modern() on the builder without using .startOffset()! This will have unintended behavior!");
-        }
-        return new BlockPattern(aisleArray, dimensions, structureDir, startOffset, symbolMap, centerChar);
+        // the reason this exists is before this rewrite, MultiblockControllerBase would
+        // pass the opposite front facing into the pattern check, but now this is fixed.
+        //
+        if (offset == null) GTLog.logger.warn(
+                "You didn't use .startOffset() on the builder! Start offset will now be auto detected, which may product unintended results!");
+        return new BlockPattern(aisles.toArray(new PatternAisle[0]), dimensions, structureDir, offset, symbolMap,
+                centerChar);
     }
 
     private void checkMissingPredicates() {
         List list = new ArrayList<>();
 
-        for (Entry entry : this.symbolMap.entrySet()) {
+        for (Char2ObjectMap.Entry entry : this.symbolMap.char2ObjectEntrySet()) {
             if (entry.getValue() == null) {
-                list.add(entry.getKey());
+                list.add(entry.getCharKey());
             }
         }
 
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index b05761d9a20..a0c6b20c1c6 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -1,7 +1,9 @@
 package gregtech.api.pattern.pattern;
 
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.pattern.OriginOffset;
 import gregtech.api.util.BlockInfo;
+import gregtech.api.util.RelativeDirection;
 
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
@@ -79,4 +81,14 @@ default void clearCache() {
     default boolean legacyBuilderError() {
         return false;
     }
+
+    OriginOffset getOffset();
+
+    default void moveOffset(RelativeDirection dir, int amount) {
+        getOffset().move(dir, amount);
+    }
+
+    default void moveOffset(RelativeDirection dir) {
+        getOffset().move(dir);
+    }
 }
diff --git a/src/main/java/gregtech/api/pattern/pattern/PatternState.java b/src/main/java/gregtech/api/pattern/pattern/PatternState.java
index bcf47a3f73e..04fa8d37ffa 100644
--- a/src/main/java/gregtech/api/pattern/pattern/PatternState.java
+++ b/src/main/java/gregtech/api/pattern/pattern/PatternState.java
@@ -17,12 +17,7 @@ public class PatternState {
      * For use by the multiblock to update its flipped state.
      */
     protected boolean actualFlipped = false;
-    protected boolean shouldUpdate = false;
-    /**
-     * For if the multiblock manually invalidates its state(like coil mismatch). This means wait until the cache is
-     * no longer valid and the raw check passes to reform the structure.
-     */
-    protected boolean isWaiting = false;
+    protected boolean shouldUpdate = true;
     protected PatternError error;
     protected EnumCheckState state;
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOven.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOven.java
index de93f45bb97..168b2f50bc1 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOven.java
@@ -52,9 +52,9 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     protected BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XXX", "XXX")
-                .aisle("XXX", "X#X", "XXX")
                 .aisle("XXX", "XYX", "XXX")
+                .aisle("XXX", "X#X", "XXX")
+                .aisle("XXX", "XXX", "XXX")
                 .where('X',
                         states(getCasingState())
                                 .or(metaTileEntities(MetaTileEntities.COKE_OVEN_HATCH).setMaxGlobalLimited(5)))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
index 9aac6264a83..c512cb16c26 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
@@ -188,9 +188,9 @@ public boolean isActive() {
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "CCC", "CCC", "CCC")
-                .aisle("XXX", "CPC", "CPC", "CCC")
                 .aisle("XXX", "CSC", "CCC", "CCC")
+                .aisle("XXX", "CPC", "CPC", "CCC")
+                .aisle("XXX", "CCC", "CCC", "CCC")
                 .where('S', selfPredicate())
                 .where('P', states(boilerType.pipeState))
                 .where('X', states(boilerType.fireboxState).setMinGlobalLimited(4)
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
index ba43b7476a0..719dac5bd54 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
@@ -79,9 +79,9 @@ protected void updateFormedValid() {}
     @NotNull
     protected BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XXX", "XXX")
-                .aisle("XXX", "X X", "XXX")
                 .aisle("XXX", "XSX", "XXX")
+                .aisle("XXX", "X X", "XXX")
+                .aisle("XXX", "XXX", "XXX")
                 .where('S', selfPredicate())
                 .where('X', states(getCasingState()).setMinGlobalLimited(23)
                         .or(metaTileEntities(getValve()).setMaxGlobalLimited(2)))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
index 8915e22f95d..1deb29da5ca 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
@@ -63,9 +63,9 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     protected BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XXX", "XXX", "XXX")
-                .aisle("XXX", "X&X", "X#X", "X#X")
                 .aisle("XXX", "XYX", "XXX", "XXX")
+                .aisle("XXX", "X&X", "X#X", "X#X")
+                .aisle("XXX", "XXX", "XXX", "XXX")
                 .where('X', states(MetaBlocks.METAL_CASING.getState(BlockMetalCasing.MetalCasingType.PRIMITIVE_BRICKS)))
                 .where('#', air())
                 .where('&', air().or(SNOW_PREDICATE)) // this won't stay in the structure, and will be broken while
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
index d092f74a738..3f26d10fa59 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
@@ -132,9 +132,9 @@ private void resetTileAbilities() {
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXXX", "**F*", "**F*")
-                .aisle("XXHX", "F**F", "FFFF")
-                .aisle("SXXX", "**F*", "**F*")
+                .aisle("XXXS", "*F**", "*F**")
+                .aisle("XHXX", "F**F", "FFFF")
+                .aisle("XXXX", "*F**", "*F**")
                 .where('S', selfPredicate())
                 .where('X', states(MetaBlocks.STEAM_CASING.getState(BlockSteamCasing.SteamCasingType.PUMP_DECK)))
                 .where('F', frames(Materials.TreatedWood))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
index 3274886e4c6..58abfc8ef6d 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
@@ -115,9 +115,9 @@ public void invalidateStructure(String name) {
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XXX", "XXX")
-                .aisle("XXX", "XCX", "XXX")
                 .aisle("XXX", "XSX", "XXX")
+                .aisle("XXX", "XCX", "XXX")
+                .aisle("XXX", "XXX", "XXX")
                 .where('X', states(getCasingState()).setMinGlobalLimited(12).or(getHatchPredicates()))
                 .where('S', selfPredicate())
                 .where('C', states(MetaBlocks.FUSION_CASING.getState(BlockFusionCasing.CasingType.SUPERCONDUCTOR_COIL)))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
index f6c17b8df45..1c08627cbd0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
@@ -79,10 +79,10 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @NotNull
     @Override
     protected BlockPattern createStructurePattern() {
-        FactoryBlockPattern pattern = FactoryBlockPattern.start(FRONT, UP, RIGHT)
-                .aisle("FIF", "RTR", "SAG", " Y ")
-                .aisle("FIF", "RTR", "DAG", " Y ").setRepeatable(3, 15)
-                .aisle("FOF", "RTR", "DAG", " Y ")
+        FactoryBlockPattern pattern = FactoryBlockPattern.start(RIGHT, UP, FRONT)
+                .aisle("FOF", "RTR", "GAD", " Y ")
+                .aisle("FIF", "RTR", "GAD", " Y ").setRepeatable(3, 15)
+                .aisle("FIF", "RTR", "GAS", " Y ")
                 .where('S', selfPredicate())
                 .where('F', states(getCasingState())
                         .or(autoAbilities(false, true, false, false, false, false, false))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
index f546a752c0a..abd25566ac2 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
@@ -54,9 +54,9 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("HCHCH", "HCHCH", "HCHCH")
-                .aisle("HCHCH", "H###H", "HCHCH")
                 .aisle("HCHCH", "HCOCH", "HCHCH")
+                .aisle("HCHCH", "H###H", "HCHCH")
+                .aisle("HCHCH", "HCHCH", "HCHCH")
                 .where('O', selfPredicate())
                 .where('H', states(getCasingState()).setMinGlobalLimited(12).or(autoAbilities()))
                 .where('#', air())
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
index b65bb4cad13..d61a74f1af3 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
@@ -157,9 +157,9 @@ public void setWorkingEnabled(boolean isWorkingAllowed) {
     @Override
     protected BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XDDDX", "XDDDX", "XDDDX")
-                .aisle("XDDDX", "XAAAX", "XDDDX")
                 .aisle("XCCCX", "XCSCX", "XCCCX")
+                .aisle("XDDDX", "XAAAX", "XDDDX")
+                .aisle("XDDDX", "XDDDX", "XDDDX")
                 .where('S', selfPredicate())
                 .where('X', states(getOuterState()))
                 .where('D', states(getInnerState()).setMinGlobalLimited(3)
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
index 79c68ed7d83..ee09ef72734 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
@@ -117,8 +117,8 @@ public void invalidateStructure(String name) {
 
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
-        return FactoryBlockPattern.start(RIGHT, FRONT, UP)
-                .aisle("YSY", "YYY", "YYY")
+        return FactoryBlockPattern.start(UP, FRONT, RIGHT)
+                .aisle("YYY", "YYY", "YSY")
                 .aisle("XXX", "X#X", "XXX").setRepeatable(1, 11)
                 .aisle("XXX", "XXX", "XXX")
                 .where('S', selfPredicate())
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index 4fe649f7669..9635e53ff9b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -118,9 +118,9 @@ public boolean checkRecipe(@NotNull Recipe recipe, boolean consumeIfSuccess) {
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "CCC", "CCC", "XXX")
-                .aisle("XXX", "C#C", "C#C", "XMX")
                 .aisle("XSX", "CCC", "CCC", "XXX")
+                .aisle("XXX", "C#C", "C#C", "XMX")
+                .aisle("XXX", "CCC", "CCC", "XXX")
                 .where('S', selfPredicate())
                 .where('X', states(getCasingState()).setMinGlobalLimited(9)
                         .or(autoAbilities(true, true, true, true, true, true, false)))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
index 69ff9d5cb0b..41dd80f7ed3 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
@@ -111,9 +111,9 @@ protected void updateFormedValid() {
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "#F#", "#F#", "#F#", "###", "###", "###")
-                .aisle("XXX", "FCF", "FCF", "FCF", "#F#", "#F#", "#F#")
                 .aisle("XSX", "#F#", "#F#", "#F#", "###", "###", "###")
+                .aisle("XXX", "FCF", "FCF", "FCF", "#F#", "#F#", "#F#")
+                .aisle("XXX", "#F#", "#F#", "#F#", "###", "###", "###")
                 .where('S', selfPredicate())
                 .where('X', states(getCasingState()).setMinGlobalLimited(3)
                         .or(abilities(MultiblockAbility.INPUT_ENERGY).setMinGlobalLimited(1).setMaxGlobalLimited(3))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
index f4a847434ed..fb92e89077f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
@@ -125,7 +125,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     protected BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("###############", "######OGO######", "###############")
+                .aisle("###############", "######OSO######", "###############")
                 .aisle("######ICI######", "####GGAAAGG####", "######ICI######")
                 .aisle("####CC###CC####", "###EAAOGOAAE###", "####CC###CC####")
                 .aisle("###C#######C###", "##EKEG###GEKE##", "###C#######C###")
@@ -139,7 +139,7 @@ protected BlockPattern createStructurePattern() {
                 .aisle("###C#######C###", "##EKEG###GEKE##", "###C#######C###")
                 .aisle("####CC###CC####", "###EAAOGOAAE###", "####CC###CC####")
                 .aisle("######ICI######", "####GGAAAGG####", "######ICI######")
-                .aisle("###############", "######OSO######", "###############")
+                .aisle("###############", "######OGO######", "###############")
                 .where('S', selfPredicate())
                 .where('G', states(getCasingState(), getGlassState()))
                 .where('E',
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
index 73e9082614d..daf6fce58a6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
@@ -198,11 +198,11 @@ private void consumeEnergy() {
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
+                .aisle("AS", "CC", "CC", "CC", "AA")
+                .aisle("AV", "VX", "VX", "VX", "AV")
+                .aisle("AV", "VX", "VX", "VX", "AV")
+                .aisle("AV", "VX", "VX", "VX", "AV")
                 .aisle("AA", "CC", "CC", "CC", "AA")
-                .aisle("VA", "XV", "XV", "XV", "VA")
-                .aisle("VA", "XV", "XV", "XV", "VA")
-                .aisle("VA", "XV", "XV", "XV", "VA")
-                .aisle("SA", "CC", "CC", "CC", "AA")
                 .where('S', selfPredicate())
                 .where('A', states(getAdvancedState()))
                 .where('V', states(getVentState()))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java
index 6ac37e3465d..c9e9a26a18e 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityImplosionCompressor.java
@@ -35,9 +35,9 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XXX", "XXX")
-                .aisle("XXX", "X#X", "XXX")
                 .aisle("XXX", "XSX", "XXX")
+                .aisle("XXX", "X#X", "XXX")
+                .aisle("XXX", "XXX", "XXX")
                 .where('S', selfPredicate())
                 .where('X', states(getCasingState()).setMinGlobalLimited(14).or(autoAbilities()))
                 .where('#', air())
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
index 442040c375f..63d09fc1a51 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
@@ -56,9 +56,9 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
         TraceabilityPredicate casing = states(getCasingState()).setMinGlobalLimited(10);
         TraceabilityPredicate abilities = autoAbilities();
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XCX", "XXX")
-                .aisle("XCX", "CPC", "XCX")
                 .aisle("XXX", "XSX", "XXX")
+                .aisle("XCX", "CPC", "XCX")
+                .aisle("XXX", "XCX", "XXX")
                 .where('S', selfPredicate())
                 .where('X', casing.or(abilities))
                 .where('P', states(getPipeCasingState()))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
index a3996dfdd4b..f5fb25a8d6b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
@@ -186,9 +186,9 @@ protected void updateFormedValid() {
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "#F#", "#F#", "#F#", "###", "###", "###")
-                .aisle("XXX", "FCF", "FCF", "FCF", "#F#", "#F#", "#F#")
                 .aisle("XSX", "#F#", "#F#", "#F#", "###", "###", "###")
+                .aisle("XXX", "FCF", "FCF", "FCF", "#F#", "#F#", "#F#")
+                .aisle("XXX", "#F#", "#F#", "#F#", "###", "###", "###")
                 .where('S', selfPredicate())
                 .where('X', states(getCasingState())
                         .or(abilities(MultiblockAbility.EXPORT_ITEMS).setMaxGlobalLimited(1).setPreviewCount(1))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
index 4c3619ce8d8..4a21309ddbd 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
@@ -126,9 +126,9 @@ public void invalidateStructure(String name) {
     @Override
     protected BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "CCC", "XXX")
-                .aisle("XXX", "C#C", "XMX")
                 .aisle("XSX", "CCC", "XXX")
+                .aisle("XXX", "C#C", "XMX")
+                .aisle("XXX", "CCC", "XXX")
                 .where('S', selfPredicate())
                 .where('X',
                         states(getCasingState()).setMinGlobalLimited(9)
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
index 6c318bb429b..cd28a1dbeac 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
@@ -100,9 +100,9 @@ public boolean canBridge(@NotNull Collection seen)
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XXX", "XXX")
-                .aisle("XXX", "XAX", "XXX")
                 .aisle("XXX", "XSX", "XXX")
+                .aisle("XXX", "XAX", "XXX")
+                .aisle("XXX", "XXX", "XXX")
                 .where('S', selfPredicate())
                 .where('A', states(getAdvancedState()))
                 .where('X', states(getCasingState()).setMinGlobalLimited(7)
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index ec8f49af778..57e842518d0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -231,8 +231,8 @@ protected boolean shouldShowVoidingModeButton() {
     @NotNull
     @Override
     protected BlockPattern createStructurePattern() {
-        return FactoryBlockPattern.start(RIGHT, FRONT, UP)
-                .aisle("XXSXX", "XXXXX", "XXXXX", "XXXXX", "XXXXX")
+        return FactoryBlockPattern.start(UP, FRONT, RIGHT)
+                .aisle("XXXXX", "XXXXX", "XXXXX", "XXXXX", "XXSXX")
                 .aisle("XXXXX", "XCCCX", "XCCCX", "XCCCX", "XXXXX")
                 .aisle("GGGGG", "GBBBG", "GBBBG", "GBBBG", "GGGGG").setRepeatable(1, MAX_BATTERY_LAYERS)
                 .aisle("GGGGG", "GGGGG", "GGGGG", "GGGGG", "GGGGG")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
index faa3847ff5f..0386fe0d3f6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
@@ -92,9 +92,9 @@ public int getMachineLimit() {
     @Override
     protected BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XXX", "XXX")
-                .aisle("XXX", "X#X", "XXX")
                 .aisle("XXX", "XSX", "XXX")
+                .aisle("XXX", "X#X", "XXX")
+                .aisle("XXX", "XXX", "XXX")
                 .where('L', states(getCasingState()))
                 .where('S', selfPredicate())
                 .where('X', states(getCasingState())
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
index 09426c6b35e..277d409da3f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
@@ -54,10 +54,10 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XXX", "XXX")
+                .aisle("XXX", "XSX", "XXX")
                 .aisle("CCC", "C#C", "CCC")
                 .aisle("CCC", "C#C", "CCC")
-                .aisle("XXX", "XSX", "XXX")
+                .aisle("XXX", "XXX", "XXX")
                 .where('S', selfPredicate())
                 .where('X', states(getCasingState()).setMinGlobalLimited(6).or(autoAbilities()))
                 .where('C', heatingCoils())
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
index 84c199a9115..ab5b7514b88 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
@@ -121,13 +121,13 @@ public IObjectHolder getObjectHolder() {
     @Override
     protected BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "VVV", "PPP", "PPP", "PPP", "VVV", "XXX")
-                .aisle("XXX", "VAV", "AAA", "AAA", "AAA", "VAV", "XXX")
-                .aisle("XXX", "VAV", "XAX", "XSX", "XAX", "VAV", "XXX")
-                .aisle("XXX", "XAX", "---", "---", "---", "XAX", "XXX")
-                .aisle(" X ", "XAX", "---", "---", "---", "XAX", " X ")
-                .aisle(" X ", "XAX", "-A-", "-H-", "-A-", "XAX", " X ")
                 .aisle("   ", "XXX", "---", "---", "---", "XXX", "   ")
+                .aisle(" X ", "XAX", "-A-", "-H-", "-A-", "XAX", " X ")
+                .aisle(" X ", "XAX", "---", "---", "---", "XAX", " X ")
+                .aisle("XXX", "XAX", "---", "---", "---", "XAX", "XXX")
+                .aisle("XXX", "VAV", "XAX", "XSX", "XAX", "VAV", "XXX")
+                .aisle("XXX", "VAV", "AAA", "AAA", "AAA", "VAV", "XXX")
+                .aisle("XXX", "VVV", "PPP", "PPP", "PPP", "VVV", "XXX")
                 .where('S', selfPredicate())
                 .where('X', states(getCasingState()))
                 .where(' ', any())
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
index 02aefada883..f0cd509244a 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java
@@ -5,6 +5,7 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
+import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
@@ -44,9 +45,9 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XXX", "XXX")
-                .aisle("XXX", "X#X", "XXX")
                 .aisle("XXX", "XSX", "XXX")
+                .aisle("XXX", "X#X", "XXX")
+                .aisle("XXX", "XXX", "XXX")
                 .where('S', selfPredicate())
                 .where('X', states(getCasingState()).setMinGlobalLimited(14).or(autoAbilities()))
                 .where('#', air())
@@ -59,7 +60,7 @@ protected void createStructurePatterns() {
         structures.put("SECOND", FactoryBlockPattern.start()
                 .aisle("X")
                 .where('X', states(getCasingState()))
-                .startOffset(RelativeDirection.FRONT, 5)
+                .startOffset(new OriginOffset().move(RelativeDirection.FRONT, 5))
                 .build());
     }
 
@@ -68,7 +69,7 @@ protected void addDisplayText(List textList) {
         super.addDisplayText(textList);
 
         ITextComponent button = new TextComponentString("Second structure offset: " +
-                ((BlockPattern) getSubstructure("SECOND")).getStartOffset(RelativeDirection.FRONT));
+                getSubstructure("SECOND").getOffset().get(RelativeDirection.FRONT));
         button.appendText(" ");
         button.appendSibling(withButton(new TextComponentString("[-]"), "sub"));
         button.appendText(" ");
@@ -83,8 +84,8 @@ protected void addDisplayText(List textList) {
     protected void handleDisplayClick(String componentData, Widget.ClickData clickData) {
         super.handleDisplayClick(componentData, clickData);
         int mod = componentData.equals("add") ? 1 : -1;
-        // ((BlockPattern) getSubstructure("SECOND").moveStartOffset(RelativeDirection.FRONT, mod);
-        // getSubstructure("SECOND").clearCache();
+        getSubstructure("SECOND").getOffset().move(RelativeDirection.FRONT, mod);
+        getSubstructure("SECOND").clearCache();
     }
 
     @SideOnly(Side.CLIENT)
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
index 6bd0d7bb4d0..4ba570e0d62 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
@@ -127,10 +127,10 @@ public void addInformation(ItemStack stack, @Nullable World player, List
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XDX", "XXX")
+                .aisle("AAA", "AYA", "AAA")
                 .aisle("XCX", "CGC", "XCX")
                 .aisle("XCX", "CGC", "XCX")
-                .aisle("AAA", "AYA", "AAA")
+                .aisle("XXX", "XDX", "XXX")
                 .where('X', states(getCasingState()))
                 .where('G', states(getGearboxState()))
                 .where('C',
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
index a408ded4cc4..80ebb0be31f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
@@ -185,9 +185,9 @@ public void addInformation(ItemStack stack, @Nullable World player, List
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("CCCC", "CHHC", "CCCC")
+                .aisle("CCCC", "CHSC", "CCCC")
                 .aisle("CHHC", "RGGR", "CHHC")
-                .aisle("CCCC", "CSHC", "CCCC")
+                .aisle("CCCC", "CHHC", "CCCC")
                 .where('S', selfPredicate())
                 .where('G', states(getGearBoxState()))
                 .where('C', states(getCasingState()))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java
index 0069dded23d..5bb49cded5b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamGrinder.java
@@ -51,9 +51,9 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity metaTileEntityHol
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "XXX", "XXX")
-                .aisle("XXX", "X#X", "XXX")
                 .aisle("XXX", "XSX", "XXX")
+                .aisle("XXX", "X#X", "XXX")
+                .aisle("XXX", "XXX", "XXX")
                 .where('S', selfPredicate())
                 .where('X', states(getCasingState()).setMinGlobalLimited(14).or(autoAbilities()))
                 .where('#', air())
diff --git a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
index bf29da1e9e9..e582373455e 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
@@ -51,9 +51,9 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     protected BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start()
-                .aisle("XXX", "CCC", "#C#")
-                .aisle("XXX", "C#C", "#C#")
                 .aisle("XXX", "CSC", "#C#")
+                .aisle("XXX", "C#C", "#C#")
+                .aisle("XXX", "CCC", "#C#")
                 .where('S', selfPredicate())
                 .where('X', states(getFireboxState())
                         .or(autoAbilities(true, false, false, false, false).setMinGlobalLimited(1)

From 5e182d66651dcd2694aad9a3ec0f223aebca00ff Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 11 Aug 2024 23:46:08 -0700
Subject: [PATCH 24/64] cleanroom part 0

---
 .../multiblock/MultiblockControllerBase.java  |  20 +--
 .../gregtech/api/pattern/GreggyBlockPos.java  |  25 ++++
 .../api/pattern/TraceabilityPredicate.java    |   1 +
 .../api/pattern/pattern/BlockPattern.java     |  10 +-
 .../pattern/pattern/ExpandablePattern.java    |  37 ++++--
 .../pattern/FactoryExpandablePattern.java     |   3 +-
 .../gregtech/api/util/RelativeDirection.java  |   4 +
 .../electric/MetaTileEntityCleanroom.java     | 114 ++++++++++++++----
 .../MetaTileEntityMultiblockPart.java         |   6 +-
 src/main/java/gregtech/core/CoreModule.java   |   1 +
 10 files changed, 172 insertions(+), 49 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 952d086b48f..e72016e1039 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -75,6 +75,7 @@
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiFunction;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -318,7 +319,7 @@ public static TraceabilityPredicate heatingCoils() {
 
     /**
      * Ensures that all the blockstates that are in the map are the same type. Returns the type if all match, or null if
-     * they don't.
+     * they don't(or none match).
      * Example: {@code allSameType(GregTechAPI.HEATING_COILS, getSubstructure("MAIN").getCache())}
      * 
      * @param info  The info, such as GregTechAPI.HEATING_COILS
@@ -407,7 +408,7 @@ public void checkStructurePattern(String name) {
         long time = System.nanoTime();
         PatternState result = pattern.checkPatternFastAt(getWorld(), getPos(),
                 getFrontFacing(), getUpwardsFacing(), allowsFlip());
-        System.out.println(
+        GTLog.logger.info(
                 "structure check for " + getClass().getSimpleName() + " took " + (System.nanoTime() - time) + " nanos");
 
         if (result.getState().isValid()) { // structure check succeeds
@@ -418,13 +419,15 @@ public void checkStructurePattern(String name) {
                     // add any new parts, because removal of parts is impossible
                     // it is possible for old parts to persist, so check that
                     forEachMultiblockPart(name, part -> {
+                        // this part is already added, so igore it
+                        if (multiblockParts.contains(part)) return true;
+
+                        // todo maybe move below into separate check?
                         if (part.isAttachedToMultiBlock() && !part.canPartShare(this, name)) {
                             invalidateStructure(name);
                             return false;
                         }
-                        if (multiblockParts.add(part)) {
-                            part.addToMultiBlock(this, name);
-                        }
+                        part.addToMultiBlock(this, name);
                         if (part instanceof IMultiblockAbilityPartabilityPart) {
                             // noinspection unchecked
                             registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
@@ -506,8 +509,11 @@ protected void registerMultiblockAbility(IMultiblockAbilityPart part) {
     }
 
     // todo do
-    protected void forEachFormed(GreggyBlockPos pos) {
-        IBlockState state = getWorld().getBlockState(pos.immutable());
+    protected void forEachFormed(String name, Consumer action) {
+        Long2ObjectMap cache = getSubstructure(name).getCache();
+        for (BlockInfo info : cache.values()) {
+            action.accept(info);
+        }
     }
 
     protected void formStructure(String name) {
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index d59afa4bf17..778726e33a3 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -70,6 +70,16 @@ public GreggyBlockPos set(EnumFacing.Axis axis, GreggyBlockPos other) {
         return this;
     }
 
+    /**
+     * Sets a coordinate in the index to the value.
+     * @param index The index, X = 0, Y = 1, Z = 2.
+     * @param value The value to set it to.
+     */
+    public GreggyBlockPos set(int index, int value) {
+        pos[index] = value;
+        return this;
+    }
+
     /**
      * Sets all 3 coordinates in the given axis order
      * 
@@ -175,6 +185,14 @@ public GreggyBlockPos diff(GreggyBlockPos other) {
         return this;
     }
 
+    /**
+     * Sets all 3 coordinates to 0.
+     */
+    public GreggyBlockPos zero() {
+        Arrays.fill(pos, 0);
+        return this;
+    }
+
     /**
      * @return True if all 3 of the coordinates are 0.
      */
@@ -203,6 +221,13 @@ public int get(EnumFacing.Axis axis) {
         return pos[axis.ordinal()];
     }
 
+    /**
+     * Gets a copy of the internal array, in xyz.
+     */
+    public int[] getAll() {
+        return Arrays.copyOf(pos, 3);
+    }
+
     public GreggyBlockPos copy() {
         return new GreggyBlockPos().from(this);
     }
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index 1d86fd7602b..123666baedf 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -63,6 +63,7 @@ public TraceabilityPredicate(Predicate predicate, Supplier predicate) {
         this(predicate, null);
     }
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 68c3bcb8b79..4900f815a87 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -6,6 +6,7 @@
 import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
+import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
 import net.minecraft.block.state.IBlockState;
@@ -249,8 +250,13 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
 
                 if (predicate != TraceabilityPredicate.ANY) {
                     TileEntity te = worldState.getTileEntity();
-                    cache.put(charPos.toLong(), new BlockInfo(worldState.getBlockState(),
-                            !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
+                    try {
+                        cache.put(charPos.toLong(), new BlockInfo(worldState.getBlockState(),
+                                !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
+                    } catch (IllegalArgumentException e) {
+                        GTLog.logger.error("bruh");
+                        throw e;
+                    }
                 }
 
                 // GTLog.logger.info("Checked pos at " + charPos + " with flip " + flip);
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index 2990c38ed45..a306d141b9e 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -113,38 +113,49 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
         int[] bounds = boundsFunction.apply(world, new GreggyBlockPos(centerPos), frontFacing, upwardsFacing);
         if (bounds == null) return false;
 
+        globalCount.clear();
+
         // where the iteration starts, in octant 7
         GreggyBlockPos negativeCorner = new GreggyBlockPos();
         // where the iteration ends, in octant 1
         GreggyBlockPos positiveCorner = new GreggyBlockPos();
 
+        // [ absolute aisle, absolute string, absolute dir ]
+        EnumFacing[] absolutes = new EnumFacing[3];
+
         for (int i = 0; i < 3; i++) {
             RelativeDirection selected = directions[i];
 
-            EnumFacing absoluteSelected = selected.getRelativeFacing(frontFacing, upwardsFacing, isFlipped);
-            boolean axisPositive = absoluteSelected.getAxisDirection() == EnumFacing.AxisDirection.POSITIVE;
+            absolutes[i] = selected.getRelativeFacing(frontFacing, upwardsFacing, isFlipped);
 
-            // negativeCorner always goes in the negative direction, while positiveCorner always goes in the positive
-            // direction
-            (axisPositive ? negativeCorner : positiveCorner).offset(absoluteSelected.getOpposite(),
-                    bounds[selected.oppositeOrdinal()]);
-            (axisPositive ? positiveCorner : negativeCorner).offset(absoluteSelected, bounds[selected.ordinal()]);
-        }
+            int ordinal = selected.ordinal(), oppositeOrdinal = selected.oppositeOrdinal();
 
-        // which way each direction progresses absolutely, in [ char, string, aisle ]
-        EnumFacing[] facings = new EnumFacing[3];
-        for (int i = 0; i < 3; i++) {
-            facings[i] = directions[2 - i].getRelativeFacing(frontFacing, upwardsFacing, false);
+            boolean axisPositive = absolutes[i].getAxisDirection() == EnumFacing.AxisDirection.POSITIVE;
+
+            if (axisPositive) {
+                positiveCorner.set(i, bounds[ordinal]);
+                negativeCorner.set(i, -bounds[oppositeOrdinal]);
+            } else {
+                negativeCorner.set(i, -bounds[ordinal]);
+                positiveCorner.set(i, bounds[oppositeOrdinal]);
+            }
         }
 
         worldState.setWorld(world);
         // this translates from the relative coordinates to world coordinates
         GreggyBlockPos translation = new GreggyBlockPos(centerPos);
 
-        for (GreggyBlockPos pos : GreggyBlockPos.allInBox(negativeCorner, positiveCorner, facings)) {
+        // SOUTH, UP, EAST means point is +z, line is +y, plane is +x. this basically means the x val of the iter is aisle count, y is str count, and z is char count.
+        for (GreggyBlockPos pos : GreggyBlockPos.allInBox(negativeCorner, positiveCorner, EnumFacing.SOUTH, EnumFacing.UP, EnumFacing.EAST)) {
 
             // test first before using .add() which mutates the pos
             TraceabilityPredicate predicate = predicateFunction.apply(pos, bounds);
+
+            // cache the pos here so that the offsets don't mess it up
+            int[] arr = pos.getAll();
+            // this basically reshuffles the coordinates into absolute form from relative form
+            pos.zero().offset(absolutes[0], arr[0]).offset(absolutes[1], arr[1]).offset(absolutes[2], arr[2]);
+            // translate from the origin to the center
             // set the pos with world coordinates
             worldState.setPos(pos.add(translation));
 
diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
index 19bbe672e1c..1d5bd90fbba 100644
--- a/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
@@ -61,8 +61,7 @@ public static FactoryExpandablePattern start() {
      * This supplies the bounds function. The inputs are: World, controller pos, front facing, up facing. The returned
      * array
      * is an int array of length 6, with how much to extend the multiblock in each direction. The order of the
-     * directions is the same
-     * as the ordinal of the enum.
+     * directions is the same as the ordinal of the RelativeDirection enum.
      */
     public FactoryExpandablePattern boundsFunction(QuadFunction function) {
         this.boundsFunction = function;
diff --git a/src/main/java/gregtech/api/util/RelativeDirection.java b/src/main/java/gregtech/api/util/RelativeDirection.java
index 5f4a946f53a..6ca739f6594 100644
--- a/src/main/java/gregtech/api/util/RelativeDirection.java
+++ b/src/main/java/gregtech/api/util/RelativeDirection.java
@@ -20,6 +20,10 @@ public enum RelativeDirection {
     BACK(EnumFacing::getOpposite);
 
     final Function actualFacing;
+
+    /**
+     * do not mutate this unless you want your house to explode
+     */
     public static final RelativeDirection[] VALUES = values();
 
     RelativeDirection(Function actualFacing) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index c2930db5c7e..5da9bb5ca2b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -28,6 +28,7 @@
 import gregtech.api.pattern.pattern.FactoryExpandablePattern;
 import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.util.BlockInfo;
+import gregtech.api.util.GTLog;
 import gregtech.api.util.GTUtility;
 import gregtech.api.util.Mods;
 import gregtech.api.util.RelativeDirection;
@@ -81,8 +82,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 
 import static gregtech.api.gui.widgets.AdvancedTextWidget.withButton;
 
@@ -94,7 +97,7 @@ public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase
 
     public static final int MIN_RADIUS = 2;
     public static final int MIN_DEPTH = 4;
-    private final int[] bounds = { 0, 1, 1, 1, 1, 1 };
+    private final int[] bounds = { 0, 4, 2, 2, 2, 2 };
     private CleanroomType cleanroomType = null;
     private int cleanAmount;
 
@@ -104,9 +107,15 @@ public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase
     private final CleanroomLogic cleanroomLogic;
     private final Collection cleanroomReceivers = new HashSet<>();
 
+    /**
+     * Reverse map from enum facing -> relative direction, refreshed on every setFrontFacing(...) call
+     */
+    private final Map facingMap = new HashMap<>();
+
     public MetaTileEntityCleanroom(ResourceLocation metaTileEntityId) {
         super(metaTileEntityId);
         this.cleanroomLogic = new CleanroomLogic(this, GTValues.LV);
+        updateFacingMap();
     }
 
     @Override
@@ -129,11 +138,26 @@ protected void formStructure(String name) {
         ICleanroomFilter type = allSameType(GregTechAPI.CLEANROOM_FILTERS, getSubstructure(name).getCache());
         if (type == null) {
             invalidateStructure(name);
-        } else {
-            this.cleanroomFilter = type;
-            this.cleanroomType = type.getCleanroomType();
+            return;
         }
 
+        this.cleanroomFilter = type;
+        this.cleanroomType = type.getCleanroomType();
+
+        forEachFormed(name, info -> {
+            TileEntity te = info.getTileEntity();
+            if (!(te instanceof IGregTechTileEntity gtte)) return;
+
+            MetaTileEntity mte = gtte.getMetaTileEntity();
+
+            if (!(mte instanceof ICleanroomReceiver receiver)) return;
+
+            if (receiver.getCleanroom() != this) {
+                receiver.setCleanroom(this);
+                cleanroomReceivers.add(receiver);
+            }
+        });
+
         // max progress is based on the dimensions of the structure: (x^3)-(x^2)
         // taller cleanrooms take longer than wider ones
         // minimum of 100 is a 5x5x5 cleanroom: 125-25=100 ticks
@@ -186,16 +210,58 @@ public boolean allowsFlip() {
     @Override
     protected IBlockPattern createStructurePattern() {
         TraceabilityPredicate wallPredicate = states(getCasingState(), getGlassState());
-        TraceabilityPredicate basePredicate = autoAbilities().or(abilities(MultiblockAbility.INPUT_ENERGY)
+        TraceabilityPredicate energyPredicate = autoAbilities().or(abilities(MultiblockAbility.INPUT_ENERGY)
                 .setMinGlobalLimited(1).setMaxGlobalLimited(3));
 
+        TraceabilityPredicate edgePredicate = states(getCasingState())
+                .or(energyPredicate);
+        TraceabilityPredicate facePredicate = wallPredicate
+                .or(energyPredicate)
+                .or(doorPredicate().setMaxGlobalLimited(8))
+                .or(abilities(MultiblockAbility.PASSTHROUGH_HATCH).setMaxGlobalLimited(30));
+        TraceabilityPredicate filterPredicate = filterPredicate();
+        TraceabilityPredicate innerPredicate = innerPredicate();
+        TraceabilityPredicate verticalEdgePredicate = edgePredicate
+                .or(states(getGlassState()));
+
         return FactoryExpandablePattern.start(RelativeDirection.UP, RelativeDirection.RIGHT, RelativeDirection.FRONT)
                 .boundsFunction((w, c, f, u) -> bounds)
                 .predicateFunction((c, b) -> {
-
+                    // controller always at origin
                     if (c.origin()) return selfPredicate();
 
-                    return wallPredicate;
+                    int intersects = 0;
+
+                    // aisle dir is up, so its bounds[0] and bounds[1]
+                    boolean topAisle = c.get(0) == b[0];
+                    boolean botAisle = c.get(0) == -b[1];
+
+                    if (topAisle || botAisle) intersects++;
+                    // negative signs for the LEFT and BACK ordinals
+                    // string dir is right, so its bounds[2] and bounds[3]
+                    if (c.get(1) == -b[2] || c.get(1) == b[3]) intersects++;
+                    // char dir is front, so its bounds[4] and bounds[5]
+                    if (c.get(2) == b[4] || c.get(2) == -b[5]) intersects++;
+
+//                    GTLog.logger.info(intersects + " intersects at " + c);
+
+                    // more than or equal to 2 intersects means it is an edge
+                    if (intersects >= 2) {
+                        if (topAisle || botAisle) return edgePredicate;
+                        return verticalEdgePredicate;
+                    }
+
+                    // 1 intersect means it is a face
+                    if (intersects == 1) {
+                        if (topAisle) {
+                            return filterPredicate;
+                        }
+
+                        return facePredicate;
+                    }
+
+                    // intersects == 0, so its not a face
+                    return innerPredicate;
                 })
                 .build();
     }
@@ -232,35 +298,37 @@ protected IBlockState getGlassState() {
     @NotNull
     protected static TraceabilityPredicate doorPredicate() {
         return new TraceabilityPredicate(
-                blockWorldState -> blockWorldState.getBlockState().getBlock() instanceof BlockDoor);
+                (worldState, patternState) -> worldState.getBlockState().getBlock() instanceof BlockDoor);
     }
 
     @NotNull
     protected TraceabilityPredicate innerPredicate() {
-        return new TraceabilityPredicate(blockWorldState -> {
+        return new TraceabilityPredicate((worldState, patternState) -> {
             // all non-MetaTileEntities are allowed inside by default
-            TileEntity tileEntity = blockWorldState.getTileEntity();
+            TileEntity tileEntity = worldState.getTileEntity();
             if (!(tileEntity instanceof IGregTechTileEntity)) return true;
 
             MetaTileEntity metaTileEntity = ((IGregTechTileEntity) tileEntity).getMetaTileEntity();
 
             // always ban other cleanrooms, can cause problems otherwise
-            if (metaTileEntity instanceof ICleanroomProvider)
-                return false;
+            if (metaTileEntity instanceof ICleanroomProvider) return false;
 
-            if (isMachineBanned(metaTileEntity))
-                return false;
+            return !isMachineBanned(metaTileEntity);
+        });
+    }
 
-            // the machine does not need a cleanroom, so do nothing more
-            if (!(metaTileEntity instanceof ICleanroomReceiver cleanroomReceiver)) return true;
+    @Override
+    public void setFrontFacing(EnumFacing facing) {
+        super.setFrontFacing(facing);
+        updateFacingMap();
+    }
 
-            // give the machine this cleanroom if it doesn't have this one
-            if (cleanroomReceiver.getCleanroom() != this) {
-                cleanroomReceiver.setCleanroom(this);
-                cleanroomReceivers.add(cleanroomReceiver);
-            }
-            return true;
-        });
+    protected void updateFacingMap() {
+        // cache relative front, back, left, right
+        for (int i = 2; i < 6; i++) {
+            EnumFacing abs = RelativeDirection.VALUES[i].getRelativeFacing(frontFacing, upwardsFacing, false);
+            facingMap.put(abs, RelativeDirection.VALUES[i]);
+        }
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
index 0d57ede47e7..ebe901ce258 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
@@ -166,6 +166,7 @@ public List getControllers() {
         return controllers;
     }
 
+    // todo either remove the cache or fix it, currently doesn't render update properly(see large boiler active)
     public ICubeRenderer getBaseTexture() {
         MultiblockControllerBase controller = getController();
         if (controller != null) {
@@ -280,8 +281,9 @@ public void onRemoval() {
         super.onRemoval();
         if (getWorld().isRemote) return;
 
-        for (MultiblockControllerBase controller : getControllers()) {
-            controller.invalidateStructure("MAIN");
+        List controllers = getControllers();
+        for (int i = 0; i < controllers.size(); i++) {
+            controllers.get(controllers.size() - 1).invalidateStructure("MAIN");
         }
     }
 
diff --git a/src/main/java/gregtech/core/CoreModule.java b/src/main/java/gregtech/core/CoreModule.java
index aecbe907d48..ebdd7ba1041 100644
--- a/src/main/java/gregtech/core/CoreModule.java
+++ b/src/main/java/gregtech/core/CoreModule.java
@@ -222,6 +222,7 @@ public void preInit(FMLPreInitializationEvent event) {
             PSS_BATTERIES.put(MetaBlocks.BATTERY_BLOCK.getState(type), type);
         }
         for (BlockCleanroomCasing.CasingType type : BlockCleanroomCasing.CasingType.values()) {
+            if (type.getCleanroomType() == null) continue;
             CLEANROOM_FILTERS.put(MetaBlocks.CLEANROOM_CASING.getState(type), type);
         }
         /* End API Block Registration */

From 07519dfe41a8a545029cce2ae2c226e8bf65d1b4 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Mon, 12 Aug 2024 14:13:33 -0700
Subject: [PATCH 25/64] assembly line and cleanroom

---
 .../multiblock/MultiblockControllerBase.java  |  1 -
 .../gregtech/api/pattern/GreggyBlockPos.java  |  1 +
 .../api/pattern/pattern/BlockPattern.java     | 10 +++
 .../pattern/pattern/ExpandablePattern.java    | 19 ++---
 .../electric/MetaTileEntityAssemblyLine.java  |  4 +-
 .../electric/MetaTileEntityCleanroom.java     | 77 +++++++++++--------
 .../MetaTileEntityMultiblockPart.java         |  2 +-
 7 files changed, 66 insertions(+), 48 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index e72016e1039..f6e90842977 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -9,7 +9,6 @@
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
-import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.IBlockPattern;
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index 778726e33a3..ffab254aada 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -72,6 +72,7 @@ public GreggyBlockPos set(EnumFacing.Axis axis, GreggyBlockPos other) {
 
     /**
      * Sets a coordinate in the index to the value.
+     * 
      * @param index The index, X = 0, Y = 1, Z = 2.
      * @param value The value to set it to.
      */
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 4900f815a87..78a293cb9e7 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -178,6 +178,15 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
 
         for (int aisleI = 0; aisleI < aisles.length; aisleI++) {
             PatternAisle aisle = aisles[aisleI];
+
+            // check everything below min repeats to ensure its valid
+            // don't check aisle.minRepeats itself since its checked below
+            for (int repeats = 1; repeats < aisle.minRepeats; repeats++) {
+                boolean aisleResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI,
+                        aisleOffset + repeats, isFlipped);
+                if (!aisleResult) return false;
+            }
+
             // if this doesn't get set in the inner loop, then it means all repeats passed
             int actualRepeats = aisle.maxRepeats;
 
@@ -246,6 +255,7 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
         for (int stringI = 0; stringI < dimensions[1]; stringI++) {
             for (int charI = 0; charI < dimensions[2]; charI++) {
                 worldState.setPos(charPos);
+                if (flip) GTLog.logger.info("checked with flip true at " + charPos);
                 TraceabilityPredicate predicate = predicates.get(aisle.charAt(stringI, charI));
 
                 if (predicate != TraceabilityPredicate.ANY) {
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index a306d141b9e..9ebcba16d1d 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -128,25 +128,18 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
 
             absolutes[i] = selected.getRelativeFacing(frontFacing, upwardsFacing, isFlipped);
 
-            int ordinal = selected.ordinal(), oppositeOrdinal = selected.oppositeOrdinal();
-
-            boolean axisPositive = absolutes[i].getAxisDirection() == EnumFacing.AxisDirection.POSITIVE;
-
-            if (axisPositive) {
-                positiveCorner.set(i, bounds[ordinal]);
-                negativeCorner.set(i, -bounds[oppositeOrdinal]);
-            } else {
-                negativeCorner.set(i, -bounds[ordinal]);
-                positiveCorner.set(i, bounds[oppositeOrdinal]);
-            }
+            negativeCorner.set(i, -bounds[selected.oppositeOrdinal()]);
+            positiveCorner.set(i, bounds[selected.ordinal()]);
         }
 
         worldState.setWorld(world);
         // this translates from the relative coordinates to world coordinates
         GreggyBlockPos translation = new GreggyBlockPos(centerPos);
 
-        // SOUTH, UP, EAST means point is +z, line is +y, plane is +x. this basically means the x val of the iter is aisle count, y is str count, and z is char count.
-        for (GreggyBlockPos pos : GreggyBlockPos.allInBox(negativeCorner, positiveCorner, EnumFacing.SOUTH, EnumFacing.UP, EnumFacing.EAST)) {
+        // SOUTH, UP, EAST means point is +z, line is +y, plane is +x. this basically means the x val of the iter is
+        // aisle count, y is str count, and z is char count.
+        for (GreggyBlockPos pos : GreggyBlockPos.allInBox(negativeCorner, positiveCorner, EnumFacing.SOUTH,
+                EnumFacing.UP, EnumFacing.EAST)) {
 
             // test first before using .add() which mutates the pos
             TraceabilityPredicate predicate = predicateFunction.apply(pos, bounds);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
index 1c08627cbd0..19c0850a6a6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
@@ -80,9 +80,9 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     protected BlockPattern createStructurePattern() {
         FactoryBlockPattern pattern = FactoryBlockPattern.start(RIGHT, UP, FRONT)
-                .aisle("FOF", "RTR", "GAD", " Y ")
-                .aisle("FIF", "RTR", "GAD", " Y ").setRepeatable(3, 15)
                 .aisle("FIF", "RTR", "GAS", " Y ")
+                .aisle("FIF", "RTR", "GAD", " Y ").setRepeatable(3, 16)
+                .aisle("FOF", "RTR", "GAD", " Y ")
                 .where('S', selfPredicate())
                 .where('F', states(getCasingState())
                         .or(autoAbilities(false, true, false, false, false, false, false))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index 5da9bb5ca2b..f8ba08513c4 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -28,7 +28,6 @@
 import gregtech.api.pattern.pattern.FactoryExpandablePattern;
 import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.util.BlockInfo;
-import gregtech.api.util.GTLog;
 import gregtech.api.util.GTUtility;
 import gregtech.api.util.Mods;
 import gregtech.api.util.RelativeDirection;
@@ -97,7 +96,9 @@ public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase
 
     public static final int MIN_RADIUS = 2;
     public static final int MIN_DEPTH = 4;
-    private final int[] bounds = { 0, 4, 2, 2, 2, 2 };
+    public static final int MAX_RADIUS = 7;
+    public static final int MAX_DEPTH = 14;
+    private final int[] bounds = { 0, MIN_DEPTH, MIN_RADIUS, MIN_RADIUS, MIN_RADIUS, MIN_RADIUS };
     private CleanroomType cleanroomType = null;
     private int cleanAmount;
 
@@ -115,7 +116,6 @@ public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase
     public MetaTileEntityCleanroom(ResourceLocation metaTileEntityId) {
         super(metaTileEntityId);
         this.cleanroomLogic = new CleanroomLogic(this, GTValues.LV);
-        updateFacingMap();
     }
 
     @Override
@@ -123,7 +123,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
         return new MetaTileEntityCleanroom(metaTileEntityId);
     }
 
-    protected void initializeAbilities() {
+    private void initializeAbilities() {
         this.energyContainer = new EnergyContainerList(getAbilities(MultiblockAbility.INPUT_ENERGY));
     }
 
@@ -190,9 +190,6 @@ protected void updateFormedValid() {
 
     @Override
     public void checkStructurePattern() {
-        if (!this.isStructureFormed()) {
-            reinitializeStructurePattern();
-        }
         super.checkStructurePattern();
     }
 
@@ -243,7 +240,7 @@ protected IBlockPattern createStructurePattern() {
                     // char dir is front, so its bounds[4] and bounds[5]
                     if (c.get(2) == b[4] || c.get(2) == -b[5]) intersects++;
 
-//                    GTLog.logger.info(intersects + " intersects at " + c);
+                    // GTLog.logger.info(intersects + " intersects at " + c);
 
                     // more than or equal to 2 intersects means it is an edge
                     if (intersects >= 2) {
@@ -253,10 +250,7 @@ protected IBlockPattern createStructurePattern() {
 
                     // 1 intersect means it is a face
                     if (intersects == 1) {
-                        if (topAisle) {
-                            return filterPredicate;
-                        }
-
+                        if (topAisle) return filterPredicate;
                         return facePredicate;
                     }
 
@@ -389,35 +383,54 @@ protected void addDisplayText(List textList) {
                 .addWorkingStatusLine()
                 .addProgressLine(getProgressPercent() / 100.0);
 
-        ITextComponent button = new TextComponentString("height: " + bounds[1]);
+        textList.add(getWithButton("North: ", EnumFacing.NORTH));
+        textList.add(getWithButton("West: ", EnumFacing.WEST));
+        textList.add(getWithButton("South: ", EnumFacing.SOUTH));
+        textList.add(getWithButton("East: ", EnumFacing.EAST));
+        textList.add(getWithButton("Height: ", EnumFacing.DOWN));
+    }
+
+    protected ITextComponent getWithButton(String text, EnumFacing facing) {
+        RelativeDirection relative = facing == EnumFacing.DOWN ? RelativeDirection.DOWN : facingMap.get(facing);
+        if (relative == null)
+            return new TextComponentString("null value at facingMap.get(EnumFacing." + facing.getName() + ")");
+
+        String name = relative.name();
+
+        ITextComponent button = new TextComponentString(text + bounds[relative.ordinal()]);
         button.appendText(" ");
-        button.appendSibling(withButton(new TextComponentString("[-]"), "subh"));
+        button.appendSibling(withButton(new TextComponentString("[-]"), name + ":-"));
         button.appendText(" ");
-        button.appendSibling(withButton(new TextComponentString("[+]"), "addh"));
-        textList.add(button);
-
-        ITextComponent button2 = new TextComponentString("left: " + bounds[2]);
-        button2.appendText(" ");
-        button2.appendSibling(withButton(new TextComponentString("[-]"), "subl"));
-        button2.appendText(" ");
-        button2.appendSibling(withButton(new TextComponentString("[+]"), "addl"));
-        textList.add(button2);
+        button.appendSibling(withButton(new TextComponentString("[+]"), name + ":+"));
+        return button;
     }
 
     @Override
     protected void handleDisplayClick(String componentData, Widget.ClickData clickData) {
         super.handleDisplayClick(componentData, clickData);
 
-        switch (componentData) {
-            case "subh" -> bounds[1] = Math.max(bounds[1], 1);
-            case "addh" -> bounds[1] += 1;
-            case "subl" -> bounds[2] = Math.max(bounds[2], 1);
-            case "addl" -> bounds[2] += 1;
+        String[] data = componentData.split(":");
+
+        switch (data[0]) {
+            case "LEFT" -> bounds[2] = MathHelper.clamp(bounds[2] + getFactor(data[1]), MIN_RADIUS, MAX_RADIUS);
+            case "RIGHT" -> bounds[3] = MathHelper.clamp(bounds[3] + getFactor(data[1]), MIN_RADIUS, MAX_RADIUS);
+            case "FRONT" -> bounds[4] = MathHelper.clamp(bounds[4] + getFactor(data[1]), MIN_RADIUS, MAX_RADIUS);
+            case "BACK" -> bounds[5] = MathHelper.clamp(bounds[5] + getFactor(data[1]), MIN_RADIUS, MAX_RADIUS);
+            case "DOWN" -> bounds[1] = MathHelper.clamp(bounds[1] + getFactor(data[1]), MIN_DEPTH, MAX_DEPTH);
+            default -> {
+                return;
+            }
         }
 
+        writeCustomData(GregtechDataCodes.UPDATE_STRUCTURE_SIZE, buf -> buf.writeVarIntArray(bounds));
+
         getSubstructure("MAIN").clearCache();
     }
 
+    protected static int getFactor(String str) {
+        return "+".equals(str) ? 1 : -1;
+    }
+
     @Override
     protected void addWarningText(List textList) {
         MultiblockDisplayText.builder(textList, isStructureFormed(), false)
@@ -544,7 +557,7 @@ public long getEnergyInputPerSecond() {
     }
 
     public boolean drainEnergy(boolean simulate) {
-        if (true) return true;
+        if (energyContainer == null) return false;
 
         long energyToDrain = isClean() ? 4 :
                 GTValues.VA[getEnergyTier()];
@@ -569,7 +582,9 @@ public  T getCapability(Capability capability, EnumFacing side) {
     @Override
     public void receiveCustomData(int dataId, PacketBuffer buf) {
         super.receiveCustomData(dataId, buf);
-        if (dataId == GregtechDataCodes.WORKABLE_ACTIVE) {
+        if (dataId == GregtechDataCodes.UPDATE_STRUCTURE_SIZE) {
+            System.arraycopy(buf.readVarIntArray(), 0, bounds, 0, 6);
+        } else if (dataId == GregtechDataCodes.WORKABLE_ACTIVE) {
             this.cleanroomLogic.setActive(buf.readBoolean());
             scheduleRenderUpdate();
         } else if (dataId == GregtechDataCodes.WORKING_ENABLED) {
@@ -588,9 +603,9 @@ public NBTTagCompound writeToNBT(@NotNull NBTTagCompound data) {
     @Override
     public void readFromNBT(NBTTagCompound data) {
         super.readFromNBT(data);
-        reinitializeStructurePattern();
         this.cleanAmount = data.getInteger("cleanAmount");
         this.cleanroomLogic.readFromNBT(data);
+        updateFacingMap();
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
index ebe901ce258..810f6b2469f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
@@ -342,7 +342,7 @@ public boolean isAttachedToMultiBlock() {
 
     @Override
     public int getDefaultPaintingColor() {
-        return !isAttachedToMultiBlock() && hatchTexture == null ? super.getDefaultPaintingColor() : 0xFFFFFF;
+        return getController() == null && hatchTexture == null ? super.getDefaultPaintingColor() : 0xFFFFFF;
     }
 
     @Override

From 9b50b624f63f2d06c80ab2d1f073870c6d00ad59 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Tue, 13 Aug 2024 20:22:04 -0700
Subject: [PATCH 26/64] assembly line and cleanroom but they work

---
 .../api/capability/GregtechDataCodes.java     |  1 +
 .../api/capability/IMultiblockController.java |  2 +-
 .../impl/SteamMultiblockRecipeLogic.java      |  2 +-
 .../multiblock/FuelMultiblockController.java  |  8 +-
 .../multiblock/IMultiblockPart.java           |  4 +-
 .../multiblock/MultiblockControllerBase.java  | 58 +++++++++----
 .../multiblock/MultiblockWithDisplayBase.java | 10 +--
 .../RecipeMapMultiblockController.java        |  6 +-
 ...ecipeMapPrimitiveMultiblockController.java |  2 +-
 .../RecipeMapSteamMultiblockController.java   |  6 +-
 .../gregtech/api/pattern/GreggyBlockPos.java  | 47 +++++++++-
 .../client/event/ClientEventHandler.java      |  2 +
 .../handler/AABBHighlightRenderer.java        | 84 ++++++++++++++++++
 .../behaviors/MultiblockBuilderBehavior.java  |  4 +-
 .../multi/MetaTileEntityLargeBoiler.java      | 14 +--
 .../multi/MetaTileEntityMultiblockTank.java   |  6 +-
 .../MetaTileEntityPrimitiveBlastFurnace.java  |  2 +-
 .../MetaTileEntityPrimitiveWaterPump.java     |  2 +-
 .../MetaTileEntityActiveTransformer.java      |  2 +-
 .../electric/MetaTileEntityAssemblyLine.java  |  2 +-
 .../electric/MetaTileEntityCleanroom.java     | 86 ++++++++++++++++---
 .../electric/MetaTileEntityCrackingUnit.java  |  4 +-
 .../electric/MetaTileEntityDataBank.java      |  6 +-
 .../MetaTileEntityDistillationTower.java      |  2 +-
 .../MetaTileEntityElectricBlastFurnace.java   |  4 +-
 .../electric/MetaTileEntityFluidDrill.java    | 10 +--
 .../electric/MetaTileEntityFusionReactor.java |  4 +-
 .../multi/electric/MetaTileEntityHPCA.java    | 14 +--
 .../electric/MetaTileEntityLargeMiner.java    | 14 +--
 .../electric/MetaTileEntityMultiSmelter.java  |  4 +-
 .../electric/MetaTileEntityNetworkSwitch.java |  8 +-
 .../MetaTileEntityPowerSubstation.java        |  6 +-
 .../MetaTileEntityProcessingArray.java        |  4 +-
 .../electric/MetaTileEntityPyrolyseOven.java  |  4 +-
 .../MetaTileEntityResearchStation.java        |  6 +-
 .../MetaTileEntityCentralMonitor.java         | 10 +--
 .../MetaTileEntityLargeCombustionEngine.java  | 10 +--
 .../generator/MetaTileEntityLargeTurbine.java | 12 +--
 .../MetaTileEntityComputationHatch.java       |  6 +-
 .../MetaTileEntityObjectHolder.java           |  2 +-
 .../multi/steam/MetaTileEntitySteamOven.java  |  2 +-
 .../MetaTileEntityCharcoalPileIgniter.java    |  4 +-
 .../provider/MaintenanceDataProvider.java     |  2 +-
 .../provider/MultiblockDataProvider.java      |  2 +-
 .../provider/MaintenanceInfoProvider.java     |  2 +-
 .../provider/MultiblockInfoProvider.java      |  2 +-
 .../loaders/recipe/BatteryRecipes.java        | 19 ++--
 47 files changed, 363 insertions(+), 150 deletions(-)
 create mode 100644 src/main/java/gregtech/client/renderer/handler/AABBHighlightRenderer.java

diff --git a/src/main/java/gregtech/api/capability/GregtechDataCodes.java b/src/main/java/gregtech/api/capability/GregtechDataCodes.java
index 6054957ac0a..80c5a853c7a 100644
--- a/src/main/java/gregtech/api/capability/GregtechDataCodes.java
+++ b/src/main/java/gregtech/api/capability/GregtechDataCodes.java
@@ -80,6 +80,7 @@ public static int assignId() {
     public static final int UPDATE_STRUCTURE_SIZE = assignId();
     public static final int STRUCTURE_FORMED = assignId();
     public static final int VARIANT_RENDER_UPDATE = assignId();
+    public static final int RENDER_UPDATE = assignId();
     public static final int IS_TAPED = assignId();
     public static final int STORE_MAINTENANCE = assignId();
     public static final int STORE_TAPED = assignId();
diff --git a/src/main/java/gregtech/api/capability/IMultiblockController.java b/src/main/java/gregtech/api/capability/IMultiblockController.java
index 34a3272420b..6fb31c54c0c 100644
--- a/src/main/java/gregtech/api/capability/IMultiblockController.java
+++ b/src/main/java/gregtech/api/capability/IMultiblockController.java
@@ -2,7 +2,7 @@
 
 public interface IMultiblockController {
 
-    boolean isStructureFormed();
+    boolean isStructureFormed(String name);
 
     default boolean isStructureObstructed() {
         return false;
diff --git a/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java
index 2c05b255d40..a414689b121 100644
--- a/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java
+++ b/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java
@@ -79,7 +79,7 @@ private void combineSteamTanks() {
     public void update() {
         // Fixes an annoying GTCE bug in AbstractRecipeLogic
         RecipeMapSteamMultiblockController controller = (RecipeMapSteamMultiblockController) metaTileEntity;
-        if (isActive && !controller.isStructureFormed()) {
+        if (isActive && !controller.isStructureFormed("MAIN")) {
             progressTime = 0;
             wasActiveAndNeedsUpdate = true;
         }
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/FuelMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/FuelMultiblockController.java
index 790a150ec0c..2a9d50fd4d7 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/FuelMultiblockController.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/FuelMultiblockController.java
@@ -44,7 +44,7 @@ protected void initializeAbilities() {
     protected void addDisplayText(List textList) {
         MultiblockFuelRecipeLogic recipeLogic = (MultiblockFuelRecipeLogic) recipeMapWorkable;
 
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeLogic.isWorkingEnabled(), recipeLogic.isActive())
                 .addEnergyProductionLine(getMaxVoltage(), recipeLogic.getRecipeEUt())
                 .addFuelNeededLine(recipeLogic.getRecipeFluidInputInfo(), recipeLogic.getPreviousRecipeDuration())
@@ -62,13 +62,13 @@ protected long getMaxVoltage() {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
                 .addLowDynamoTierLine(isDynamoTierTooLow())
                 .addMaintenanceProblemLines(getMaintenanceProblems());
     }
 
     protected boolean isDynamoTierTooLow() {
-        if (isStructureFormed()) {
+        if (isStructureFormed("MAIN")) {
             IEnergyContainer energyContainer = recipeMapWorkable.getEnergyContainer();
             if (energyContainer != null && energyContainer.getEnergyCapacity() > 0) {
                 long maxVoltage = Math.max(energyContainer.getInputVoltage(), energyContainer.getOutputVoltage());
@@ -144,7 +144,7 @@ protected void addFuelText(List textList) {
         int fuelCapacity = 0;
         FluidStack fuelStack = null;
         MultiblockFuelRecipeLogic recipeLogic = (MultiblockFuelRecipeLogic) recipeMapWorkable;
-        if (isStructureFormed() && recipeLogic.getInputFluidStack() != null && getInputFluidInventory() != null) {
+        if (isStructureFormed("MAIN") && recipeLogic.getInputFluidStack() != null && getInputFluidInventory() != null) {
             fuelStack = recipeLogic.getInputFluidStack().copy();
             fuelStack.amount = Integer.MAX_VALUE;
             int[] fuelAmount = getTotalFluidAmount(fuelStack, getInputFluidInventory());
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
index 02283b0969b..830aa377535 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
@@ -1,6 +1,7 @@
 package gregtech.api.metatileentity.multiblock;
 
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 public interface IMultiblockPart {
 
@@ -24,8 +25,9 @@ default void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
     int getWallshareCount();
 
     /**
-     * Gets the name of the substructure the part is attached to.
+     * Gets the name of the substructure the part is attached to, or null if it is not attached.
      */
+    @Nullable
     String getSubstructureName();
 
     boolean canPartShare(MultiblockControllerBase target, String substructureName);
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index f6e90842977..2c9fd217634 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -83,7 +83,10 @@
 import static gregtech.api.capability.GregtechDataCodes.*;
 
 public abstract class MultiblockControllerBase extends MetaTileEntity implements IMultiblockController {
-
+    protected final Comparator partComparator = Comparator.comparingLong(part -> {
+                MetaTileEntity mte = (MetaTileEntity) part;
+                return ((long) multiblockPartSorter().apply(mte.getPos()) << 32) | mte.getPos().hashCode();
+            });
     /**
      * Null until the first time {@link MultiblockControllerBase#getMatchingShapes()} is called, if it is not overriden
      */
@@ -93,10 +96,7 @@ public abstract class MultiblockControllerBase extends MetaTileEntity implements
 
     // treeset here to get logn time for contains, and for automatically sorting itself
     // prioritize the manually specified sorter first, defaulting to the hashcode for tiebreakers
-    private final NavigableSet multiblockParts = new TreeSet<>(Comparator.comparingLong(part -> {
-        MetaTileEntity mte = (MetaTileEntity) part;
-        return ((long) multiblockPartSorter().apply(mte.getPos()) << 32) | mte.getPos().hashCode();
-    }));
+    private final NavigableSet multiblockParts = new TreeSet<>(partComparator);
 
     protected EnumFacing upwardsFacing = EnumFacing.NORTH;
     protected final Object2ObjectMap structures = new Object2ObjectOpenHashMap<>();
@@ -125,7 +125,7 @@ public void update() {
             }
             // DummyWorld is the world for the JEI preview. We do not want to update the Multi in this world,
             // besides initially forming it in checkStructurePattern
-            if (isStructureFormed() && !(getWorld() instanceof DummyWorld)) {
+            if (isStructureFormed("MAIN") && !(getWorld() instanceof DummyWorld)) {
                 updateFormedValid();
             }
         }
@@ -139,7 +139,7 @@ public void update() {
     /**
      * @return structure pattern of this multiblock
      */
-    // todo fix central monitor, charcoal pile igniter, and cleanroom(and vacuum freezer)
+    // todo fix central monitor, charcoal pile igniter, (and vacuum freezer)
     @NotNull
     protected abstract IBlockPattern createStructurePattern();
 
@@ -289,8 +289,8 @@ public static TraceabilityPredicate states(IBlockState... allowedStates) {
     public static TraceabilityPredicate frames(Material... frameMaterials) {
         return states(Arrays.stream(frameMaterials).map(m -> MetaBlocks.FRAMES.get(m).getBlock(m))
                 .toArray(IBlockState[]::new))
-                        .or(new TraceabilityPredicate(blockWorldState -> {
-                            TileEntity tileEntity = blockWorldState.getTileEntity();
+                        .or(new TraceabilityPredicate((worldState, patternState) -> {
+                            TileEntity tileEntity = worldState.getTileEntity();
                             if (!(tileEntity instanceof IPipeTilepipeTile)) {
                                 return false;
                             }
@@ -417,6 +417,8 @@ public void checkStructurePattern(String name) {
                 if (result.getState() == PatternState.EnumCheckState.VALID_UNCACHED) {
                     // add any new parts, because removal of parts is impossible
                     // it is possible for old parts to persist, so check that
+                    List> addedParts = new ArrayList<>();
+
                     forEachMultiblockPart(name, part -> {
                         // this part is already added, so igore it
                         if (multiblockParts.contains(part)) return true;
@@ -427,12 +429,20 @@ public void checkStructurePattern(String name) {
                             return false;
                         }
                         part.addToMultiBlock(this, name);
-                        if (part instanceof IMultiblockAbilityPartabilityPart) {
+                        if (part instanceof IMultiblockAbilityPart abilityPart) {
                             // noinspection unchecked
-                            registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
+                            addedParts.add((IMultiblockAbilityPart) abilityPart);
                         }
                         return true;
                     });
+
+                    // another bandaid fix, see below todo
+                    addedParts.sort(partComparator);
+
+                    for (IMultiblockAbilityPart part : addedParts) {
+                        registerMultiblockAbility(part);
+                    }
+
                     formStructure(name);
                 }
                 return;
@@ -456,12 +466,21 @@ public void checkStructurePattern(String name) {
                 // parts *should* not have this controller added
                 multiblockParts.add(part);
                 part.addToMultiBlock(this, name);
-                if (part instanceof IMultiblockAbilityPartabilityPart) {
+//                if (part instanceof IMultiblockAbilityPartabilityPart) {
+//                    // noinspection unchecked
+//                    registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
+//                }
+                return true;
+            });
+
+            // todo this is maybe a bandaid fix, maybe use NavigableSet instead of using List and relying on the NavigableSet of multiblockParts?
+            for (IMultiblockPart part : multiblockParts) {
+                if (name.equals(part.getSubstructureName()) && part instanceof IMultiblockAbilityPart abilityPart) {
                     // noinspection unchecked
                     registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
                 }
-                return true;
-            });
+            }
+
             formStructure(name);
         } else { // structure check fails
             if (result.isFormed()) { // invalidate if not already
@@ -507,7 +526,6 @@ protected void registerMultiblockAbility(IMultiblockAbilityPart part) {
         part.registerAbilities(abilityList);
     }
 
-    // todo do
     protected void forEachFormed(String name, Consumer action) {
         Long2ObjectMap cache = getSubstructure(name).getCache();
         for (BlockInfo info : cache.values()) {
@@ -587,7 +605,6 @@ public  List getAbilities(MultiblockAbility ability) {
         return Collections.unmodifiableList(rawList);
     }
 
-    // todo fix/update usages of this
     public NavigableSet getMultiblockParts() {
         return Collections.unmodifiableNavigableSet(multiblockParts);
     }
@@ -640,7 +657,7 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
                 getSubstructure(name).getPatternState().setFormed(buf.readBoolean());
             }
 
-            if (!isStructureFormed()) {
+            if (!isStructureFormed("MAIN")) {
                 GregTechAPI.soundManager.stopTileSound(getPos());
             }
         }
@@ -657,6 +674,10 @@ public  T getCapability(Capability capability, EnumFacing side) {
         return null;
     }
 
+    /**
+     * Use {@link MultiblockControllerBase#isStructureFormed(String)} instead!
+     */
+    @Deprecated
     public boolean isStructureFormed() {
         return isStructureFormed("MAIN");
     }
@@ -710,7 +731,7 @@ public boolean onRightClick(EntityPlayer playerIn, EnumHand hand, EnumFacing fac
         if (super.onRightClick(playerIn, hand, facing, hitResult))
             return true;
 
-        if (this.getWorld().isRemote && !this.isStructureFormed() && playerIn.isSneaking() &&
+        if (this.getWorld().isRemote && !this.isStructureFormed("MAIN") && playerIn.isSneaking() &&
                 playerIn.getHeldItem(hand).isEmpty()) {
             MultiblockPreviewRenderer.renderMultiBlockPreview(this, 60000);
             return true;
@@ -792,7 +813,6 @@ public int getDefaultPaintingColor() {
     }
 
     public void explodeMultiblock(float explosionPower) {
-        ;
         for (IMultiblockPart part : multiblockParts) {
             part.removeFromMultiBlock(this);
             ((MetaTileEntity) part).doExplosion(explosionPower);
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java
index b1a26a7d4ee..3bcf15174c1 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java
@@ -269,7 +269,7 @@ public boolean isMufflerFaceFree() {
         if (hasMufflerMechanics() && getAbilities(MultiblockAbility.MUFFLER_HATCH).size() == 0)
             return false;
 
-        return isStructureFormed() && hasMufflerMechanics() &&
+        return isStructureFormed("MAIN") && hasMufflerMechanics() &&
                 getAbilities(MultiblockAbility.MUFFLER_HATCH).get(0).isFrontFaceFree();
     }
 
@@ -302,7 +302,7 @@ protected void setRecoveryItems(ItemStack... recoveryItems) {
      * @return whether the current multiblock is active or not
      */
     public boolean isActive() {
-        return isStructureFormed();
+        return isStructureFormed("MAIN");
     }
 
     @Override
@@ -366,7 +366,7 @@ protected TraceabilityPredicate maintenancePredicate() {
      * to use translation, use TextComponentTranslation
      */
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed());
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"));
     }
 
     /**
@@ -512,7 +512,7 @@ protected Widget getFlexButton(int x, int y, int width, int height) {
      * Recommended to only display warnings if the structure is already formed.
      */
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
                 .addMaintenanceProblemLines(getMaintenanceProblems());
     }
 
@@ -521,7 +521,7 @@ protected void addWarningText(List textList) {
      * Prioritized over any warnings provided by {@link MultiblockWithDisplayBase#addWarningText}.
      */
     protected void addErrorText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .addMufflerObstructedLine(hasMufflerMechanics() && !isMufflerFaceFree());
     }
 
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java
index c1885f1056b..967d27b76e0 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java
@@ -113,7 +113,7 @@ protected void updateFormedValid() {
 
     @Override
     public boolean isActive() {
-        return isStructureFormed() && recipeMapWorkable.isActive() && recipeMapWorkable.isWorkingEnabled();
+        return isStructureFormed("MAIN") && recipeMapWorkable.isActive() && recipeMapWorkable.isWorkingEnabled();
     }
 
     protected void initializeAbilities() {
@@ -144,7 +144,7 @@ protected boolean allowSameFluidFillForOutputs() {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(recipeMapWorkable.getEnergyContainer())
                 .addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
@@ -155,7 +155,7 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
                 .addLowPowerLine(recipeMapWorkable.isHasNotEnoughEnergy())
                 .addMaintenanceProblemLines(getMaintenanceProblems());
     }
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java
index c7cbe408016..3d54883b449 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java
@@ -90,7 +90,7 @@ public SoundEvent getSound() {
 
     @Override
     protected boolean openGUIOnRightClick() {
-        return isStructureFormed();
+        return isStructureFormed("MAIN");
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java
index 668434b31f6..f567d4bb2a5 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java
@@ -101,7 +101,7 @@ private void resetTileAbilities() {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addCustom(tl -> {
                     // custom steam tank line
@@ -127,9 +127,9 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
                 .addCustom(tl -> {
-                    if (isStructureFormed() && recipeMapWorkable.isHasNotEnoughEnergy()) {
+                    if (isStructureFormed("MAIN") && recipeMapWorkable.isHasNotEnoughEnergy()) {
                         tl.add(TextComponentUtil.translationWithColor(
                                 TextFormatting.YELLOW,
                                 "gregtech.multiblock.steam.low_steam"));
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index ffab254aada..5a0d0e00e2b 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -81,6 +81,21 @@ public GreggyBlockPos set(int index, int value) {
         return this;
     }
 
+    /**
+     * Sets the x value.
+     */
+    public GreggyBlockPos x(int val) { return set(0, val); }
+
+    /**
+     * Sets the y value.
+     */
+    public GreggyBlockPos y(int val) { return set(1, val); }
+
+    /**
+     * Sets the z value.
+     */
+    public GreggyBlockPos z(int val) { return set(2, val); }
+
     /**
      * Sets all 3 coordinates in the given axis order
      * 
@@ -119,6 +134,16 @@ public GreggyBlockPos from(GreggyBlockPos other) {
         return this;
     }
 
+    /**
+     * BlockPos verion of {@link GreggyBlockPos#from(GreggyBlockPos)}
+     */
+    public GreggyBlockPos from(BlockPos other) {
+        pos[0] = other.getX();
+        pos[1] = other.getY();
+        pos[2] = other.getZ();
+        return this;
+    }
+
     /**
      * Equivalent to calling {@link GreggyBlockPos#offset(EnumFacing, int)} with amount set to 1
      */
@@ -222,6 +247,21 @@ public int get(EnumFacing.Axis axis) {
         return pos[axis.ordinal()];
     }
 
+    /**
+     * Gets the x value.
+     */
+    public int x() { return get(0); }
+
+    /**
+     * Gets the y value.
+     */
+    public int y() { return get(1); }
+
+    /**
+     * Gets the z value.
+     */
+    public int z() { return get(2); }
+
     /**
      * Gets a copy of the internal array, in xyz.
      */
@@ -233,9 +273,14 @@ public GreggyBlockPos copy() {
         return new GreggyBlockPos().from(this);
     }
 
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(pos);
+    }
+
     @Override
     public String toString() {
-        return super.toString() + "[x=" + pos[0] + ", y=" + pos[1] + ", z=" + pos[2] + "]";
+        return super.toString() + "{x=" + pos[0] + ", y=" + pos[1] + ", z=" + pos[2] + "}";
     }
 
     /**
diff --git a/src/main/java/gregtech/client/event/ClientEventHandler.java b/src/main/java/gregtech/client/event/ClientEventHandler.java
index 57c79a3afb7..7b26faadc2e 100644
--- a/src/main/java/gregtech/client/event/ClientEventHandler.java
+++ b/src/main/java/gregtech/client/event/ClientEventHandler.java
@@ -8,6 +8,7 @@
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.util.CapesRegistry;
 import gregtech.client.particle.GTParticleManager;
+import gregtech.client.renderer.handler.AABBHighlightRenderer;
 import gregtech.client.renderer.handler.BlockPosHighlightRenderer;
 import gregtech.client.renderer.handler.MultiblockPreviewRenderer;
 import gregtech.client.utils.BloomEffectUtil;
@@ -78,6 +79,7 @@ public static void onClientTick(TickEvent.ClientTickEvent event) {
     public static void onRenderWorldLast(RenderWorldLastEvent event) {
         DepthTextureUtil.renderWorld(event);
         MultiblockPreviewRenderer.renderWorldLastEvent(event);
+        AABBHighlightRenderer.renderWorldLastEvent(event);
         BlockPosHighlightRenderer.renderWorldLastEvent(event);
         GTParticleManager.renderWorld(event);
     }
diff --git a/src/main/java/gregtech/client/renderer/handler/AABBHighlightRenderer.java b/src/main/java/gregtech/client/renderer/handler/AABBHighlightRenderer.java
new file mode 100644
index 00000000000..22b160d2641
--- /dev/null
+++ b/src/main/java/gregtech/client/renderer/handler/AABBHighlightRenderer.java
@@ -0,0 +1,84 @@
+package gregtech.client.renderer.handler;
+
+import com.github.bsideup.jabel.Desugar;
+
+import gregtech.api.pattern.GreggyBlockPos;
+import gregtech.client.utils.RenderBufferHelper;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.EntityPlayerSP;
+import net.minecraft.client.renderer.BufferBuilder;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
+import net.minecraftforge.client.event.RenderWorldLastEvent;
+import net.minecraftforge.fml.relauncher.Side;
+import net.minecraftforge.fml.relauncher.SideOnly;
+
+import org.lwjgl.opengl.GL11;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+import java.util.function.Predicate;
+
+@SideOnly(Side.CLIENT)
+// maybe refactor as subclass of GTParticle? idk
+public class AABBHighlightRenderer {
+    private static final Map rendering = new HashMap<>();
+
+    public static void renderWorldLastEvent(RenderWorldLastEvent event) {
+        EntityPlayerSP p = Minecraft.getMinecraft().player;
+        double doubleX = p.lastTickPosX + (p.posX - p.lastTickPosX) * event.getPartialTicks();
+        double doubleY = p.lastTickPosY + (p.posY - p.lastTickPosY) * event.getPartialTicks();
+        double doubleZ = p.lastTickPosZ + (p.posZ - p.lastTickPosZ) * event.getPartialTicks();
+
+        GlStateManager.pushMatrix();
+        GlStateManager.translate(-doubleX, -doubleY, -doubleZ);
+        // maybe not necessary? idk what it even does, but one time the outline was gray despite it being white and i can't reproduce it
+        GlStateManager.color(1, 1, 1);
+
+        GlStateManager.disableDepth();
+        GlStateManager.disableTexture2D();
+
+        GlStateManager.glLineWidth(5);
+
+        Tessellator tessellator = Tessellator.getInstance();
+        BufferBuilder buffer = tessellator.getBuffer();
+
+        buffer.begin(GL11.GL_LINES, DefaultVertexFormats.POSITION_COLOR);
+
+        long time = System.currentTimeMillis();
+        for (Iterator> iter = rendering.entrySet().iterator(); iter.hasNext();) {
+            Map.Entry entry = iter.next();
+
+            AABBRender aabb = entry.getKey();
+            if (time > aabb.end() || !entry.getValue().getAsBoolean()) iter.remove();
+
+            // todo maybe use GL_QUADS and draw 12 prisms instead of drawing 12 lines? this prevents incorrect scaling, and fix or javadoc the +1 issue
+            RenderBufferHelper.renderCubeFrame(buffer, aabb.from.x(), aabb.from.y(), aabb.from.z(),
+                    aabb.to.x(), aabb.to.y(), aabb.to.z(),
+                    aabb.r, aabb.g, aabb.b, 1);
+        }
+
+        tessellator.draw();
+
+        GlStateManager.enableTexture2D();
+        GlStateManager.enableDepth();
+        GlStateManager.popMatrix();
+    }
+
+    public static void addAABB(AABBRender aabb, BooleanSupplier predicate) {
+        rendering.put(aabb, predicate);
+    }
+
+    public static void removeAABB(AABBRender aabb) {
+        rendering.remove(aabb);
+    }
+
+    @Desugar
+    public record AABBRender(GreggyBlockPos from, GreggyBlockPos to, float r, float g, float b, long end) {};
+}
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index c16693c4729..c03251b80d0 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -41,14 +41,14 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
         if (player.isSneaking()) {
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
-            if (!multiblock.isStructureFormed()) {
+            if (!multiblock.isStructureFormed("MAIN")) {
                 // multiblock.structurePatterns[0].autoBuild(player, multiblock);
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
         } else {
             // If not sneaking, try to show structure debug info (if any) in chat.
-            if (!multiblock.isStructureFormed()) {
+            if (!multiblock.isStructureFormed("MAIN")) {
                 // PatternError error = multiblock.structurePatterns[0].getError();
                 // if (error != null) {
                 // player.sendMessage(
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
index c512cb16c26..d9de77f0a11 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
@@ -96,10 +96,10 @@ private void resetTileAbilities() {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeLogic.isWorkingEnabled(), recipeLogic.isActive())
                 .addCustom(tl -> {
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
                         // Steam Output line
                         ITextComponent steamOutput = TextComponentUtil.stringWithColor(
                                 TextFormatting.AQUA,
@@ -149,7 +149,7 @@ private TextFormatting getNumberColor(int number) {
     @Override
     protected void addWarningText(List textList) {
         super.addWarningText(textList);
-        if (isStructureFormed()) {
+        if (isStructureFormed("MAIN")) {
             int[] waterAmount = getWaterAmount();
             if (waterAmount[0] == 0) {
                 textList.add(TextComponentUtil.translationWithColor(TextFormatting.YELLOW,
@@ -234,7 +234,7 @@ protected ICubeRenderer getFrontOverlay() {
     }
 
     private boolean isFireboxPart(IMultiblockPart sourcePart) {
-        return isStructureFormed() && (((MetaTileEntity) sourcePart).getPos().getY() < getPos().getY());
+        return isStructureFormed("MAIN") && (((MetaTileEntity) sourcePart).getPos().getY() < getPos().getY());
     }
 
     @SideOnly(Side.CLIENT)
@@ -321,7 +321,7 @@ protected boolean shouldShowVoidingModeButton() {
 
     @Override
     public double getFillPercentage(int index) {
-        if (!isStructureFormed()) return 0;
+        if (!isStructureFormed("MAIN")) return 0;
         int[] waterAmount = getWaterAmount();
         if (waterAmount[1] == 0) return 0; // no water capacity
         return (1.0 * waterAmount[0]) / waterAmount[1];
@@ -334,7 +334,7 @@ public TextureArea getProgressBarTexture(int index) {
 
     @Override
     public void addBarHoverText(List hoverList, int index) {
-        if (!isStructureFormed()) {
+        if (!isStructureFormed("MAIN")) {
             hoverList.add(TextComponentUtil.translationWithColor(TextFormatting.GRAY,
                     "gregtech.multiblock.invalid_structure"));
         } else {
@@ -360,7 +360,7 @@ public void addBarHoverText(List hoverList, int index) {
      * If there is no water in the boiler (or the structure isn't formed, both of these values will be zero.
      */
     private int[] getWaterAmount() {
-        if (!isStructureFormed()) return new int[] { 0, 0 };
+        if (!isStructureFormed("MAIN")) return new int[] { 0, 0 };
         List tanks = getAbilities(MultiblockAbility.IMPORT_FLUIDS);
         int filled = 0, capacity = 0;
         for (IFluidTank tank : tanks) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
index 719dac5bd54..349798a673c 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
@@ -118,14 +118,14 @@ public boolean hasMaintenanceMechanics() {
     @Override
     public boolean onRightClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing,
                                 CuboidRayTraceResult hitResult) {
-        if (!isStructureFormed())
+        if (!isStructureFormed("MAIN"))
             return false;
         return super.onRightClick(playerIn, hand, facing, hitResult);
     }
 
     @Override
     protected boolean openGUIOnRightClick() {
-        return isStructureFormed();
+        return isStructureFormed("MAIN");
     }
 
     @Override
@@ -162,7 +162,7 @@ public void addInformation(ItemStack stack, @Nullable World player, @NotNull Lis
     @Override
     public  T getCapability(Capability capability, EnumFacing side) {
         if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
-            if (isStructureFormed()) {
+            if (isStructureFormed("MAIN")) {
                 return CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY.cast(fluidInventory);
             } else {
                 return null;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
index 1deb29da5ca..e1f5091f866 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
@@ -108,7 +108,7 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
         super.renderMetaTileEntity(renderState, translation, pipeline);
         getFrontOverlay().renderOrientedState(renderState, translation, pipeline, getFrontFacing(),
                 recipeMapWorkable.isActive(), recipeMapWorkable.isWorkingEnabled());
-        if (recipeMapWorkable.isActive() && isStructureFormed()) {
+        if (recipeMapWorkable.isActive() && isStructureFormed("MAIN")) {
             EnumFacing back = getFrontFacing().getOpposite();
             Matrix4 offset = translation.copy().translate(back.getXOffset(), -0.3, back.getZOffset());
             CubeRendererState op = Textures.RENDER_STATE.get();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
index 3f26d10fa59..aeebfd0be3f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
@@ -56,7 +56,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     public void update() {
         super.update();
-        if (!getWorld().isRemote && getOffsetTimer() % 20 == 0 && isStructureFormed()) {
+        if (!getWorld().isRemote && getOffsetTimer() % 20 == 0 && isStructureFormed("MAIN")) {
             if (biomeModifier == 0) {
                 biomeModifier = getAmount();
             } else if (biomeModifier > 0) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
index 58abfc8ef6d..36c83c111ec 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
@@ -164,7 +164,7 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(true, isActive()) // set to true because we only want a two-state system (running or
                                                     // not running)
                 .setWorkingStatusKeys(
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
index 19c0850a6a6..82aa3973fb8 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
@@ -158,7 +158,7 @@ public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
             }
         } else {
             // controller rendering
-            if (isStructureFormed()) {
+            if (isStructureFormed("MAIN")) {
                 return Textures.GRATE_CASING_STEEL_FRONT;
             } else {
                 return Textures.SOLID_STEEL_CASING;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index f8ba08513c4..6fe37a621e0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -23,6 +23,7 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
+import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.FactoryExpandablePattern;
@@ -33,6 +34,7 @@
 import gregtech.api.util.RelativeDirection;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.client.renderer.ICubeRenderer;
+import gregtech.client.renderer.handler.AABBHighlightRenderer;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.client.utils.TooltipHelper;
 import gregtech.common.ConfigHolder;
@@ -98,6 +100,7 @@ public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase
     public static final int MIN_DEPTH = 4;
     public static final int MAX_RADIUS = 7;
     public static final int MAX_DEPTH = 14;
+    private static final GreggyBlockPos offset = new GreggyBlockPos(1, 1, 1);
     private final int[] bounds = { 0, MIN_DEPTH, MIN_RADIUS, MIN_RADIUS, MIN_RADIUS, MIN_RADIUS };
     private CleanroomType cleanroomType = null;
     private int cleanAmount;
@@ -105,8 +108,10 @@ public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase
     private IEnergyContainer energyContainer;
 
     private ICleanroomFilter cleanroomFilter;
+    private boolean renderingAABB;
     private final CleanroomLogic cleanroomLogic;
     private final Collection cleanroomReceivers = new HashSet<>();
+    private AABBHighlightRenderer.AABBRender aabb;
 
     /**
      * Reverse map from enum facing -> relative direction, refreshed on every setFrontFacing(...) call
@@ -135,6 +140,10 @@ private void resetTileAbilities() {
     protected void formStructure(String name) {
         super.formStructure(name);
         initializeAbilities();
+
+        renderingAABB = false;
+        writeCustomData(GregtechDataCodes.RENDER_UPDATE, buf -> buf.writeBoolean(false));
+
         ICleanroomFilter type = allSameType(GregTechAPI.CLEANROOM_FILTERS, getSubstructure(name).getCache());
         if (type == null) {
             invalidateStructure(name);
@@ -230,15 +239,15 @@ protected IBlockPattern createStructurePattern() {
                     int intersects = 0;
 
                     // aisle dir is up, so its bounds[0] and bounds[1]
-                    boolean topAisle = c.get(0) == b[0];
-                    boolean botAisle = c.get(0) == -b[1];
+                    boolean topAisle = c.x() == b[0];
+                    boolean botAisle = c.x() == -b[1];
 
                     if (topAisle || botAisle) intersects++;
                     // negative signs for the LEFT and BACK ordinals
                     // string dir is right, so its bounds[2] and bounds[3]
-                    if (c.get(1) == -b[2] || c.get(1) == b[3]) intersects++;
+                    if (c.y() == -b[2] || c.y() == b[3]) intersects++;
                     // char dir is front, so its bounds[4] and bounds[5]
-                    if (c.get(2) == b[4] || c.get(2) == -b[5]) intersects++;
+                    if (c.z() == b[4] || c.z() == -b[5]) intersects++;
 
                     // GTLog.logger.info(intersects + " intersects at " + c);
 
@@ -346,12 +355,12 @@ protected boolean isMachineBanned(MetaTileEntity metaTileEntity) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(cleanroomLogic.isWorkingEnabled(), cleanroomLogic.isActive())
                 .addEnergyUsageLine(energyContainer)
                 .addCustom(tl -> {
                     // Cleanliness status line
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
                         ITextComponent cleanState;
                         if (isClean()) {
                             cleanState = TextComponentUtil.translationWithColor(
@@ -379,15 +388,22 @@ protected void addDisplayText(List textList) {
                                 "gregtech.multiblock.cleanroom.low_tier", energyNeeded));
                     }
                 })
+                .addCustom(tl -> {
+                    if (isStructureFormed("MAIN")) return;
+
+                    // todo lang translations
+                    tl.add(getWithButton("North: ", EnumFacing.NORTH));
+                    tl.add(getWithButton("West: ", EnumFacing.WEST));
+                    tl.add(getWithButton("South: ", EnumFacing.SOUTH));
+                    tl.add(getWithButton("East: ", EnumFacing.EAST));
+                    tl.add(getWithButton("Height: ", EnumFacing.DOWN));
+
+                    tl.add(withButton(new TextComponentString(renderingAABB ? "[Disable Outline]" : "[Enable Outline]"), "render:" +
+                            renderingAABB));
+                })
                 .addEnergyUsageExactLine(isClean() ? 4 : GTValues.VA[getEnergyTier()])
                 .addWorkingStatusLine()
                 .addProgressLine(getProgressPercent() / 100.0);
-
-        textList.add(getWithButton("North: ", EnumFacing.NORTH));
-        textList.add(getWithButton("West: ", EnumFacing.WEST));
-        textList.add(getWithButton("South: ", EnumFacing.SOUTH));
-        textList.add(getWithButton("East: ", EnumFacing.EAST));
-        textList.add(getWithButton("Height: ", EnumFacing.DOWN));
     }
 
     protected ITextComponent getWithButton(String text, EnumFacing facing) {
@@ -411,6 +427,12 @@ protected void handleDisplayClick(String componentData, Widget.ClickData clickDa
 
         String[] data = componentData.split(":");
 
+        if ("render".equals(data[0])) {
+            boolean render = !Boolean.parseBoolean(data[1]);
+            renderingAABB = render;
+            writeCustomData(GregtechDataCodes.RENDER_UPDATE, buf -> buf.writeBoolean(render));
+        }
+
         switch (data[0]) {
             case "LEFT" -> bounds[2] = MathHelper.clamp(bounds[2] + getFactor(data[1]), MIN_RADIUS, MAX_RADIUS);
             case "RIGHT" -> bounds[3] = MathHelper.clamp(bounds[3] + getFactor(data[1]), MIN_RADIUS, MAX_RADIUS);
@@ -433,10 +455,10 @@ protected static int getFactor(String str) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
                 .addLowPowerLine(!drainEnergy(true))
                 .addCustom(tl -> {
-                    if (isStructureFormed() && !isClean()) {
+                    if (isStructureFormed("MAIN") && !isClean()) {
                         tl.add(TextComponentUtil.translationWithColor(
                                 TextFormatting.YELLOW,
                                 "gregtech.multiblock.cleanroom.warning_contaminated"));
@@ -470,6 +492,38 @@ public void addInformation(ItemStack stack, @Nullable World player, List
         }
     }
 
+    @SideOnly(Side.CLIENT)
+    protected void renderAABB(boolean render) {
+        if (render) {
+            if (aabb == null) aabb = new AABBHighlightRenderer.AABBRender(new GreggyBlockPos(getPos()), new GreggyBlockPos(getPos()), 1, 1, 1, Long.MAX_VALUE);
+
+            // reset coords
+            aabb.from().from(getPos());
+            aabb.to().from(getPos());
+
+            // ordinal 0 is UP, which is always 0
+            for (int i = 1; i < 6; i++) {
+                EnumFacing facing = RelativeDirection.VALUES[i].getRelativeFacing(getFrontFacing(), getUpwardsFacing(), false);
+                if (facing.getAxisDirection() == EnumFacing.AxisDirection.POSITIVE) {
+                    // from is always absolutely positive
+                    aabb.from().offset(facing, bounds[i]);
+                } else {
+                    // to is always absolutely negative
+                    aabb.to().offset(facing, bounds[i]);
+                }
+            }
+
+            // offset by 1 since the renderer doesn't do it
+            aabb.from().add(offset);
+
+            // this is so scuffed im sorry for going back to kila level code :sob:
+            // surely this won't cause the gc to blow up
+            AABBHighlightRenderer.addAABB(aabb, () -> isValid() && getWorld().isBlockLoaded(getPos(), false) && getWorld().getTileEntity(getPos()) == getHolder());
+        } else {
+            AABBHighlightRenderer.removeAABB(aabb);
+        }
+    }
+
     @Override
     public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation, IVertexOperation[] pipeline) {
         super.renderMetaTileEntity(renderState, translation, pipeline);
@@ -584,12 +638,16 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
         super.receiveCustomData(dataId, buf);
         if (dataId == GregtechDataCodes.UPDATE_STRUCTURE_SIZE) {
             System.arraycopy(buf.readVarIntArray(), 0, bounds, 0, 6);
+            renderAABB(renderingAABB);
         } else if (dataId == GregtechDataCodes.WORKABLE_ACTIVE) {
             this.cleanroomLogic.setActive(buf.readBoolean());
             scheduleRenderUpdate();
         } else if (dataId == GregtechDataCodes.WORKING_ENABLED) {
             this.cleanroomLogic.setWorkingEnabled(buf.readBoolean());
             scheduleRenderUpdate();
+        } else if (dataId == GregtechDataCodes.RENDER_UPDATE) {
+            this.renderingAABB = buf.readBoolean();
+            renderAABB(this.renderingAABB);
         }
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
index abd25566ac2..57cc6ce1afa 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
@@ -81,13 +81,13 @@ public SoundEvent getBreakdownSound() {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(getEnergyContainer())
                 .addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
                 .addCustom(tl -> {
                     // Coil energy discount line
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
                         ITextComponent energyDiscount = TextComponentUtil.stringWithColor(TextFormatting.AQUA,
                                 (100 - 10 * coilTier) + "%");
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
index d61a74f1af3..351a67fb205 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
@@ -202,7 +202,7 @@ public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
             }
         } else {
             // controller rendering
-            if (isStructureFormed()) {
+            if (isStructureFormed("MAIN")) {
                 return Textures.HIGH_POWER_CASING;
             } else {
                 return Textures.COMPUTER_CASING;
@@ -250,7 +250,7 @@ public void addInformation(ItemStack stack, @Nullable World world, @NotNull List
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(true, isActive() && isWorkingEnabled()) // transform into two-state system for display
                 .setWorkingStatusKeys(
                         "gregtech.multiblock.idling",
@@ -262,7 +262,7 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
                 .addLowPowerLine(hasNotEnoughEnergy)
                 .addMaintenanceProblemLines(getMaintenanceProblems());
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
index ee09ef72734..e45bc37c969 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
@@ -87,7 +87,7 @@ public boolean allowsExtendedFacing() {
 
     @Override
     protected void addDisplayText(List textList) {
-        if (isStructureFormed()) {
+        if (isStructureFormed("MAIN")) {
             FluidStack stackInTank = importFluids.drain(Integer.MAX_VALUE, false);
             if (stackInTank != null && stackInTank.amount > 0) {
                 ITextComponent fluidName = TextComponentUtil.setColor(GTUtility.getFluidTranslation(stackInTank),
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index 9635e53ff9b..a356974ac87 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -68,13 +68,13 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(getEnergyContainer())
                 .addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
                 .addCustom(tl -> {
                     // Coil heat capacity line
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
                         ITextComponent heatString = TextComponentUtil.stringWithColor(
                                 TextFormatting.RED,
                                 TextFormattingUtil.formatNumbers(blastFurnaceTemperature) + "K");
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
index 41dd80f7ed3..8f51424e8a7 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
@@ -159,7 +159,7 @@ public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(minerLogic.isWorkingEnabled(), minerLogic.isActive())
                 .setWorkingStatusKeys(
                         "gregtech.multiblock.idling",
@@ -167,7 +167,7 @@ protected void addDisplayText(List textList) {
                         "gregtech.multiblock.miner.drilling")
                 .addEnergyUsageLine(energyContainer)
                 .addCustom(tl -> {
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
                         if (minerLogic.getDrilledFluid() != null) {
                             // Fluid name
                             Fluid drilledFluid = minerLogic.getDrilledFluid();
@@ -204,10 +204,10 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
-                .addLowPowerLine(isStructureFormed() && !drainEnergy(true))
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
+                .addLowPowerLine(isStructureFormed("MAIN") && !drainEnergy(true))
                 .addCustom(tl -> {
-                    if (isStructureFormed() && minerLogic.isInventoryFull()) {
+                    if (isStructureFormed("MAIN") && minerLogic.isInventoryFull()) {
                         tl.add(TextComponentUtil.translationWithColor(
                                 TextFormatting.YELLOW,
                                 "gregtech.machine.miner.invfull"));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
index fb92e89077f..34aa2e49219 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
@@ -307,7 +307,7 @@ protected void updateFormedValid() {
                 setFusionRingColor(0xFF000000 |
                         recipeMapWorkable.getPreviousRecipe().getFluidOutputs().get(0).getFluid().getColor());
             }
-        } else if (!recipeMapWorkable.isWorking() && isStructureFormed()) {
+        } else if (!recipeMapWorkable.isWorking() && isStructureFormed("MAIN")) {
             setFusionRingColor(NO_COLOR);
         }
     }
@@ -566,7 +566,7 @@ public ProgressWidget getWidget(MetaTileEntityFusionReactor instance) {
                         x, y, width, height, texture, moveType)
                                 .setIgnoreColor(true)
                                 .setHoverTextConsumer(
-                                        tl -> MultiblockDisplayText.builder(tl, instance.isStructureFormed())
+                                        tl -> MultiblockDisplayText.builder(tl, instance.isStructureFormed("MAIN"))
                                                 .setWorkingStatus(instance.recipeMapWorkable.isWorkingEnabled(),
                                                         instance.recipeMapWorkable.isActive())
                                                 .addWorkingStatusLine());
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
index daf6fce58a6..9b8dcd9ba05 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
@@ -126,7 +126,7 @@ public int getMaxCWUt(@NotNull Collection seen) {
     public boolean canBridge(@NotNull Collection seen) {
         seen.add(this);
         // don't show a problem if the structure is not yet formed
-        return !isStructureFormed() || hpcaHandler.hasHPCABridge();
+        return !isStructureFormed("MAIN") || hpcaHandler.hasHPCABridge();
     }
 
     @Override
@@ -134,7 +134,7 @@ public void update() {
         super.update();
         // we need to know what components we have on the client
         if (getWorld().isRemote) {
-            if (isStructureFormed()) {
+            if (isStructureFormed("MAIN")) {
                 hpcaHandler.tryGatherClientComponents(getWorld(), getPos(), getFrontFacing(), getUpwardsFacing(),
                         isFlipped());
             } else {
@@ -376,7 +376,7 @@ protected ModularUI.Builder createUITemplate(EntityPlayer entityPlayer) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(true, hpcaHandler.getAllocatedCWUt() > 0) // transform into two-state system for
                 // display
                 .setWorkingStatusKeys(
@@ -384,7 +384,7 @@ protected void addDisplayText(List textList) {
                         "gregtech.multiblock.idling",
                         "gregtech.multiblock.data_bank.providing")
                 .addCustom(tl -> {
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
                         // Energy Usage
                         ITextComponent voltageName = new TextComponentString(
                                 GTValues.VNF[GTUtility.getTierByVoltage(hpcaHandler.getMaxEUt())]);
@@ -419,10 +419,10 @@ private TextFormatting getDisplayTemperatureColor() {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
                 .addLowPowerLine(hasNotEnoughEnergy)
                 .addCustom(tl -> {
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
                         if (temperature > 500) {
                             // Temperature warning
                             tl.add(TextComponentUtil.translationWithColor(
@@ -445,7 +445,7 @@ protected void addWarningText(List textList) {
     @Override
     protected void addErrorText(List textList) {
         super.addErrorText(textList);
-        if (isStructureFormed()) {
+        if (isStructureFormed("MAIN")) {
             if (temperature > 1000) {
                 textList.add(TextComponentUtil.translationWithColor(TextFormatting.RED,
                         "gregtech.multiblock.hpca.error_temperature"));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
index f5fb25a8d6b..52161722432 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
@@ -234,7 +234,7 @@ public void addToolUsages(ItemStack stack, @Nullable World world, List t
     protected void addDisplayText(List textList) {
         super.addDisplayText(textList);
 
-        if (this.isStructureFormed()) {
+        if (this.isStructureFormed("MAIN")) {
             if (energyContainer != null && energyContainer.getEnergyCapacity() > 0) {
                 int energyContainer = getEnergyTier();
                 long maxVoltage = GTValues.V[energyContainer];
@@ -270,7 +270,7 @@ else if (!this.isWorkingEnabled())
     }
 
     private void addDisplayText2(List textList) {
-        if (this.isStructureFormed()) {
+        if (this.isStructureFormed("MAIN")) {
             ITextComponent mCoords = new TextComponentString("    ")
                     .appendSibling(new TextComponentTranslation("gregtech.machine.miner.minex",
                             this.minerLogic.getMineX().get()))
@@ -286,10 +286,10 @@ private void addDisplayText2(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
-                .addLowPowerLine(isStructureFormed() && !drainEnergy(true))
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
+                .addLowPowerLine(isStructureFormed("MAIN") && !drainEnergy(true))
                 .addCustom(tl -> {
-                    if (isStructureFormed() && isInventoryFull) {
+                    if (isStructureFormed("MAIN") && isInventoryFull) {
                         tl.add(TextComponentUtil.translationWithColor(
                                 TextFormatting.YELLOW,
                                 "gregtech.machine.miner.invfull"));
@@ -300,7 +300,7 @@ protected void addWarningText(List textList) {
     @Override
     protected void addErrorText(List textList) {
         super.addErrorText(textList);
-        if (isStructureFormed() && !drainFluid(true)) {
+        if (isStructureFormed("MAIN") && !drainFluid(true)) {
             textList.add(TextComponentUtil.translationWithColor(TextFormatting.RED,
                     "gregtech.machine.miner.multi.needsfluid"));
         }
@@ -445,7 +445,7 @@ private void setCurrentMode(int mode) {
     @Override
     public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing,
                                       CuboidRayTraceResult hitResult) {
-        if (getWorld().isRemote || !this.isStructureFormed())
+        if (getWorld().isRemote || !this.isStructureFormed("MAIN"))
             return true;
 
         if (!this.isActive()) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
index 4a21309ddbd..9c9a6f0e2ce 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
@@ -58,12 +58,12 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(recipeMapWorkable.getEnergyContainer())
                 .addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
                 .addCustom(tl -> {
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
                         // Heating coil discount
                         if (heatingCoilDiscount > 1) {
                             ITextComponent coilDiscount = TextComponentUtil.stringWithColor(
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
index cd28a1dbeac..53f3d6d0818 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
@@ -75,7 +75,7 @@ public void invalidateStructure(String name) {
 
     @Override
     protected int getEnergyUsage() {
-        return isStructureFormed() ? computationHandler.getEUt() : 0;
+        return isStructureFormed("MAIN") ? computationHandler.getEUt() : 0;
     }
 
     @Override
@@ -87,7 +87,7 @@ public int requestCWUt(int cwut, boolean simulate, @NotNull Collection seen) {
         seen.add(this);
-        return isStructureFormed() ? computationHandler.getMaxCWUt(seen) : 0;
+        return isStructureFormed("MAIN") ? computationHandler.getMaxCWUt(seen) : 0;
     }
 
     // allows chaining Network Switches together
@@ -145,7 +145,7 @@ public void addInformation(ItemStack stack, @Nullable World world, @NotNull List
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(true, isActive() && isWorkingEnabled()) // transform into two-state system for display
                 .setWorkingStatusKeys(
                         "gregtech.multiblock.idling",
@@ -159,7 +159,7 @@ protected void addDisplayText(List textList) {
     @Override
     protected void addWarningText(List textList) {
         super.addWarningText(textList);
-        if (isStructureFormed() && computationHandler.hasNonBridgingConnections()) {
+        if (isStructureFormed("MAIN") && computationHandler.hasNonBridgingConnections()) {
             textList.add(TextComponentUtil.translationWithColor(
                     TextFormatting.YELLOW,
                     "gregtech.multiblock.computation.non_bridging.detailed"));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index 57e842518d0..5f0e95f8ac7 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -318,14 +318,14 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(true, isActive() && isWorkingEnabled()) // transform into two-state system for display
                 .setWorkingStatusKeys(
                         "gregtech.multiblock.idling",
                         "gregtech.multiblock.idling",
                         "gregtech.machine.active_transformer.routing")
                 .addCustom(tl -> {
-                    if (isStructureFormed() && energyBank != null) {
+                    if (isStructureFormed("MAIN") && energyBank != null) {
                         BigInteger energyStored = energyBank.getStored();
                         BigInteger energyCapacity = energyBank.getCapacity();
 
@@ -409,7 +409,7 @@ protected void addDisplayText(List textList) {
     @Override
     protected void addWarningText(List textList) {
         super.addWarningText(textList);
-        if (isStructureFormed()) {
+        if (isStructureFormed("MAIN")) {
             if (averageInLastSec < averageOutLastSec) { // decreasing
                 BigInteger timeToDrainSeconds = energyBank.getStored()
                         .divide(BigInteger.valueOf((averageOutLastSec - averageInLastSec) * 20));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
index 0386fe0d3f6..86a261b4916 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
@@ -121,12 +121,12 @@ public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
     protected void addDisplayText(List textList) {
         ProcessingArrayWorkable logic = (ProcessingArrayWorkable) recipeMapWorkable;
 
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(recipeMapWorkable.getEnergyContainer())
                 .addEnergyTierLine(logic.currentMachineStack == ItemStack.EMPTY ? -1 : logic.machineTier)
                 .addCustom(tl -> {
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
 
                         // Machine mode text
                         // Shared text components for both states
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
index 277d409da3f..75394c5e199 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
@@ -100,12 +100,12 @@ protected void formStructure(String name) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(recipeMapWorkable.getEnergyContainer())
                 .addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
                 .addCustom(tl -> {
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
                         int processingSpeed = coilTier == 0 ? 75 : 50 * (coilTier + 1);
                         ITextComponent speedIncrease = TextComponentUtil.stringWithColor(
                                 getSpeedColor(processingSpeed),
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
index ab5b7514b88..6c5ecf6bd73 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
@@ -86,7 +86,7 @@ protected void formStructure(String name) {
     @Override
     public void checkStructurePattern() {
         super.checkStructurePattern();
-        if (isStructureFormed() && objectHolder.getFrontFacing() != getFrontFacing().getOpposite()) {
+        if (isStructureFormed("MAIN") && objectHolder.getFrontFacing() != getFrontFacing().getOpposite()) {
             invalidateStructure("MAIN");
         }
     }
@@ -223,7 +223,7 @@ public void addInformation(ItemStack stack, @Nullable World world, @NotNull List
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .setWorkingStatusKeys(
                         "gregtech.multiblock.idling",
@@ -239,7 +239,7 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
                 .addLowPowerLine(recipeMapWorkable.isHasNotEnoughEnergy())
                 .addLowComputationLine(getRecipeMapWorkable().isHasNotEnoughComputation())
                 .addMaintenanceProblemLines(getMaintenanceProblems());
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index a71972b8326..75f17e61eae 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -256,7 +256,7 @@ private void setActive(boolean isActive) {
     }
 
     public boolean isActive() {
-        return isStructureFormed() && isActive;
+        return isStructureFormed("MAIN") && isActive;
     }
 
     private void clearScreens() {
@@ -281,7 +281,7 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
     protected void addDisplayText(List textList) {
         super.addDisplayText(textList);
         textList.add(new TextComponentTranslation("gregtech.multiblock.central_monitor.height", this.height));
-        if (!isStructureFormed()) {
+        if (!isStructureFormed("MAIN")) {
             ITextComponent buttonText = new TextComponentTranslation(
                     "gregtech.multiblock.central_monitor.height_modify", height);
             buttonText.appendText(" ");
@@ -343,7 +343,7 @@ public void receiveCustomData(int id, PacketBuffer buf) {
         } else if (id == GregtechDataCodes.UPDATE_ACTIVE) {
             this.isActive = buf.readBoolean();
         } else if (id == GregtechDataCodes.STRUCTURE_FORMED) {
-            if (!this.isStructureFormed()) {
+            if (!this.isStructureFormed("MAIN")) {
                 clearScreens();
             }
         }
@@ -470,7 +470,7 @@ public ICubeRenderer getBaseTexture(IMultiblockPart iMultiblockPart) {
 
     @Override
     public boolean shouldRenderInPass(int pass) {
-        if (this.isStructureFormed()) {
+        if (this.isStructureFormed("MAIN")) {
             return pass == 0;
         }
         return false;
@@ -485,7 +485,7 @@ public boolean isGlobalRenderer() {
     @Override
     @SideOnly(Side.CLIENT)
     public void renderMetaTileEntity(double x, double y, double z, float partialTicks) {
-        if (!this.isStructureFormed()) return;
+        if (!this.isStructureFormed("MAIN")) return;
         RenderUtil.useStencil(() -> {
             GlStateManager.pushMatrix();
             RenderUtil.moveToFace(x, y, z, this.frontFacing);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
index 4ba570e0d62..fbd958ca139 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
@@ -70,7 +70,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     protected void addDisplayText(List textList) {
         LargeCombustionEngineWorkableHandler recipeLogic = ((LargeCombustionEngineWorkableHandler) recipeMapWorkable);
 
-        MultiblockDisplayText.Builder builder = MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.Builder builder = MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeLogic.isWorkingEnabled(), recipeLogic.isActive());
 
         if (isExtreme) {
@@ -81,7 +81,7 @@ protected void addDisplayText(List textList) {
 
         builder.addFuelNeededLine(recipeLogic.getRecipeFluidInputInfo(), recipeLogic.getPreviousRecipeDuration())
                 .addCustom(tl -> {
-                    if (isStructureFormed() && recipeLogic.isOxygenBoosted) {
+                    if (isStructureFormed("MAIN") && recipeLogic.isOxygenBoosted) {
                         String key = isExtreme ? "gregtech.multiblock.large_combustion_engine.liquid_oxygen_boosted" :
                                 "gregtech.multiblock.large_combustion_engine.oxygen_boosted";
                         tl.add(TextComponentUtil.translationWithColor(TextFormatting.AQUA, key));
@@ -93,7 +93,7 @@ protected void addDisplayText(List textList) {
     @Override
     protected void addErrorText(List textList) {
         super.addErrorText(textList);
-        if (isStructureFormed()) {
+        if (isStructureFormed("MAIN")) {
             if (checkIntakesObstructed()) {
                 textList.add(TextComponentUtil.translationWithColor(TextFormatting.RED,
                         "gregtech.multiblock.large_combustion_engine.obstructed"));
@@ -281,7 +281,7 @@ public void addBarHoverText(List hoverList, int index) {
             // Lubricant
             int lubricantStored = 0;
             int lubricantCapacity = 0;
-            if (isStructureFormed() && getInputFluidInventory() != null) {
+            if (isStructureFormed("MAIN") && getInputFluidInventory() != null) {
                 // Hunt for tanks with lubricant in them
                 int[] lubricantAmount = getTotalFluidAmount(Materials.Lubricant.getFluid(Integer.MAX_VALUE),
                         getInputFluidInventory());
@@ -302,7 +302,7 @@ public void addBarHoverText(List hoverList, int index) {
             if (isBoostAllowed()) {
                 int oxygenStored = 0;
                 int oxygenCapacity = 0;
-                if (isStructureFormed() && getInputFluidInventory() != null) {
+                if (isStructureFormed("MAIN") && getInputFluidInventory() != null) {
                     // Hunt for tanks with Oxygen or LOX (depending on tier) in them
                     FluidStack oxygenStack = isExtreme ?
                             Materials.Oxygen.getFluid(FluidStorageKeys.LIQUID, Integer.MAX_VALUE) :
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
index 80ebb0be31f..a0aeda378d6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
@@ -90,7 +90,7 @@ public void invalidateStructure(String name) {
     public boolean isRotorFaceFree() {
         IRotorHolder rotorHolder = getRotorHolder();
         if (rotorHolder != null)
-            return isStructureFormed() && getRotorHolder().isFrontFaceFree();
+            return isStructureFormed("MAIN") && getRotorHolder().isFrontFaceFree();
         return false;
     }
 
@@ -116,11 +116,11 @@ protected long getMaxVoltage() {
     protected void addDisplayText(List textList) {
         MultiblockFuelRecipeLogic recipeLogic = (MultiblockFuelRecipeLogic) recipeMapWorkable;
 
-        MultiblockDisplayText.builder(textList, isStructureFormed())
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
                 .setWorkingStatus(recipeLogic.isWorkingEnabled(), recipeLogic.isActive())
                 .addEnergyProductionLine(getMaxVoltage(), recipeLogic.getRecipeEUt())
                 .addCustom(tl -> {
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
                         IRotorHolder rotorHolder = getRotorHolder();
                         if (rotorHolder.getRotorEfficiency() > 0) {
                             ITextComponent efficiencyInfo = TextComponentUtil.stringWithColor(
@@ -139,9 +139,9 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
                 .addCustom(tl -> {
-                    if (isStructureFormed()) {
+                    if (isStructureFormed("MAIN")) {
                         IRotorHolder rotorHolder = getRotorHolder();
                         if (rotorHolder.getRotorEfficiency() > 0) {
                             if (rotorHolder.getRotorDurabilityPercent() <= MIN_DURABILITY_TO_WARN) {
@@ -159,7 +159,7 @@ protected void addWarningText(List textList) {
     @Override
     protected void addErrorText(List textList) {
         super.addErrorText(textList);
-        if (isStructureFormed()) {
+        if (isStructureFormed("MAIN")) {
             if (!isRotorFaceFree()) {
                 textList.add(TextComponentUtil.translationWithColor(TextFormatting.RED,
                         "gregtech.multiblock.turbine.obstructed"));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java
index 85c2bc25173..14694c83868 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java
@@ -53,7 +53,7 @@ public boolean isTransmitter() {
     public int requestCWUt(int cwut, boolean simulate, @NotNull Collection seen) {
         seen.add(this);
         var controller = getController();
-        if (controller == null || !controller.isStructureFormed()) return 0;
+        if (controller == null || !controller.isStructureFormed("MAIN")) return 0;
         if (isTransmitter()) {
             // Ask the Multiblock controller, which *should* be an IOpticalComputationProvider
             if (controller instanceof IOpticalComputationProvider provider) {
@@ -74,7 +74,7 @@ public int requestCWUt(int cwut, boolean simulate, @NotNull Collection seen) {
         seen.add(this);
         var controller = getController();
-        if (controller == null || !controller.isStructureFormed()) return 0;
+        if (controller == null || !controller.isStructureFormed("MAIN")) return 0;
         if (isTransmitter()) {
             // Ask the Multiblock controller, which *should* be an IOpticalComputationProvider
             if (controller instanceof IOpticalComputationProvider provider) {
@@ -96,7 +96,7 @@ public boolean canBridge(@NotNull Collection seen)
         seen.add(this);
         var controller = getController();
         // return true here so that unlinked hatches don't cause problems in multis like the Network Switch
-        if (controller == null || !controller.isStructureFormed()) return true;
+        if (controller == null || !controller.isStructureFormed("MAIN")) return true;
         if (isTransmitter()) {
             // Ask the Multiblock controller, which *should* be an IOpticalComputationProvider
             if (controller instanceof IOpticalComputationProvider provider) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
index b0827af76d1..ce580c08774 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
@@ -103,7 +103,7 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
     public void setFrontFacing(EnumFacing frontFacing) {
         super.setFrontFacing(frontFacing);
         var controller = getController();
-        if (controller != null && controller.isStructureFormed()) {
+        if (controller != null && controller.isStructureFormed("MAIN")) {
             controller.checkStructurePattern();
         }
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
index e582373455e..ae5ff782df2 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
@@ -77,7 +77,7 @@ public IBlockState getFireboxState() {
     }
 
     private boolean isFireboxPart(IMultiblockPart sourcePart) {
-        return isStructureFormed() && (((MetaTileEntity) sourcePart).getPos().getY() < getPos().getY());
+        return isStructureFormed("MAIN") && (((MetaTileEntity) sourcePart).getPos().getY() < getPos().getY());
     }
 
     @SideOnly(Side.CLIENT)
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index 0dd2d7b39a9..c811fdbf5c4 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -296,7 +296,7 @@ private void updateMaxProgressTime() {
     public void update() {
         super.update();
         if (getWorld() != null) {
-            if (!getWorld().isRemote && !this.isStructureFormed() && getOffsetTimer() % 20 == 0) {
+            if (!getWorld().isRemote && !this.isStructureFormed("MAIN") && getOffsetTimer() % 20 == 0) {
                 this.reinitializeStructurePattern();
             } else if (isActive) {
                 BlockPos pos = getPos();
@@ -490,7 +490,7 @@ public static void onItemUse(@NotNull PlayerInteractEvent.RightClickBlock event)
         if (tileEntity instanceof IGregTechTileEntity) {
             mte = ((IGregTechTileEntity) tileEntity).getMetaTileEntity();
         }
-        if (mte instanceof MetaTileEntityCharcoalPileIgniter && ((IMultiblockController) mte).isStructureFormed()) {
+        if (mte instanceof MetaTileEntityCharcoalPileIgniter && ((IMultiblockController) mte).isStructureFormed("MAIN")) {
             if (event.getSide().isClient()) {
                 event.setCanceled(true);
                 event.getEntityPlayer().swingArm(EnumHand.MAIN_HAND);
diff --git a/src/main/java/gregtech/integration/hwyla/provider/MaintenanceDataProvider.java b/src/main/java/gregtech/integration/hwyla/provider/MaintenanceDataProvider.java
index 38719913fd4..ad53210eab8 100644
--- a/src/main/java/gregtech/integration/hwyla/provider/MaintenanceDataProvider.java
+++ b/src/main/java/gregtech/integration/hwyla/provider/MaintenanceDataProvider.java
@@ -67,7 +67,7 @@ public List getWailaBody(ItemStack itemStack, List tooltip, IWai
 
             IMultiblockController controller = accessor.getTileEntity()
                     .getCapability(GregtechCapabilities.CAPABILITY_MULTIBLOCK_CONTROLLER, null);
-            if (controller == null || !controller.isStructureFormed()) {
+            if (controller == null || !controller.isStructureFormed("MAIN")) {
                 return tooltip;
             }
 
diff --git a/src/main/java/gregtech/integration/hwyla/provider/MultiblockDataProvider.java b/src/main/java/gregtech/integration/hwyla/provider/MultiblockDataProvider.java
index 06f2b7c6580..e42d2d8d4d9 100644
--- a/src/main/java/gregtech/integration/hwyla/provider/MultiblockDataProvider.java
+++ b/src/main/java/gregtech/integration/hwyla/provider/MultiblockDataProvider.java
@@ -37,7 +37,7 @@ public void register(@NotNull IWailaRegistrar registrar) {
     @Override
     protected NBTTagCompound getNBTData(IMultiblockController capability, NBTTagCompound tag) {
         NBTTagCompound subTag = new NBTTagCompound();
-        subTag.setBoolean("Formed", capability.isStructureFormed());
+        subTag.setBoolean("Formed", capability.isStructureFormed("MAIN"));
         subTag.setBoolean("Obstructed", capability.isStructureObstructed());
         tag.setTag("gregtech.IMultiblockController", subTag);
         return tag;
diff --git a/src/main/java/gregtech/integration/theoneprobe/provider/MaintenanceInfoProvider.java b/src/main/java/gregtech/integration/theoneprobe/provider/MaintenanceInfoProvider.java
index bf448048a1e..211b1206b1e 100644
--- a/src/main/java/gregtech/integration/theoneprobe/provider/MaintenanceInfoProvider.java
+++ b/src/main/java/gregtech/integration/theoneprobe/provider/MaintenanceInfoProvider.java
@@ -47,7 +47,7 @@ protected void addProbeInfo(IMaintenance capability, IProbeInfo probeInfo, Entit
             if (tileEntity.hasCapability(GregtechCapabilities.CAPABILITY_MULTIBLOCK_CONTROLLER, null)) {
                 // noinspection ConstantConditions
                 if (tileEntity.getCapability(GregtechCapabilities.CAPABILITY_MULTIBLOCK_CONTROLLER, null)
-                        .isStructureFormed()) {
+                        .isStructureFormed("MAIN")) {
                     if (capability.hasMaintenanceProblems()) {
                         if (player.isSneaking()) {
                             int problems = capability.getMaintenanceProblems();
diff --git a/src/main/java/gregtech/integration/theoneprobe/provider/MultiblockInfoProvider.java b/src/main/java/gregtech/integration/theoneprobe/provider/MultiblockInfoProvider.java
index 368e7b91318..8a2cec68153 100644
--- a/src/main/java/gregtech/integration/theoneprobe/provider/MultiblockInfoProvider.java
+++ b/src/main/java/gregtech/integration/theoneprobe/provider/MultiblockInfoProvider.java
@@ -31,7 +31,7 @@ protected Capability getCapability() {
     protected void addProbeInfo(@NotNull IMultiblockController capability, @NotNull IProbeInfo probeInfo,
                                 @NotNull EntityPlayer player, @NotNull TileEntity tileEntity,
                                 @NotNull IProbeHitData data) {
-        if (capability.isStructureFormed()) {
+        if (capability.isStructureFormed("MAIN")) {
             probeInfo.text(TextStyleClass.OK + "{*gregtech.top.valid_structure*}");
             if (capability.isStructureObstructed()) {
                 probeInfo.text(TextFormatting.RED + "{*gregtech.top.obstructed_structure*}");
diff --git a/src/main/java/gregtech/loaders/recipe/BatteryRecipes.java b/src/main/java/gregtech/loaders/recipe/BatteryRecipes.java
index c23ec372712..8fc9440d079 100644
--- a/src/main/java/gregtech/loaders/recipe/BatteryRecipes.java
+++ b/src/main/java/gregtech/loaders/recipe/BatteryRecipes.java
@@ -345,20 +345,21 @@ private static void gemBatteries() {
 
         // Lapotronic Energy Cluster
         ASSEMBLY_LINE_RECIPES.recipeBuilder().EUt(80000).duration(1000)
-                .input(EXTREME_CIRCUIT_BOARD)
-                .input(plate, Europium, 8)
-                .input(circuit, Tier.LuV, 4)
-                .inputNBT(ENERGY_LAPOTRONIC_ORB, NBTMatcher.ANY, NBTCondition.ANY)
-                .input(FIELD_GENERATOR_IV)
-                .input(HIGH_POWER_INTEGRATED_CIRCUIT, 16)
+//                .input(EXTREME_CIRCUIT_BOARD)
+//                .input(plate, Europium, 8)
+//                .input(circuit, Tier.LuV, 4)
+//                .inputNBT(ENERGY_LAPOTRONIC_ORB, NBTMatcher.ANY, NBTCondition.ANY)
+//                .input(FIELD_GENERATOR_IV)
+//                .input(HIGH_POWER_INTEGRATED_CIRCUIT, 16)
+                // todo fix
                 .input(ADVANCED_SMD_DIODE, 8)
                 .input(ADVANCED_SMD_CAPACITOR, 8)
                 .input(ADVANCED_SMD_RESISTOR, 8)
                 .input(ADVANCED_SMD_TRANSISTOR, 8)
                 .input(ADVANCED_SMD_INDUCTOR, 8)
-                .input(wireFine, Platinum, 64)
-                .input(bolt, Naquadah, 16)
-                .fluidInputs(SolderingAlloy.getFluid(L * 5))
+//                .input(wireFine, Platinum, 64)
+//                .input(bolt, Naquadah, 16)
+//                .fluidInputs(SolderingAlloy.getFluid(L * 5))
                 .output(ENERGY_LAPOTRONIC_ORB_CLUSTER)
                 .scannerResearch(ENERGY_LAPOTRONIC_ORB.getStackForm())
                 .buildAndRegister();

From 22e979b024f220e9dffc6d02e956c5acddf9b3aa Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Wed, 14 Aug 2024 19:03:13 -0700
Subject: [PATCH 27/64] central monitor stuff

---
 .../multiblock/MultiblockControllerBase.java  |  24 ++--
 .../gregtech/api/pattern/GreggyBlockPos.java  |  24 +++-
 .../api/pattern/pattern/BlockPattern.java     |   2 +
 .../handler/AABBHighlightRenderer.java        |  13 +-
 .../AdvancedMonitorPluginBehavior.java        | 119 +++++++++---------
 .../electric/MetaTileEntityCleanroom.java     |  14 ++-
 .../MetaTileEntityCentralMonitor.java         |  16 +--
 .../MetaTileEntityCharcoalPileIgniter.java    |   3 +-
 .../loaders/recipe/BatteryRecipes.java        |  18 +--
 9 files changed, 123 insertions(+), 110 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 2c9fd217634..eeba0692079 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -83,10 +83,11 @@
 import static gregtech.api.capability.GregtechDataCodes.*;
 
 public abstract class MultiblockControllerBase extends MetaTileEntity implements IMultiblockController {
+
     protected final Comparator partComparator = Comparator.comparingLong(part -> {
-                MetaTileEntity mte = (MetaTileEntity) part;
-                return ((long) multiblockPartSorter().apply(mte.getPos()) << 32) | mte.getPos().hashCode();
-            });
+        MetaTileEntity mte = (MetaTileEntity) part;
+        return ((long) multiblockPartSorter().apply(mte.getPos()) << 32) | mte.getPos().hashCode();
+    });
     /**
      * Null until the first time {@link MultiblockControllerBase#getMatchingShapes()} is called, if it is not overriden
      */
@@ -429,7 +430,7 @@ public void checkStructurePattern(String name) {
                             return false;
                         }
                         part.addToMultiBlock(this, name);
-                        if (part instanceof IMultiblockAbilityPart abilityPart) {
+                        if (part instanceof IMultiblockAbilityPartabilityPart) {
                             // noinspection unchecked
                             addedParts.add((IMultiblockAbilityPart) abilityPart);
                         }
@@ -466,16 +467,17 @@ public void checkStructurePattern(String name) {
                 // parts *should* not have this controller added
                 multiblockParts.add(part);
                 part.addToMultiBlock(this, name);
-//                if (part instanceof IMultiblockAbilityPartabilityPart) {
-//                    // noinspection unchecked
-//                    registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
-//                }
+                // if (part instanceof IMultiblockAbilityPartabilityPart) {
+                // // noinspection unchecked
+                // registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
+                // }
                 return true;
             });
 
-            // todo this is maybe a bandaid fix, maybe use NavigableSet instead of using List and relying on the NavigableSet of multiblockParts?
+            // todo this is maybe a bandaid fix, maybe use NavigableSet instead of using List and
+            // relying on the NavigableSet of multiblockParts?
             for (IMultiblockPart part : multiblockParts) {
-                if (name.equals(part.getSubstructureName()) && part instanceof IMultiblockAbilityPart abilityPart) {
+                if (name.equals(part.getSubstructureName()) && part instanceof IMultiblockAbilityPartabilityPart) {
                     // noinspection unchecked
                     registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
                 }
@@ -587,7 +589,7 @@ protected void invalidStructureCaches() {
         }
     }
 
-    protected IBlockPattern getSubstructure(String name) {
+    public IBlockPattern getSubstructure(String name) {
         return structures.get(name);
     }
 
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index 5a0d0e00e2b..e428b5c038c 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -84,17 +84,23 @@ public GreggyBlockPos set(int index, int value) {
     /**
      * Sets the x value.
      */
-    public GreggyBlockPos x(int val) { return set(0, val); }
+    public GreggyBlockPos x(int val) {
+        return set(0, val);
+    }
 
     /**
      * Sets the y value.
      */
-    public GreggyBlockPos y(int val) { return set(1, val); }
+    public GreggyBlockPos y(int val) {
+        return set(1, val);
+    }
 
     /**
      * Sets the z value.
      */
-    public GreggyBlockPos z(int val) { return set(2, val); }
+    public GreggyBlockPos z(int val) {
+        return set(2, val);
+    }
 
     /**
      * Sets all 3 coordinates in the given axis order
@@ -250,17 +256,23 @@ public int get(EnumFacing.Axis axis) {
     /**
      * Gets the x value.
      */
-    public int x() { return get(0); }
+    public int x() {
+        return get(0);
+    }
 
     /**
      * Gets the y value.
      */
-    public int y() { return get(1); }
+    public int y() {
+        return get(1);
+    }
 
     /**
      * Gets the z value.
      */
-    public int z() { return get(2); }
+    public int z() {
+        return get(2);
+    }
 
     /**
      * Gets a copy of the internal array, in xyz.
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 78a293cb9e7..e0cde2d895d 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -206,6 +206,7 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
                 }
             }
 
+            aisle.setActualRepeats(actualRepeats);
             aisleOffset += actualRepeats;
         }
 
@@ -260,6 +261,7 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
 
                 if (predicate != TraceabilityPredicate.ANY) {
                     TileEntity te = worldState.getTileEntity();
+                    // todo it caused an exception twice but never reproduced, maybe figure out or just remove
                     try {
                         cache.put(charPos.toLong(), new BlockInfo(worldState.getBlockState(),
                                 !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
diff --git a/src/main/java/gregtech/client/renderer/handler/AABBHighlightRenderer.java b/src/main/java/gregtech/client/renderer/handler/AABBHighlightRenderer.java
index 22b160d2641..405f42831a6 100644
--- a/src/main/java/gregtech/client/renderer/handler/AABBHighlightRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/AABBHighlightRenderer.java
@@ -1,7 +1,5 @@
 package gregtech.client.renderer.handler;
 
-import com.github.bsideup.jabel.Desugar;
-
 import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.client.utils.RenderBufferHelper;
 
@@ -15,19 +13,18 @@
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
 
+import com.github.bsideup.jabel.Desugar;
 import org.lwjgl.opengl.GL11;
 
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
 import java.util.function.BooleanSupplier;
-import java.util.function.Predicate;
 
 @SideOnly(Side.CLIENT)
 // maybe refactor as subclass of GTParticle? idk
 public class AABBHighlightRenderer {
+
     private static final Map rendering = new HashMap<>();
 
     public static void renderWorldLastEvent(RenderWorldLastEvent event) {
@@ -38,7 +35,8 @@ public static void renderWorldLastEvent(RenderWorldLastEvent event) {
 
         GlStateManager.pushMatrix();
         GlStateManager.translate(-doubleX, -doubleY, -doubleZ);
-        // maybe not necessary? idk what it even does, but one time the outline was gray despite it being white and i can't reproduce it
+        // maybe not necessary? idk what it even does, but one time the outline was gray despite it being white and i
+        // can't reproduce it
         GlStateManager.color(1, 1, 1);
 
         GlStateManager.disableDepth();
@@ -58,7 +56,8 @@ public static void renderWorldLastEvent(RenderWorldLastEvent event) {
             AABBRender aabb = entry.getKey();
             if (time > aabb.end() || !entry.getValue().getAsBoolean()) iter.remove();
 
-            // todo maybe use GL_QUADS and draw 12 prisms instead of drawing 12 lines? this prevents incorrect scaling, and fix or javadoc the +1 issue
+            // todo maybe use GL_QUADS and draw 12 prisms instead of drawing 12 lines? this prevents incorrect scaling,
+            // and fix or javadoc the +1 issue
             RenderBufferHelper.renderCubeFrame(buffer, aabb.from.x(), aabb.from.y(), aabb.from.z(),
                     aabb.to.x(), aabb.to.y(), aabb.to.z(),
                     aabb.r, aabb.g, aabb.b, 1);
diff --git a/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
index 83fd0ebcec5..f2f2b3732e2 100644
--- a/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
@@ -6,13 +6,16 @@
 import gregtech.api.gui.widgets.ToggleButtonWidget;
 import gregtech.api.items.behavior.MonitorPluginBaseBehavior;
 import gregtech.api.items.behavior.ProxyHolderPluginBehavior;
+import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
+import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.client.renderer.scene.FBOWorldSceneRenderer;
 import gregtech.client.renderer.scene.WorldSceneRenderer;
 import gregtech.client.utils.RenderUtil;
 import gregtech.client.utils.TrackedDummyWorld;
 import gregtech.common.gui.widget.WidgetScrollBar;
 import gregtech.common.gui.widget.monitor.WidgetPluginConfig;
+import gregtech.common.metatileentities.multi.electric.centralmonitor.MetaTileEntityCentralMonitor;
 import gregtech.common.metatileentities.multi.electric.centralmonitor.MetaTileEntityMonitorScreen;
 
 import net.minecraft.block.state.IBlockState;
@@ -41,11 +44,13 @@
 import codechicken.lib.render.pipeline.ColourMultiplier;
 import codechicken.lib.vec.Cuboid6;
 import codechicken.lib.vec.Translation;
+import org.apache.commons.lang3.tuple.MutablePair;
 import org.apache.commons.lang3.tuple.Pair;
 import org.lwjgl.input.Mouse;
 import org.lwjgl.opengl.GL11;
 
 import java.util.*;
+import java.util.stream.Collectors;
 
 import javax.vecmath.Vector3f;
 
@@ -213,65 +218,61 @@ public void setConfig(float scale, int rY, int rX, float spin, boolean connect)
 
     @Override
     public void update() {
-        // super.update();
-        // if (this.screen.getOffsetTimer() % 20 == 0) {
-        // if (this.screen.getWorld().isRemote) { // check connections
-        // if (worldSceneRenderer == null && validPos != null && validPos.size() > 0) {
-        // createWorldScene();
-        // }
-        // if (this.connect && worldSceneRenderer != null &&
-        // this.screen.getController() instanceof MetaTileEntityCentralMonitor) {
-        // if (connections == null) connections = new HashMap<>();
-        // connections.clear();
-        // for (MetaTileEntityMonitorScreen[] monitorScreens : ((MetaTileEntityCentralMonitor) this.screen
-        // .getController()).screens) {
-        // for (MetaTileEntityMonitorScreen screen : monitorScreens) {
-        // if (screen != null && screen.plugin instanceof FakeGuiPluginBehavior &&
-        // ((FakeGuiPluginBehavior) screen.plugin).getHolder() == this.holder) {
-        // MetaTileEntity met = ((FakeGuiPluginBehavior) screen.plugin).getRealMTE();
-        // if (met != null) {
-        // BlockPos pos = met.getPos();
-        // Pair, Vector3f> tuple = connections
-        // .getOrDefault(pos, new MutablePair<>(new ArrayList<>(), null));
-        // tuple.getLeft().add(screen);
-        // connections.put(pos, tuple);
-        // }
-        // }
-        // }
-        // }
-        // }
-        // } else { // check multi-block valid
-        // if (holder != null && holder.getMetaTileEntity() instanceof MultiblockControllerBase) {
-        // MultiblockControllerBase entity = (MultiblockControllerBase) holder.getMetaTileEntity();
-        // if (entity.isStructureFormed()) {
-        // if (!isValid) {
-        // PatternMatchContext result = entity.structurePattern.checkPatternFastAt(
-        // entity.getWorld(), entity.getPos(), entity.getFrontFacing().getOpposite(),
-        // entity.getUpwardsFacing(), entity.allowsFlip());
-        // if (result != null) {
-        // validPos = entity.structurePattern.cache.keySet().stream().map(BlockPos::fromLong)
-        // .collect(Collectors.toSet());
-        // writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> {
-        // buf.writeVarInt(validPos.size());
-        // for (BlockPos pos : validPos) {
-        // buf.writeBlockPos(pos);
-        // }
-        // });
-        // isValid = true;
-        // } else {
-        // validPos = Collections.emptySet();
-        // }
-        // }
-        // } else if (isValid) {
-        // writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> buf.writeVarInt(0));
-        // isValid = false;
-        // }
-        // }
-        // }
-        // }
-        // if (this.screen.getWorld().isRemote && spin > 0 && lastMouse == null) {
-        // rotationPitch = (int) ((rotationPitch + spin * 4) % 360);
-        // }
+        super.update();
+        if (this.screen.getOffsetTimer() % 20 == 0) {
+            if (this.screen.getWorld().isRemote) { // check connections
+                if (worldSceneRenderer == null && validPos != null && !validPos.isEmpty()) {
+                    createWorldScene();
+                }
+                if (this.connect && worldSceneRenderer != null &&
+                        this.screen.getController() instanceof MetaTileEntityCentralMonitor) {
+                    if (connections == null) connections = new HashMap<>();
+                    connections.clear();
+                    for (MetaTileEntityMonitorScreen[] monitorScreens : ((MetaTileEntityCentralMonitor) this.screen
+                            .getController()).screens) {
+                        for (MetaTileEntityMonitorScreen screen : monitorScreens) {
+                            if (screen != null && screen.plugin instanceof FakeGuiPluginBehavior &&
+                                    ((FakeGuiPluginBehavior) screen.plugin).getHolder() == this.holder) {
+                                MetaTileEntity met = ((FakeGuiPluginBehavior) screen.plugin).getRealMTE();
+                                if (met != null) {
+                                    BlockPos pos = met.getPos();
+                                    Pair, Vector3f> tuple = connections
+                                            .getOrDefault(pos, new MutablePair<>(new ArrayList<>(), null));
+                                    tuple.getLeft().add(screen);
+                                    connections.put(pos, tuple);
+                                }
+                            }
+                        }
+                    }
+                }
+            } else { // check multi-block valid
+                if (holder != null && holder.getMetaTileEntity() instanceof MultiblockControllerBase entity) {
+                    if (entity.isStructureFormed("MAIN")) {
+                        if (!isValid) {
+                            if (entity.getSubstructure("MAIN").getPatternState().getState().isValid()) {
+                                validPos = entity.getSubstructure("MAIN").getCache().keySet().stream().map(BlockPos::fromLong)
+                                        .collect(Collectors.toSet());
+                                writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> {
+                                    buf.writeVarInt(validPos.size());
+                                    for (BlockPos pos : validPos) {
+                                        buf.writeBlockPos(pos);
+                                    }
+                                });
+                                isValid = true;
+                            } else {
+                                validPos = Collections.emptySet();
+                            }
+                        }
+                    } else if (isValid) {
+                        writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> buf.writeVarInt(0));
+                        isValid = false;
+                    }
+                }
+            }
+        }
+        if (this.screen.getWorld().isRemote && spin > 0 && lastMouse == null) {
+            rotationPitch = (int) ((rotationPitch + spin * 4) % 360);
+        }
     }
 
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index 6fe37a621e0..c6c2645b497 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -398,8 +398,9 @@ protected void addDisplayText(List textList) {
                     tl.add(getWithButton("East: ", EnumFacing.EAST));
                     tl.add(getWithButton("Height: ", EnumFacing.DOWN));
 
-                    tl.add(withButton(new TextComponentString(renderingAABB ? "[Disable Outline]" : "[Enable Outline]"), "render:" +
-                            renderingAABB));
+                    tl.add(withButton(new TextComponentString(renderingAABB ? "[Disable Outline]" : "[Enable Outline]"),
+                            "render:" +
+                                    renderingAABB));
                 })
                 .addEnergyUsageExactLine(isClean() ? 4 : GTValues.VA[getEnergyTier()])
                 .addWorkingStatusLine()
@@ -495,7 +496,8 @@ public void addInformation(ItemStack stack, @Nullable World player, List
     @SideOnly(Side.CLIENT)
     protected void renderAABB(boolean render) {
         if (render) {
-            if (aabb == null) aabb = new AABBHighlightRenderer.AABBRender(new GreggyBlockPos(getPos()), new GreggyBlockPos(getPos()), 1, 1, 1, Long.MAX_VALUE);
+            if (aabb == null) aabb = new AABBHighlightRenderer.AABBRender(new GreggyBlockPos(getPos()),
+                    new GreggyBlockPos(getPos()), 1, 1, 1, Long.MAX_VALUE);
 
             // reset coords
             aabb.from().from(getPos());
@@ -503,7 +505,8 @@ protected void renderAABB(boolean render) {
 
             // ordinal 0 is UP, which is always 0
             for (int i = 1; i < 6; i++) {
-                EnumFacing facing = RelativeDirection.VALUES[i].getRelativeFacing(getFrontFacing(), getUpwardsFacing(), false);
+                EnumFacing facing = RelativeDirection.VALUES[i].getRelativeFacing(getFrontFacing(), getUpwardsFacing(),
+                        false);
                 if (facing.getAxisDirection() == EnumFacing.AxisDirection.POSITIVE) {
                     // from is always absolutely positive
                     aabb.from().offset(facing, bounds[i]);
@@ -518,7 +521,8 @@ protected void renderAABB(boolean render) {
 
             // this is so scuffed im sorry for going back to kila level code :sob:
             // surely this won't cause the gc to blow up
-            AABBHighlightRenderer.addAABB(aabb, () -> isValid() && getWorld().isBlockLoaded(getPos(), false) && getWorld().getTileEntity(getPos()) == getHolder());
+            AABBHighlightRenderer.addAABB(aabb, () -> isValid() && getWorld().isBlockLoaded(getPos(), false) &&
+                    getWorld().getTileEntity(getPos()) == getHolder());
         } else {
             AABBHighlightRenderer.removeAABB(aabb);
         }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index 75f17e61eae..f0d3be1ac28 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -397,18 +397,10 @@ public Set getAllCovers() {
 
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
-        StringBuilder start = new StringBuilder("AS");
-        StringBuilder slice = new StringBuilder("BB");
-        StringBuilder end = new StringBuilder("AA");
-        for (int i = 0; i < height - 2; i++) {
-            start.append('A');
-            slice.append('B');
-            end.append('A');
-        }
-        return FactoryBlockPattern.start(UP, BACK, RIGHT)
-                .aisle(start.toString())
-                .aisle(slice.toString()).setRepeatable(3, MAX_WIDTH)
-                .aisle(end.toString())
+        return FactoryBlockPattern.start(RIGHT, BACK, UP)
+                .aisle("A".repeat(height))
+                .aisle("B".repeat(height)).setRepeatable(3, MAX_WIDTH)
+                .aisle("AS" + "A".repeat(height - 2))
                 .where('S', selfPredicate())
                 .where('A', states(MetaBlocks.METAL_CASING.getState(BlockMetalCasing.MetalCasingType.STEEL_SOLID))
                         .or(abilities(MultiblockAbility.INPUT_ENERGY).setMinGlobalLimited(1).setMaxGlobalLimited(3)
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index c811fdbf5c4..1ed21bc183e 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -490,7 +490,8 @@ public static void onItemUse(@NotNull PlayerInteractEvent.RightClickBlock event)
         if (tileEntity instanceof IGregTechTileEntity) {
             mte = ((IGregTechTileEntity) tileEntity).getMetaTileEntity();
         }
-        if (mte instanceof MetaTileEntityCharcoalPileIgniter && ((IMultiblockController) mte).isStructureFormed("MAIN")) {
+        if (mte instanceof MetaTileEntityCharcoalPileIgniter &&
+                ((IMultiblockController) mte).isStructureFormed("MAIN")) {
             if (event.getSide().isClient()) {
                 event.setCanceled(true);
                 event.getEntityPlayer().swingArm(EnumHand.MAIN_HAND);
diff --git a/src/main/java/gregtech/loaders/recipe/BatteryRecipes.java b/src/main/java/gregtech/loaders/recipe/BatteryRecipes.java
index 8fc9440d079..d037aa479a6 100644
--- a/src/main/java/gregtech/loaders/recipe/BatteryRecipes.java
+++ b/src/main/java/gregtech/loaders/recipe/BatteryRecipes.java
@@ -345,21 +345,21 @@ private static void gemBatteries() {
 
         // Lapotronic Energy Cluster
         ASSEMBLY_LINE_RECIPES.recipeBuilder().EUt(80000).duration(1000)
-//                .input(EXTREME_CIRCUIT_BOARD)
-//                .input(plate, Europium, 8)
-//                .input(circuit, Tier.LuV, 4)
-//                .inputNBT(ENERGY_LAPOTRONIC_ORB, NBTMatcher.ANY, NBTCondition.ANY)
-//                .input(FIELD_GENERATOR_IV)
-//                .input(HIGH_POWER_INTEGRATED_CIRCUIT, 16)
+                // .input(EXTREME_CIRCUIT_BOARD)
+                // .input(plate, Europium, 8)
+                // .input(circuit, Tier.LuV, 4)
+                // .inputNBT(ENERGY_LAPOTRONIC_ORB, NBTMatcher.ANY, NBTCondition.ANY)
+                // .input(FIELD_GENERATOR_IV)
+                // .input(HIGH_POWER_INTEGRATED_CIRCUIT, 16)
                 // todo fix
                 .input(ADVANCED_SMD_DIODE, 8)
                 .input(ADVANCED_SMD_CAPACITOR, 8)
                 .input(ADVANCED_SMD_RESISTOR, 8)
                 .input(ADVANCED_SMD_TRANSISTOR, 8)
                 .input(ADVANCED_SMD_INDUCTOR, 8)
-//                .input(wireFine, Platinum, 64)
-//                .input(bolt, Naquadah, 16)
-//                .fluidInputs(SolderingAlloy.getFluid(L * 5))
+                // .input(wireFine, Platinum, 64)
+                // .input(bolt, Naquadah, 16)
+                // .fluidInputs(SolderingAlloy.getFluid(L * 5))
                 .output(ENERGY_LAPOTRONIC_ORB_CLUSTER)
                 .scannerResearch(ENERGY_LAPOTRONIC_ORB.getStackForm())
                 .buildAndRegister();

From 25abc28ea2448f3144df891284722447f4366c3d Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Wed, 14 Aug 2024 20:14:52 -0700
Subject: [PATCH 28/64] java 11 method :sob:

---
 .../MetaTileEntityCentralMonitor.java            | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index f0d3be1ac28..75f17e61eae 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -397,10 +397,18 @@ public Set getAllCovers() {
 
     @Override
     protected @NotNull BlockPattern createStructurePattern() {
-        return FactoryBlockPattern.start(RIGHT, BACK, UP)
-                .aisle("A".repeat(height))
-                .aisle("B".repeat(height)).setRepeatable(3, MAX_WIDTH)
-                .aisle("AS" + "A".repeat(height - 2))
+        StringBuilder start = new StringBuilder("AS");
+        StringBuilder slice = new StringBuilder("BB");
+        StringBuilder end = new StringBuilder("AA");
+        for (int i = 0; i < height - 2; i++) {
+            start.append('A');
+            slice.append('B');
+            end.append('A');
+        }
+        return FactoryBlockPattern.start(UP, BACK, RIGHT)
+                .aisle(start.toString())
+                .aisle(slice.toString()).setRepeatable(3, MAX_WIDTH)
+                .aisle(end.toString())
                 .where('S', selfPredicate())
                 .where('A', states(MetaBlocks.METAL_CASING.getState(BlockMetalCasing.MetalCasingType.STEEL_SOLID))
                         .or(abilities(MultiblockAbility.INPUT_ENERGY).setMinGlobalLimited(1).setMaxGlobalLimited(3)

From f09a0b6ae0afa6d73f2faaa00c71b4baeb4ac523 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Thu, 15 Aug 2024 19:52:51 -0700
Subject: [PATCH 29/64] charcoal pile igniter and refactor

---
 .../multiblock/MultiblockControllerBase.java  |  40 +--
 .../api/pattern/MultiblockShapeInfo.java      |  17 --
 .../api/pattern/pattern/BlockPattern.java     |   3 +-
 .../pattern/pattern/ExpandablePattern.java    |   3 +-
 .../api/pattern/pattern/IBlockPattern.java    |  12 +-
 .../pattern/pattern/PreviewBlockPattern.java  | 254 -----------------
 .../AdvancedMonitorPluginBehavior.java        |   3 +-
 .../electric/MetaTileEntityCleanroom.java     |   2 +-
 .../MetaTileEntityCharcoalPileIgniter.java    | 269 +++++++-----------
 9 files changed, 126 insertions(+), 477 deletions(-)
 delete mode 100644 src/main/java/gregtech/api/pattern/pattern/PreviewBlockPattern.java

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index eeba0692079..dd7fafc00d2 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -9,11 +9,11 @@
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
+import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.pattern.pattern.PatternState;
-import gregtech.api.pattern.pattern.PreviewBlockPattern;
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.unification.material.Material;
 import gregtech.api.util.BlockInfo;
@@ -73,12 +73,12 @@
 import java.util.Objects;
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
 
 import static gregtech.api.capability.GregtechDataCodes.*;
 
@@ -88,10 +88,6 @@ public abstract class MultiblockControllerBase extends MetaTileEntity implements
         MetaTileEntity mte = (MetaTileEntity) part;
         return ((long) multiblockPartSorter().apply(mte.getPos()) << 32) | mte.getPos().hashCode();
     });
-    /**
-     * Null until the first time {@link MultiblockControllerBase#getMatchingShapes()} is called, if it is not overriden
-     */
-    protected PreviewBlockPattern defaultPattern;
 
     private final Map, List> multiblockAbilities = new HashMap<>();
 
@@ -140,7 +136,7 @@ public void update() {
     /**
      * @return structure pattern of this multiblock
      */
-    // todo fix central monitor, charcoal pile igniter, (and vacuum freezer)
+    // todo fix central monitor, and vacuum freezer
     @NotNull
     protected abstract IBlockPattern createStructurePattern();
 
@@ -528,10 +524,11 @@ protected void registerMultiblockAbility(IMultiblockAbilityPart part) {
         part.registerAbilities(abilityList);
     }
 
-    protected void forEachFormed(String name, Consumer action) {
+    protected void forEachFormed(String name, BiConsumer action) {
         Long2ObjectMap cache = getSubstructure(name).getCache();
-        for (BlockInfo info : cache.values()) {
-            action.accept(info);
+        GreggyBlockPos pos = new GreggyBlockPos();
+        for (Long2ObjectMap.Entry entry : cache.long2ObjectEntrySet()) {
+            action.accept(entry.getValue(), pos.fromLong(entry.getLongKey()));
         }
     }
 
@@ -777,30 +774,23 @@ public List getMatchingShapes() {
      * The new(and better) way of getting shapes for in world, jei, and autobuild. Default impl just converts
      * {@link MultiblockControllerBase#getMatchingShapes()}, if not empty, to this. If getMatchingShapes is empty, uses
      * a default generated structure pattern, it's not very good which is why you should override this.
-     * 
-     * @param keyMap  A map for autobuild, or null if it is an in world or jei preview.
+     *
+     * @param keyMap  A map for autobuild, or null if it is an in world or jei preview. Note that for in world and jei
+     *                previews you can return a singleton list(only the first element will be used anyway).
      * @param hatches This is whether you should put hatches, JEI previews need hatches, but autobuild and in world
-     *                previews shouldn't(unless the hatch is necessary and only has one valid spot, such as EBF)
+     *                previews shouldn't(unless the hatch is necessary and only has one valid spot, such as EBF muffler)
      */
     // todo add use for the keyMap with the multiblock builder
-    public List getBuildableShapes(@Nullable Object2IntMap keyMap, boolean hatches) {
+    // todo maybe add name arg for building substructures
+    public List getBuildableShapes(@Nullable Object2IntMap keyMap, boolean hatches) {
         List infos = getMatchingShapes();
 
         // if there is no overriden getMatchingShapes() just return the default one
         if (infos.isEmpty()) {
-            if (defaultPattern == null) {
-
-                // only generate for the first pattern, if you have more than 1 pattern you better override this
-                defaultPattern = getSubstructure("MAIN").getDefaultShape();
-
-                if (defaultPattern == null) return Collections.emptyList();
-            }
-
-            return Collections.singletonList(defaultPattern);
+            return Collections.singletonList(getSubstructure("MAIN").getDefaultShape());
         }
 
-        // otherwise just convert them all the preview block pattern and return
-        return getMatchingShapes().stream().map(PreviewBlockPattern::new).collect(Collectors.toList());
+        return infos;
     }
 
     @SideOnly(Side.CLIENT)
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index 3ade4b6eac3..40a6a199e42 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -3,7 +3,6 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.pattern.pattern.PatternAisle;
-import gregtech.api.pattern.pattern.PreviewBlockPattern;
 import gregtech.api.util.BlockInfo;
 
 import net.minecraft.block.state.IBlockState;
@@ -12,18 +11,12 @@
 
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
-import org.jetbrains.annotations.ApiStatus;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Supplier;
 
-@Deprecated
 public class MultiblockShapeInfo {
-
-    /**
-     * Array of aisles, you should use {@link PreviewBlockPattern} instead
-     */
     protected final PatternAisle[] aisles;
     protected final Char2ObjectMap symbols;
 
@@ -36,16 +29,6 @@ public static Builder builder() {
         return new Builder();
     }
 
-    @ApiStatus.Internal
-    public PatternAisle[] getAisles() {
-        return aisles;
-    }
-
-    @ApiStatus.Internal
-    public Char2ObjectMap getSymbols() {
-        return symbols;
-    }
-
     public static class Builder {
 
         private List shape = new ArrayList<>();
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index e0cde2d895d..3051f01cf8f 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -3,6 +3,7 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
+import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
@@ -296,7 +297,7 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
 
     @Override
     // todo get real
-    public PreviewBlockPattern getDefaultShape() {
+    public MultiblockShapeInfo getDefaultShape() {
         char[][][] pattern = new char[dimensions[2]][dimensions[1]][dimensions[0]];
 
         for (PatternAisle aisle : aisles) {
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index 9ebcba16d1d..68f4ea40c33 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -3,6 +3,7 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
+import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
@@ -172,7 +173,7 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
     }
 
     @Override
-    public PreviewBlockPattern getDefaultShape() {
+    public MultiblockShapeInfo getDefaultShape() {
         // todo undo
         return null;
     }
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index a0c6b20c1c6..e83c60f13dd 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -1,6 +1,7 @@
 package gregtech.api.pattern.pattern;
 
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.OriginOffset;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.RelativeDirection;
@@ -11,6 +12,7 @@
 
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 public interface IBlockPattern {
 
@@ -46,9 +48,11 @@ boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
                            boolean isFlipped);
 
     /**
-     * Gets the default shape, if the multiblock does not specify one.
+     * Gets the default shape, if the multiblock does not specify one. Return null to represent the default shape does
+     * not exist.
      */
-    PreviewBlockPattern getDefaultShape();
+    @Nullable
+    MultiblockShapeInfo getDefaultShape();
 
     /**
      * Gets the internal pattern state, you should use the one returned from
@@ -65,9 +69,7 @@ default void clearCache() {
     }
 
     /**
-     * Gets the cache, if you modify literally anything in the cache except clearing it(in which case you should use
-     * clearCache())
-     * then GTCEu is now licensed under ARR just for you, so close your IDE or else.
+     * Gets the cache, do not modify. Note that the cache stores everything in the AABB of the substructure, except for any() TraceabilityPredicates.
      * 
      * @return The cache for rapid pattern checking.
      */
diff --git a/src/main/java/gregtech/api/pattern/pattern/PreviewBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/PreviewBlockPattern.java
deleted file mode 100644
index bde18d53626..00000000000
--- a/src/main/java/gregtech/api/pattern/pattern/PreviewBlockPattern.java
+++ /dev/null
@@ -1,254 +0,0 @@
-package gregtech.api.pattern.pattern;
-
-import gregtech.api.metatileentity.MetaTileEntity;
-import gregtech.api.metatileentity.MetaTileEntityHolder;
-import gregtech.api.pattern.MultiblockShapeInfo;
-import gregtech.api.util.BlockInfo;
-import gregtech.api.util.RelativeDirection;
-
-import net.minecraft.block.state.IBlockState;
-import net.minecraft.tileentity.TileEntity;
-import net.minecraft.util.EnumFacing;
-
-import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
-import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
-import org.apache.commons.lang3.ArrayUtils;
-import org.apache.commons.lang3.StringUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Supplier;
-
-import static gregtech.api.pattern.pattern.FactoryBlockPattern.COMMA_JOIN;
-
-/**
- * Class holding data for a concrete multiblock. This multiblock can be formed or unformed in this.
- */
-public class PreviewBlockPattern {
-
-    /**
-     * In [ aisleDir, stringDir, charDir ]
-     */
-    protected final RelativeDirection[] structureDir;
-
-    protected final PatternAisle[] aisles;
-    protected final int[] dimensions, startOffset;
-    protected final Char2ObjectMap symbols;
-
-    /**
-     * Legacy compat only, do not use for new code.
-     */
-    public PreviewBlockPattern(MultiblockShapeInfo info) {
-        this.aisles = info.getAisles();
-        this.dimensions = new int[] { aisles.length, aisles[0].getStringCount(), aisles[0].getCharCount() };
-        structureDir = new RelativeDirection[] { RelativeDirection.BACK, RelativeDirection.UP,
-                RelativeDirection.RIGHT };
-        this.startOffset = new int[3];
-        // i am lazy so hopefully addons follow the convention of using 'S' for their self predicate(aka center
-        // predicate)
-        legacyStartOffset('S');
-        this.symbols = info.getSymbols();
-    }
-
-    public PreviewBlockPattern(PatternAisle[] aisles, int[] dimensions, RelativeDirection[] directions,
-                               int[] startOffset, Char2ObjectMap symbols) {
-        this.aisles = aisles;
-        this.dimensions = dimensions;
-        this.structureDir = directions;
-        this.startOffset = startOffset;
-        this.symbols = symbols;
-    }
-
-    private void legacyStartOffset(char center) {
-        // could also use aisles.length but this is cooler
-        for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
-            int[] result = aisles[aisleI].firstInstanceOf(center);
-            if (result != null) {
-                startOffset[0] = aisleI;
-                startOffset[1] = result[0];
-                startOffset[2] = result[1];
-                return;
-            }
-        }
-
-        System.out.println("FAILED TO FIND PREDICATE");
-    }
-
-    public static class Builder {
-
-        /**
-         * In [ aisle count, string count, char count ]
-         */
-        protected final int[] dimensions = new int[3];
-
-        /**
-         * In relative directions opposite to {@code directions}
-         */
-        protected final int[] offset = new int[3];
-
-        /**
-         * Way the builder progresses
-         * 
-         * @see FactoryBlockPattern
-         */
-        protected final RelativeDirection[] directions = new RelativeDirection[3];
-        protected final List aisles = new ArrayList<>();
-        protected final Char2ObjectMap symbolMap = new Char2ObjectOpenHashMap<>();
-
-        /**
-         * @see Builder#start(RelativeDirection, RelativeDirection, RelativeDirection)
-         */
-        protected Builder(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
-            directions[0] = charDir;
-            directions[1] = stringDir;
-            directions[2] = aisleDir;
-
-            boolean[] flags = new boolean[3];
-            for (int i = 0; i < 3; i++) {
-                flags[directions[i].ordinal() / 2] = true;
-            }
-            if (!(flags[0] && flags[1] && flags[2])) throw new IllegalArgumentException("Must have 3 different axes!");
-        }
-
-        /**
-         * Same as calling {@link Builder#start(RelativeDirection, RelativeDirection, RelativeDirection)} with BACK, UP,
-         * RIGHT
-         */
-        public static Builder start() {
-            return new Builder(RelativeDirection.BACK, RelativeDirection.UP, RelativeDirection.RIGHT);
-        }
-
-        /**
-         * Starts the builder, each pair of relative directions must be used exactly once!
-         * 
-         * @param aisleDir  The direction for aisles to advance in.
-         * @param stringDir The direction for strings to advance in.
-         * @param charDir   The direction for chars to advance in.
-         */
-        public static Builder start(RelativeDirection aisleDir, RelativeDirection stringDir,
-                                    RelativeDirection charDir) {
-            return new Builder(aisleDir, stringDir, charDir);
-        }
-
-        // protected because it doesn't do any dimension checks and just uses trust
-        protected Builder aisle(int repeats, PatternAisle aisle) {
-            for (String str : aisle.pattern) {
-                for (char c : str.toCharArray()) {
-                    if (!this.symbolMap.containsKey(c)) {
-                        this.symbolMap.put(c, null);
-                    }
-                }
-            }
-            PatternAisle copy = aisle.copy();
-            copy.actualRepeats = repeats;
-            aisles.add(copy);
-            return this;
-        }
-
-        /**
-         * Adds a new aisle to the builder.
-         * 
-         * @param repeats Amount of repeats.
-         * @param aisle   The aisle.
-         */
-        public Builder aisle(int repeats, String... aisle) {
-            // straight up copied from factory block pattern's code
-            if (ArrayUtils.isEmpty(aisle) || StringUtils.isEmpty(aisle[0]))
-                throw new IllegalArgumentException("Empty pattern for aisle");
-
-            // set the dimensions if the user hasn't already
-            if (dimensions[2] == -1) {
-                dimensions[2] = aisle[0].length();
-            }
-            if (dimensions[1] == -1) {
-                dimensions[1] = aisle.length;
-            }
-
-            if (aisle.length != dimensions[1]) {
-                throw new IllegalArgumentException("Expected aisle with height of " + dimensions[1] +
-                        ", but was given one with a height of " + aisle.length + ")");
-            } else {
-                for (String s : aisle) {
-                    if (s.length() != dimensions[2]) {
-                        throw new IllegalArgumentException(
-                                "Not all rows in the given aisle are the correct width (expected " + dimensions[2] +
-                                        ", found one with " + s.length() + ")");
-                    }
-
-                    for (char c : s.toCharArray()) {
-                        if (!this.symbolMap.containsKey(c)) {
-                            this.symbolMap.put(c, null);
-                        }
-                    }
-                }
-
-                aisles.add(new PatternAisle(repeats, repeats, aisle));
-                return this;
-            }
-        }
-
-        /**
-         * Same as calling {@link Builder#aisle(int, String...)} with the first argument being 1
-         */
-        public Builder aisle(String... aisle) {
-            return aisle(1, aisle);
-        }
-
-        // all the .where() are copied from MultiblockShapeInfo, yay for code stealing
-        public Builder where(char symbol, BlockInfo value) {
-            this.symbolMap.put(symbol, value);
-            return this;
-        }
-
-        public Builder where(char symbol, IBlockState blockState) {
-            return where(symbol, new BlockInfo(blockState));
-        }
-
-        public Builder where(char symbol, IBlockState blockState, TileEntity tileEntity) {
-            return where(symbol, new BlockInfo(blockState, tileEntity));
-        }
-
-        public Builder where(char symbol, MetaTileEntity tileEntity, EnumFacing frontSide) {
-            MetaTileEntityHolder holder = new MetaTileEntityHolder();
-            holder.setMetaTileEntity(tileEntity);
-            holder.getMetaTileEntity().onPlacement();
-            holder.getMetaTileEntity().setFrontFacing(frontSide);
-            return where(symbol, new BlockInfo(tileEntity.getBlock().getDefaultState(), holder));
-        }
-
-        /**
-         * @param partSupplier Should supply either a MetaTileEntity or an IBlockState.
-         */
-        public Builder where(char symbol, Supplier partSupplier, EnumFacing frontSideIfTE) {
-            Object part = partSupplier.get();
-            if (part instanceof IBlockState) {
-                return where(symbol, (IBlockState) part);
-            } else if (part instanceof MetaTileEntity) {
-                return where(symbol, (MetaTileEntity) part, frontSideIfTE);
-            } else throw new IllegalArgumentException(
-                    "Supplier must supply either a MetaTileEntity or an IBlockState! Actual: " + part.getClass());
-        }
-
-        protected void validateMissingValues() {
-            List chars = new ArrayList<>();
-
-            for (Char2ObjectMap.Entry entry : symbolMap.char2ObjectEntrySet()) {
-                if (entry.getValue() == null) {
-                    chars.add(entry.getCharKey());
-                }
-            }
-
-            if (!chars.isEmpty()) {
-                throw new IllegalStateException(
-                        "Predicates for character(s) " + COMMA_JOIN.join(chars) + " are missing");
-            }
-        }
-
-        public PreviewBlockPattern build() {
-            validateMissingValues();
-            dimensions[0] = aisles.size();
-            return new PreviewBlockPattern(aisles.toArray(new PatternAisle[0]), dimensions, directions, offset,
-                    symbolMap);
-        }
-    }
-}
diff --git a/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
index f2f2b3732e2..ede9ff3e270 100644
--- a/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
@@ -250,7 +250,8 @@ public void update() {
                     if (entity.isStructureFormed("MAIN")) {
                         if (!isValid) {
                             if (entity.getSubstructure("MAIN").getPatternState().getState().isValid()) {
-                                validPos = entity.getSubstructure("MAIN").getCache().keySet().stream().map(BlockPos::fromLong)
+                                validPos = entity.getSubstructure("MAIN").getCache().keySet().stream()
+                                        .map(BlockPos::fromLong)
                                         .collect(Collectors.toSet());
                                 writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> {
                                     buf.writeVarInt(validPos.size());
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index c6c2645b497..dae9c0f1f18 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -153,7 +153,7 @@ protected void formStructure(String name) {
         this.cleanroomFilter = type;
         this.cleanroomType = type.getCleanroomType();
 
-        forEachFormed(name, info -> {
+        forEachFormed(name, (info, pos) -> {
             TileEntity te = info.getTileEntity();
             if (!(te instanceof IGregTechTileEntity gtte)) return;
 
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index 1ed21bc183e..9bdbf9bf4eb 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -1,5 +1,7 @@
 package gregtech.common.metatileentities.primitive;
 
+import codechicken.lib.raytracer.CuboidRayTraceResult;
+
 import gregtech.api.GTValues;
 import gregtech.api.capability.GregtechDataCodes;
 import gregtech.api.capability.GregtechTileCapabilities;
@@ -10,11 +12,16 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
+import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.*;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.pattern.FactoryExpandablePattern;
+import gregtech.api.pattern.pattern.IBlockPattern;
+import gregtech.api.util.GTLog;
 import gregtech.api.util.Mods;
+import gregtech.api.util.RelativeDirection;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.client.utils.TooltipHelper;
@@ -34,6 +41,7 @@
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.*;
 import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.text.TextComponentString;
 import net.minecraft.world.World;
 import net.minecraftforge.common.MinecraftForge;
 import net.minecraftforge.common.capabilities.Capability;
@@ -58,7 +66,9 @@
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 @ZenClass("mods.gregtech.machines.CharcoalPileIgniter")
@@ -67,6 +77,8 @@ public class MetaTileEntityCharcoalPileIgniter extends MultiblockControllerBase
 
     private static final int MIN_RADIUS = 1;
     private static final int MIN_DEPTH = 2;
+    private static final int MAX_RADIUS = 5;
+    private static final int MAX_DEPTH = 5;
 
     private static final Set WALL_BLOCKS = new ObjectOpenHashSet<>();
 
@@ -79,14 +91,17 @@ public class MetaTileEntityCharcoalPileIgniter extends MultiblockControllerBase
         WALL_BLOCKS.add(Blocks.SAND);
     }
 
-    private int lDist = 0;
-    private int rDist = 0;
-    private int hDist = 0;
+    private final int[] bounds = new int[] { 0, MIN_DEPTH, MIN_RADIUS, MIN_RADIUS, MIN_RADIUS, MIN_RADIUS };
 
     private boolean isActive;
     private int progressTime = 0;
     private int maxProgress = 0;
 
+    /**
+     * Reverse map from enum facing -> relative direction, refreshed on every setFrontFacing(...) call
+     */
+    private final Map facingMap = new HashMap<>();
+
     public MetaTileEntityCharcoalPileIgniter(ResourceLocation metaTileEntityId) {
         super(metaTileEntityId);
         MinecraftForge.EVENT_BUS.register(MetaTileEntityCharcoalPileIgniter.class);
@@ -115,172 +130,66 @@ public void invalidateStructure(String name) {
     @Override
     protected void formStructure(String name) {
         super.formStructure(name);
+        // this doesn't iterate over any(), so doesn't count the borders
+        forEachFormed("MAIN", (info, pos) -> {
+            BlockPos immutable = pos.immutable();
+
+            if (info.getBlockState().getBlock().isWood(getWorld(), immutable)) {
+                logPositions.add(immutable);
+            }
+        });
         // calculate the duration upon formation
         updateMaxProgressTime();
     }
 
     @NotNull
     @Override
-    protected BlockPattern createStructurePattern() {
-        // update the structure's dimensions just before we create it
-        // return the default structure, even if there is no valid size found
-        // this means auto-build will still work, and prevents terminal crashes.
-        if (getWorld() != null) updateStructureDimensions();
-
-        // these can sometimes get set to 0 when loading the game, breaking JEI
-        if (lDist < 1) lDist = MIN_RADIUS;
-        if (rDist < 1) rDist = MIN_RADIUS;
-        if (hDist < 2) hDist = MIN_DEPTH;
-
-        // swap the left and right distances if the front facing is east or west
-        // i guess allows BlockPattern checkPatternAt to get the correct relative position, somehow.
-        if (this.frontFacing == EnumFacing.EAST || this.frontFacing == EnumFacing.WEST) {
-            int tmp = lDist;
-            lDist = rDist;
-            rDist = tmp;
-        }
-
-        StringBuilder wallBuilder = new StringBuilder();       // " XXX "
-        StringBuilder floorBuilder = new StringBuilder();      // " BBB "
-        StringBuilder cornerBuilder = new StringBuilder();     // " "
-        StringBuilder ctrlBuilder = new StringBuilder();       // " XSX "
-        StringBuilder woodBuilder = new StringBuilder();       // "XCCCX"
-
-        // everything to the left of the controller
-        wallBuilder.append(" ");
-        floorBuilder.append(" ");
-        ctrlBuilder.append(" ");
-        woodBuilder.append("X");
-
-        for (int i = 0; i < lDist; i++) {
-            cornerBuilder.append(" ");
-            if (i > 0) {
-                wallBuilder.append("X");
-                floorBuilder.append("B");
-                ctrlBuilder.append("X");
-                woodBuilder.append("C");
-            }
-        }
-
-        // everything in-line with the controller
-        wallBuilder.append("X");
-        floorBuilder.append("B");
-        cornerBuilder.append(" ");
-        ctrlBuilder.append("S");
-        woodBuilder.append("C");
-
-        // everything to the right of the controller
-        for (int i = 0; i < rDist; i++) {
-            cornerBuilder.append(" ");
-            if (i < rDist - 1) {
-                wallBuilder.append("X");
-                floorBuilder.append("B");
-                ctrlBuilder.append("X");
-                woodBuilder.append("C");
-            }
-        }
-
-        wallBuilder.append(" ");
-        floorBuilder.append(" ");
-        ctrlBuilder.append(" ");
-        woodBuilder.append("X");
-
-        String[] wall = new String[hDist + 1]; // " ", " XXX ", " "
-        Arrays.fill(wall, wallBuilder.toString());
-        wall[0] = cornerBuilder.toString();
-        wall[wall.length - 1] = cornerBuilder.toString();
-
-        String[] slice = new String[hDist + 1]; // " BBB ", "XCCCX", " XXX "
-        Arrays.fill(slice, woodBuilder.toString());
-        slice[0] = floorBuilder.toString();
-
-        String[] center = Arrays.copyOf(slice, slice.length); // " BBB ", "XCCCX", " XSX "
-        // inverse the center slice if facing east or west.
-        if (this.frontFacing == EnumFacing.EAST || this.frontFacing == EnumFacing.WEST) {
-            center[center.length - 1] = ctrlBuilder.reverse().toString();
-        } else {
-            center[center.length - 1] = ctrlBuilder.toString();
-        }
+    protected IBlockPattern createStructurePattern() {
+        TraceabilityPredicate floorPredicate = blocks(Blocks.BRICK_BLOCK);
+        TraceabilityPredicate wallPredicate = blocks(WALL_BLOCKS.toArray(new Block[0]));
+        TraceabilityPredicate logPredicate = logPredicate();
+
+        // basically cleanroom code
+        return FactoryExpandablePattern.start(RelativeDirection.UP, RelativeDirection.RIGHT, RelativeDirection.FRONT)
+                .boundsFunction((w, c, f, u) -> bounds)
+                .predicateFunction((c, b) -> {
+                    // controller always at origin
+                    if (c.origin()) return selfPredicate();
+
+                    int intersects = 0;
+
+                    // aisle dir is up, so its bounds[0] and bounds[1]
+                    boolean topAisle = c.x() == b[0];
+                    boolean botAisle = c.x() == -b[1];
+
+                    if (topAisle || botAisle) intersects++;
+                    // negative signs for the LEFT and BACK ordinals
+                    // string dir is right, so its bounds[2] and bounds[3]
+                    if (c.y() == -b[2] || c.y() == b[3]) intersects++;
+                    // char dir is front, so its bounds[4] and bounds[5]
+                    if (c.z() == b[4] || c.z() == -b[5]) intersects++;
+
+                    // GTLog.logger.info(intersects + " intersects at " + c);
+
+                    // more than or equal to 2 intersects means it is an edge
+                    if (intersects >= 2) return any();
+
+                    // 1 intersect means it is a face
+                    if (intersects == 1) {
+                        if (botAisle) return floorPredicate;
+                        return wallPredicate;
+                    }
 
-        // slice is finished after center, so we can re-use it a bit more
-        slice[slice.length - 1] = wallBuilder.toString();
-
-        return FactoryBlockPattern.start()
-                .aisle(wall)
-                .aisle(slice).setRepeatable(0, 4)
-                .aisle(center)
-                .aisle(slice).setRepeatable(0, 4)
-                .aisle(wall)
-                .where('S', selfPredicate())
-                .where('B', blocks(Blocks.BRICK_BLOCK))
-                .where('X', blocks(WALL_BLOCKS.toArray(new Block[0])))
-                .where('C', logPredicate())
-                .where(' ', any())
+                    // intersects == 0, so its not a face
+                    return logPredicate;
+                })
                 .build();
     }
 
     @NotNull
     private TraceabilityPredicate logPredicate() {
-        return new TraceabilityPredicate(blockWorldState -> {
-            if (blockWorldState.getBlockState().getBlock().isWood(blockWorldState.getWorld(),
-                    blockWorldState.getPos())) {
-                // store the position of every log, so we can easily turn them into charcoal
-                logPositions.add(blockWorldState.getPos());
-                return true;
-            }
-            return false;
-        });
-    }
-
-    private boolean updateStructureDimensions() {
-        World world = getWorld();
-        EnumFacing left = getFrontFacing().getOpposite().rotateYCCW();
-        EnumFacing right = left.getOpposite();
-
-        // l, r move down 1 block because the top layer has no bricks
-        BlockPos.MutableBlockPos lPos = new BlockPos.MutableBlockPos(getPos()).move(EnumFacing.DOWN);
-        BlockPos.MutableBlockPos rPos = new BlockPos.MutableBlockPos(getPos()).move(EnumFacing.DOWN);
-        BlockPos.MutableBlockPos hPos = new BlockPos.MutableBlockPos(getPos());
-
-        // find the distances from the controller to the brick blocks on one horizontal axis and the Y axis
-        // repeatable aisles take care of the second horizontal axis
-        int lDist = 0;
-        int rDist = 0;
-        int hDist = 0;
-
-        // find the left, right, height distances for the structure pattern
-        // maximum size is 11x11x6 including walls, so check 5 block radius around the controller for blocks
-        for (int i = 1; i < 6; i++) {
-            if (lDist != 0 && rDist != 0 && hDist != 0) break;
-            if (lDist == 0 && isBlockWall(world, lPos, left)) lDist = i;
-            if (rDist == 0 && isBlockWall(world, rPos, right)) rDist = i;
-            if (hDist == 0 && isBlockFloor(world, hPos)) hDist = i;
-        }
-
-        if (lDist < MIN_RADIUS || rDist < MIN_RADIUS || hDist < MIN_DEPTH) {
-            invalidateStructure("MAIN");
-            return false;
-        }
-
-        this.lDist = lDist;
-        this.rDist = rDist;
-        this.hDist = hDist;
-
-        writeCustomData(GregtechDataCodes.UPDATE_STRUCTURE_SIZE, buf -> {
-            buf.writeInt(this.lDist);
-            buf.writeInt(this.rDist);
-            buf.writeInt(this.hDist);
-        });
-        return true;
-    }
-
-    private static boolean isBlockWall(@NotNull World world, @NotNull BlockPos.MutableBlockPos pos,
-                                       @NotNull EnumFacing direction) {
-        return WALL_BLOCKS.contains(world.getBlockState(pos.move(direction)).getBlock());
-    }
-
-    private static boolean isBlockFloor(@NotNull World world, @NotNull BlockPos.MutableBlockPos pos) {
-        return world.getBlockState(pos.move(EnumFacing.DOWN)).getBlock() == Blocks.BRICK_BLOCK;
+        return new TraceabilityPredicate((worldState, patternState) -> worldState.getBlockState().getBlock().isWood(worldState.getWorld(),
+                worldState.getPos()) || worldState.getBlockState().equals(MetaBlocks.BRITTLE_CHARCOAL.getDefaultState()));
     }
 
     private void setActive(boolean active) {
@@ -331,6 +240,36 @@ private void convertLogBlocks() {
         logPositions.clear();
     }
 
+    protected void updateFacingMap() {
+        // cache relative front, back, left, right
+        for (int i = 2; i < 6; i++) {
+            EnumFacing abs = RelativeDirection.VALUES[i].getRelativeFacing(frontFacing, upwardsFacing, false);
+            facingMap.put(abs, RelativeDirection.VALUES[i]);
+        }
+    }
+
+    @Override
+    public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing, CuboidRayTraceResult hitResult) {
+        if (!getWorld().isRemote && !playerIn.isSneaking()) {
+            RelativeDirection dir = facingMap.getOrDefault(facing, RelativeDirection.DOWN);
+            bounds[dir.ordinal()] += 1;
+            if (bounds[dir.ordinal()] > (dir == RelativeDirection.DOWN ? MAX_DEPTH : MAX_RADIUS)) {
+                bounds[dir.ordinal()] = (dir == RelativeDirection.DOWN ? MIN_DEPTH : MIN_RADIUS);
+            }
+
+            playerIn.sendMessage(new TextComponentString(facing.name() + " radius: " + bounds[dir.ordinal()]));
+            getSubstructure("MAIN").clearCache();
+            return true;
+        }
+        return super.onScrewdriverClick(playerIn, hand, facing, hitResult);
+    }
+
+    @Override
+    public void setFrontFacing(EnumFacing facing) {
+        super.setFrontFacing(facing);
+        updateFacingMap();
+    }
+
     @SideOnly(Side.CLIENT)
     @Override
     public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
@@ -379,9 +318,6 @@ public List getMatchingShapes() {
     @Override
     public NBTTagCompound writeToNBT(NBTTagCompound data) {
         super.writeToNBT(data);
-        data.setInteger("lDist", this.lDist);
-        data.setInteger("rDist", this.rDist);
-        data.setInteger("hDist", this.hDist);
         data.setInteger("progressTime", this.progressTime);
         data.setInteger("maxProgress", this.maxProgress);
         data.setBoolean("isActive", this.isActive);
@@ -391,20 +327,15 @@ public NBTTagCompound writeToNBT(NBTTagCompound data) {
     @Override
     public void readFromNBT(NBTTagCompound data) {
         super.readFromNBT(data);
-        this.lDist = data.hasKey("lDist") ? data.getInteger("lDist") : this.lDist;
-        this.rDist = data.hasKey("rDist") ? data.getInteger("rDist") : this.rDist;
-        this.hDist = data.hasKey("hDist") ? data.getInteger("hDist") : this.hDist;
         this.progressTime = data.getInteger("progressTime");
         this.maxProgress = data.getInteger("maxProgress");
         this.isActive = data.getBoolean("isActive");
+        updateFacingMap();
     }
 
     @Override
     public void writeInitialSyncData(PacketBuffer buf) {
         super.writeInitialSyncData(buf);
-        buf.writeInt(this.lDist);
-        buf.writeInt(this.rDist);
-        buf.writeInt(this.hDist);
         buf.writeInt(this.progressTime);
         buf.writeInt(this.maxProgress);
         buf.writeBoolean(this.isActive);
@@ -413,9 +344,6 @@ public void writeInitialSyncData(PacketBuffer buf) {
     @Override
     public void receiveInitialSyncData(PacketBuffer buf) {
         super.receiveInitialSyncData(buf);
-        this.lDist = buf.readInt();
-        this.rDist = buf.readInt();
-        this.hDist = buf.readInt();
         this.progressTime = buf.readInt();
         this.maxProgress = buf.readInt();
         this.isActive = buf.readBoolean();
@@ -425,9 +353,6 @@ public void receiveInitialSyncData(PacketBuffer buf) {
     public void receiveCustomData(int dataId, PacketBuffer buf) {
         super.receiveCustomData(dataId, buf);
         if (dataId == GregtechDataCodes.UPDATE_STRUCTURE_SIZE) {
-            this.lDist = buf.readInt();
-            this.rDist = buf.readInt();
-            this.hDist = buf.readInt();
         } else if (dataId == GregtechDataCodes.WORKABLE_ACTIVE) {
             this.isActive = buf.readBoolean();
             scheduleRenderUpdate();

From d65a1a778b2fd769af270a8e531438f3778002f0 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Fri, 16 Aug 2024 19:36:31 -0700
Subject: [PATCH 30/64] persistent nbt data

---
 .../multiblock/MultiblockControllerBase.java  |  2 ++
 .../electric/MetaTileEntityCleanroom.java     | 10 ++++++++
 .../MetaTileEntityCentralMonitor.java         |  2 +-
 .../MetaTileEntityCharcoalPileIgniter.java    | 25 +++++++++++--------
 4 files changed, 28 insertions(+), 11 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index dd7fafc00d2..7b52ae49368 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -209,6 +209,8 @@ protected void setFlipped(boolean flipped, String name) {
     /**
      * Gets the inactive texture for this part, used for when the multiblock is unformed and you want the part to keep
      * its overlay. Return null to ignore and make hatches go back to their default textures on unform.
+     * This is currently pretty buggy but this is like minimum priority stuff so i cant really be
+     * bothered to fix especially about rendering.
      */
     public @Nullable ICubeRenderer getInactiveTexture(IMultiblockPart part) {
         return null;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index dae9c0f1f18..2f369dcaab2 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -659,6 +659,7 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
     public NBTTagCompound writeToNBT(@NotNull NBTTagCompound data) {
         super.writeToNBT(data);
         data.setInteger("cleanAmount", this.cleanAmount);
+        data.setIntArray("bounds", bounds);
         return this.cleanroomLogic.writeToNBT(data);
     }
 
@@ -667,6 +668,15 @@ public void readFromNBT(NBTTagCompound data) {
         super.readFromNBT(data);
         this.cleanAmount = data.getInteger("cleanAmount");
         this.cleanroomLogic.readFromNBT(data);
+        if (data.hasKey("bounds")) {
+            System.arraycopy(data.getIntArray("bounds"), 0, bounds, 0, 6);
+        } else if (data.hasKey("lDist")) {
+            bounds[1] = data.getInteger("hDist");
+            bounds[2] = data.getInteger("lDist");
+            bounds[3] = data.getInteger("rDist");
+            bounds[4] = data.getInteger("fDist");
+            bounds[5] = data.getInteger("bDist");
+        }
         updateFacingMap();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index 75f17e61eae..95d9c132932 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -405,7 +405,7 @@ public Set getAllCovers() {
             slice.append('B');
             end.append('A');
         }
-        return FactoryBlockPattern.start(UP, BACK, RIGHT)
+        return FactoryBlockPattern.start(LEFT, BACK, UP)
                 .aisle(start.toString())
                 .aisle(slice.toString()).setRepeatable(3, MAX_WIDTH)
                 .aisle(end.toString())
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index 9bdbf9bf4eb..36752340e37 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -1,7 +1,5 @@
 package gregtech.common.metatileentities.primitive;
 
-import codechicken.lib.raytracer.CuboidRayTraceResult;
-
 import gregtech.api.GTValues;
 import gregtech.api.capability.GregtechDataCodes;
 import gregtech.api.capability.GregtechTileCapabilities;
@@ -12,14 +10,11 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
-import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.*;
-import gregtech.api.pattern.pattern.BlockPattern;
-import gregtech.api.pattern.pattern.FactoryBlockPattern;
+import gregtech.api.pattern.MultiblockShapeInfo;
+import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.FactoryExpandablePattern;
 import gregtech.api.pattern.pattern.IBlockPattern;
-import gregtech.api.util.GTLog;
 import gregtech.api.util.Mods;
 import gregtech.api.util.RelativeDirection;
 import gregtech.client.renderer.ICubeRenderer;
@@ -39,7 +34,12 @@
 import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.network.PacketBuffer;
 import net.minecraft.tileentity.TileEntity;
-import net.minecraft.util.*;
+import net.minecraft.util.EnumActionResult;
+import net.minecraft.util.EnumFacing;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.EnumParticleTypes;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.SoundCategory;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.text.TextComponentString;
 import net.minecraft.world.World;
@@ -51,6 +51,7 @@
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
 
+import codechicken.lib.raytracer.CuboidRayTraceResult;
 import codechicken.lib.render.CCRenderState;
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
@@ -64,7 +65,6 @@
 import stanhebben.zenscript.annotations.ZenClass;
 import stanhebben.zenscript.annotations.ZenMethod;
 
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -250,7 +250,10 @@ protected void updateFacingMap() {
 
     @Override
     public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing, CuboidRayTraceResult hitResult) {
-        if (!getWorld().isRemote && !playerIn.isSneaking()) {
+        // todo add general direction lang(applies for cleanroom as well)
+        if (!playerIn.isSneaking()) {
+            if (getWorld().isRemote) return true;
+
             RelativeDirection dir = facingMap.getOrDefault(facing, RelativeDirection.DOWN);
             bounds[dir.ordinal()] += 1;
             if (bounds[dir.ordinal()] > (dir == RelativeDirection.DOWN ? MAX_DEPTH : MAX_RADIUS)) {
@@ -321,6 +324,7 @@ public NBTTagCompound writeToNBT(NBTTagCompound data) {
         data.setInteger("progressTime", this.progressTime);
         data.setInteger("maxProgress", this.maxProgress);
         data.setBoolean("isActive", this.isActive);
+        data.setIntArray("bounds", this.bounds);
         return data;
     }
 
@@ -330,6 +334,7 @@ public void readFromNBT(NBTTagCompound data) {
         this.progressTime = data.getInteger("progressTime");
         this.maxProgress = data.getInteger("maxProgress");
         this.isActive = data.getBoolean("isActive");
+        if (data.hasKey("bounds")) System.arraycopy(data.getIntArray("bounds"), 0, bounds, 0, 6);
         updateFacingMap();
     }
 

From f8f014c8e31fa15c0484adc4fedefaedf2d72605 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sat, 17 Aug 2024 13:06:15 -0700
Subject: [PATCH 31/64] default pattern got real

---
 .../impl/DistillationTowerLogicHandler.java   |  2 +-
 .../multiblock/MultiblockControllerBase.java  |  6 +-
 .../api/pattern/MultiblockShapeInfo.java      | 41 +++++++-
 .../api/pattern/TraceabilityPredicate.java    | 70 +++-----------
 .../api/pattern/pattern/BlockPattern.java     | 95 ++++++++++++++++---
 .../java/gregtech/api/util/BlockInfo.java     | 14 ++-
 .../behaviors/MultiblockBuilderBehavior.java  |  7 +-
 .../MetaTileEntityMultiblockPart.java         |  2 +-
 .../MultiblockInfoRecipeWrapper.java          |  1 -
 9 files changed, 153 insertions(+), 85 deletions(-)

diff --git a/src/main/java/gregtech/api/capability/impl/DistillationTowerLogicHandler.java b/src/main/java/gregtech/api/capability/impl/DistillationTowerLogicHandler.java
index 76da13f1518..5c1a1fdb4af 100644
--- a/src/main/java/gregtech/api/capability/impl/DistillationTowerLogicHandler.java
+++ b/src/main/java/gregtech/api/capability/impl/DistillationTowerLogicHandler.java
@@ -63,7 +63,7 @@ public boolean applyFluidToOutputs(List fluids, boolean doFill) {
      * @param structurePattern the structure pattern
      */
     public void determineLayerCount(@NotNull BlockPattern structurePattern) {
-        this.setLayerCount(structurePattern.formedRepetitionCount[1] + 1);
+        this.setLayerCount(structurePattern.getRepetitionCount(1) + 1);
     }
 
     /**
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 7b52ae49368..41112d6b72a 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -209,8 +209,6 @@ protected void setFlipped(boolean flipped, String name) {
     /**
      * Gets the inactive texture for this part, used for when the multiblock is unformed and you want the part to keep
      * its overlay. Return null to ignore and make hatches go back to their default textures on unform.
-     * This is currently pretty buggy but this is like minimum priority stuff so i cant really be
-     * bothered to fix especially about rendering.
      */
     public @Nullable ICubeRenderer getInactiveTexture(IMultiblockPart part) {
         return null;
@@ -789,7 +787,9 @@ public List getBuildableShapes(@Nullable Object2IntMap symbols;
+    protected final RelativeDirection[] directions;
 
-    public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symbols) {
+    public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symbols, RelativeDirection[] directions) {
         this.aisles = aisles;
         this.symbols = symbols;
+        this.directions = directions;
+    }
+
+    public static Builder builder(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
+        return new Builder(aisleDir, stringDir, charDir);
     }
 
     public static Builder builder() {
-        return new Builder();
+        return builder(RelativeDirection.BACK, RelativeDirection.UP, RelativeDirection.RIGHT);
     }
 
     public static class Builder {
 
         private List shape = new ArrayList<>();
         private Char2ObjectMap symbolMap = new Char2ObjectOpenHashMap<>();
+        private final RelativeDirection[] directions = new RelativeDirection[3];
+
+        public Builder(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
+            directions[0] = aisleDir;
+            directions[1] = stringDir;
+            directions[2] = charDir;
+            int flags = 0;
+            for (int i = 0; i < 3; i++) {
+                switch (directions[i]) {
+                    case UP:
+                    case DOWN:
+                        flags |= 0x1;
+                        break;
+                    case LEFT:
+                    case RIGHT:
+                        flags |= 0x2;
+                        break;
+                    case FRONT:
+                    case BACK:
+                        flags |= 0x4;
+                        break;
+                }
+            }
+            if (flags != 0x7) throw new IllegalArgumentException("Must have 3 different axes!");
+        }
 
         public Builder aisle(String... data) {
             this.shape.add(new PatternAisle(1, data));
@@ -74,14 +107,14 @@ public Builder where(char symbol, Supplier partSupplier, EnumFacing frontSide
         }
 
         public Builder shallowCopy() {
-            Builder builder = new Builder();
+            Builder builder = new Builder(directions[0], directions[1], directions[2]);
             builder.shape = new ArrayList<>(this.shape);
             builder.symbolMap = new Char2ObjectOpenHashMap<>(this.symbolMap);
             return builder;
         }
 
         public MultiblockShapeInfo build() {
-            return new MultiblockShapeInfo(shape.toArray(new PatternAisle[0]), symbolMap);
+            return new MultiblockShapeInfo(shape.toArray(new PatternAisle[0]), symbolMap, directions);
         }
     }
 }
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index 123666baedf..72999d0ab1b 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -27,11 +27,11 @@
 public class TraceabilityPredicate {
 
     // Allow any block.
-    public static final TraceabilityPredicate ANY = new TraceabilityPredicate((state) -> true);
+    public static final TraceabilityPredicate ANY = new TraceabilityPredicate((worldState, patternState) -> true);
     // Allow the air block.
     public static final TraceabilityPredicate AIR = new TraceabilityPredicate(
-            blockWorldState -> blockWorldState.getBlockState().getBlock().isAir(blockWorldState.getBlockState(),
-                    blockWorldState.getWorld(), blockWorldState.getPos()));
+            (worldState, patternState) -> worldState.getBlockState().getBlock().isAir(worldState.getBlockState(),
+                    worldState.getWorld(), worldState.getPos()));
     // Allow all heating coils, and require them to have the same type.
     public static Supplier HEATING_COILS = () -> new TraceabilityPredicate(
             (worldState, patternState) -> GregTechAPI.HEATING_COILS.containsKey(worldState.getBlockState()),
@@ -43,7 +43,6 @@ public class TraceabilityPredicate {
                             .addTooltips("gregtech.multiblock.pattern.error.coils");
 
     public final List common = new ArrayList<>();
-    public final List limited = new ArrayList<>();
     protected boolean isCenter;
     protected boolean hasAir = false;
     protected boolean isSingle = true;
@@ -52,7 +51,6 @@ public TraceabilityPredicate() {}
 
     public TraceabilityPredicate(TraceabilityPredicate predicate) {
         common.addAll(predicate.common);
-        limited.addAll(predicate.limited);
         isCenter = predicate.isCenter;
         hasAir = predicate.hasAir;
         isSingle = predicate.isSingle;
@@ -77,10 +75,6 @@ public TraceabilityPredicate(BiPredicate predicat
         this(predicate, null);
     }
 
-    public boolean isSingle() {
-        return isSingle;
-    }
-
     /**
      * Mark it as the controller of this multi. Normally you won't call it yourself. Use
      * {@link MultiblockControllerBase#selfPredicate()} plz.
@@ -95,7 +89,8 @@ public boolean isCenter() {
     }
 
     public TraceabilityPredicate sort() {
-        limited.sort(Comparator.comparingInt(a -> ((a.minLayerCount + 1) * 100 + a.minGlobalCount)));
+        // reverse so that all min layer and global counts are at the front
+        common.sort(Collections.reverseOrder(Comparator.comparingInt(a -> ((a.minLayerCount + 1) * 100 + a.minGlobalCount))));
         return this;
     }
 
@@ -114,13 +109,6 @@ public TraceabilityPredicate addTooltips(String... tips) {
                 }
                 predicate.toolTips.addAll(tooltips);
             });
-            limited.forEach(predicate -> {
-                if (predicate.candidates == null) return;
-                if (predicate.toolTips == null) {
-                    predicate.toolTips = new ArrayList<>();
-                }
-                predicate.toolTips.addAll(tooltips);
-            });
         }
         return this;
     }
@@ -135,9 +123,6 @@ public List> getCandidates() {
         for (TraceabilityPredicate.SimplePredicate common : common) {
             candidates.add(common.getCandidates());
         }
-        for (TraceabilityPredicate.SimplePredicate limited : limited) {
-            candidates.add(limited.getCandidates());
-        }
         return candidates;
     }
 
@@ -155,11 +140,7 @@ public TraceabilityPredicate addTooltip(String langKey, Object... data) {
      * Set the minimum number of candidate blocks.
      */
     public TraceabilityPredicate setMinGlobalLimited(int min) {
-        limited.addAll(common);
-        common.clear();
-        for (SimplePredicate predicate : limited) {
-            predicate.minGlobalCount = min;
-        }
+        common.forEach(p -> p.minGlobalCount = min);
         return this;
     }
 
@@ -171,11 +152,7 @@ public TraceabilityPredicate setMinGlobalLimited(int min, int previewCount) {
      * Set the maximum number of candidate blocks.
      */
     public TraceabilityPredicate setMaxGlobalLimited(int max) {
-        limited.addAll(common);
-        common.clear();
-        for (SimplePredicate predicate : limited) {
-            predicate.maxGlobalCount = max;
-        }
+        common.forEach(p -> p.maxGlobalCount = max);
         return this;
     }
 
@@ -187,11 +164,7 @@ public TraceabilityPredicate setMaxGlobalLimited(int max, int previewCount) {
      * Set the minimum number of candidate blocks for each aisle layer.
      */
     public TraceabilityPredicate setMinLayerLimited(int min) {
-        limited.addAll(common);
-        common.clear();
-        for (SimplePredicate predicate : limited) {
-            predicate.minLayerCount = min;
-        }
+        common.forEach(p -> p.minLayerCount = min);
         return this;
     }
 
@@ -203,11 +176,7 @@ public TraceabilityPredicate setMinLayerLimited(int min, int previewCount) {
      * Set the maximum number of candidate blocks for each aisle layer.
      */
     public TraceabilityPredicate setMaxLayerLimited(int max) {
-        limited.addAll(common);
-        common.clear();
-        for (SimplePredicate predicate : limited) {
-            predicate.maxLayerCount = max;
-        }
+        common.forEach(p -> p.maxLayerCount = max);
         return this;
     }
 
@@ -228,19 +197,13 @@ public TraceabilityPredicate setExactLimit(int limit) {
      * Set the number of it appears in JEI pages. It only affects JEI preview. (The specific number)
      */
     public TraceabilityPredicate setPreviewCount(int count) {
-        common.forEach(predicate -> predicate.previewCount = count);
-        limited.forEach(predicate -> predicate.previewCount = count);
+        common.forEach(p -> p.previewCount = count);
         return this;
     }
 
-    public boolean test(BlockWorldState blockWorldState, PatternState info, Object2IntMap globalCache,
+    public boolean test(BlockWorldState worldState, PatternState patternState, Object2IntMap globalCache,
                         Object2IntMap layerCache) {
-        for (SimplePredicate predicate : limited) {
-            if (predicate.testLimited(blockWorldState, info, globalCache, layerCache)) {
-                return true;
-            }
-        }
-        return common.stream().anyMatch(predicate -> predicate.test(blockWorldState, info));
+        return common.stream().anyMatch(predicate -> predicate.testLimited(worldState, patternState, globalCache, layerCache));
     }
 
     public TraceabilityPredicate or(TraceabilityPredicate other) {
@@ -253,7 +216,6 @@ public TraceabilityPredicate or(TraceabilityPredicate other) {
             }
             newPredicate.hasAir = newPredicate.hasAir || this == AIR || other == AIR;
             newPredicate.common.addAll(other.common);
-            newPredicate.limited.addAll(other.limited);
             return newPredicate;
         }
         return this;
@@ -323,10 +285,6 @@ public List getToolTips(TraceabilityPredicate predicates) {
             return result;
         }
 
-        public boolean test(BlockWorldState state, PatternState info) {
-            return predicate.test(state, info);
-        }
-
         public boolean testLimited(BlockWorldState worldState, PatternState patternState,
                                    Object2IntMap globalCache,
                                    Object2IntMap layerCache) {
@@ -335,7 +293,7 @@ public boolean testLimited(BlockWorldState worldState, PatternState patternState
 
         public boolean testGlobal(BlockWorldState worldState, PatternState patternState,
                                   Object2IntMap cache) {
-            if (minGlobalCount == -1 && maxGlobalCount == -1 || cache == null) return true;
+            if (minGlobalCount == -1 && maxGlobalCount == -1 || cache == null) return predicate.test(worldState, patternState);
 
             boolean base = predicate.test(worldState, patternState);
             int count = cache.getInt(this);
@@ -348,7 +306,7 @@ public boolean testGlobal(BlockWorldState worldState, PatternState patternState,
 
         public boolean testLayer(BlockWorldState worldState, PatternState patternState,
                                  Object2IntMap cache) {
-            if (minLayerCount == -1 && maxLayerCount == -1 || cache == null) return true;
+            if (minLayerCount == -1 && maxLayerCount == -1 || cache == null) return predicate.test(worldState, patternState);
 
             boolean base = predicate.test(worldState, patternState);
             int count = cache.getInt(this);
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 3051f01cf8f..87a44b0c61d 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -1,5 +1,7 @@
 package gregtech.api.pattern.pattern;
 
+import com.github.bsideup.jabel.Desugar;
+
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
@@ -10,6 +12,12 @@
 import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
+import it.unimi.dsi.fastutil.chars.Char2IntMap;
+import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap;
+import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.chars.CharIterator;
+import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
+
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
@@ -21,9 +29,16 @@
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import org.apache.commons.lang3.tuple.Pair;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
 public class BlockPattern implements IBlockPattern {
 
     /**
@@ -49,11 +64,6 @@ public class BlockPattern implements IBlockPattern {
     protected final PatternState state = new PatternState();
     protected final Long2ObjectMap cache = new Long2ObjectOpenHashMap<>();
 
-    /**
-     * The repetitions per aisle along the axis of repetition
-     */
-    public int[] formedRepetitionCount;
-
     // how many not nulls to keep someone from not passing in null?
     public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] dimensions,
                         @NotNull RelativeDirection @NotNull [] directions,
@@ -295,17 +305,73 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
         return true;
     }
 
+    public int getRepetitionCount(int aisleI) {
+        return aisles[aisleI].actualRepeats;
+    }
+
     @Override
-    // todo get real
     public MultiblockShapeInfo getDefaultShape() {
-        char[][][] pattern = new char[dimensions[2]][dimensions[1]][dimensions[0]];
-
-        for (PatternAisle aisle : aisles) {
-            char[][] resultAisle = new char[dimensions[2]][dimensions[1]];
+        // for each symbol, which simple predicate is being used
+        // this advances whenever a minimum has been satisfied(if any), or a maximum has been reached(if any)
+        // preview counts are treated as exactly that many
+        Char2IntMap predicateIndex = new Char2IntOpenHashMap();
+        // candidates to be passed into MultiblockShapeInfo
+        Char2ObjectMap candidates = new Char2ObjectOpenHashMap<>();
+        // cache for candidates
+        Map infos = new HashMap<>();
+        Object2IntMap globalCache = new Object2IntOpenHashMap<>();
+        Object2IntMap layerCache = new Object2IntOpenHashMap<>();
+
+        List pattern = new ArrayList<>(dimensions[0]);
+
+        // 0 is reserved as a null for char
+        char currentChar = 1;
 
-            for (String str : aisle.pattern) {
-                for (char c : str.toCharArray()) {
-                    // TraceabilityPredicate predicate =
+        for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
+            for (int repeats = 1; repeats <= aisles[aisleI].minRepeats; repeats++) {
+                pattern.add(new char[dimensions[1]][dimensions[2]]);
+                layerCache.clear();
+                for (int stringI = 0; stringI < dimensions[1]; stringI++) {
+                    for (int charI = 0; charI < dimensions[2]; charI++) {
+                        char c = aisles[aisleI].charAt(stringI, charI);
+                        TraceabilityPredicate predicate = predicates.get(c);
+                        TraceabilityPredicate.SimplePredicate simple = predicate.common.get(predicateIndex.get(c));
+
+                        if (simple.candidates == null) continue;
+
+                        // cache all the BlockInfo, so that we only need to get it once per simple predicate
+                        if (!infos.containsKey(simple)) {
+                            BlockInfo info = simple.candidates.get()[0];
+                            infos.put(simple, new BlockInfoChar(info, currentChar));
+                            candidates.put(currentChar, info);
+                            currentChar++;
+                        }
+
+                        BlockInfoChar info = infos.get(simple);
+                        int layerCount = layerCache.getInt(simple) + 1;
+                        int globalCount = globalCache.getInt(simple) + 1;
+
+                        pattern.get(aisleI + repeats - 1)[stringI][charI] = info.c;
+                        layerCache.put(simple, layerCount);
+                        globalCache.put(simple, globalCount);
+
+                        TraceabilityPredicate.SimplePredicate next = simple;
+
+                        // don't need inequalities since everything is incremented once at a time
+                        // min layer reached OR min global reached OR preview reached OR max layer reached OR max global reached
+                        while ((next.minLayerCount != -1 && layerCount == next.minLayerCount) ||
+                                (next.minGlobalCount != -1 && globalCount == next.minGlobalCount) ||
+                                (next.previewCount != -1 && globalCount == next.previewCount) ||
+                                (next.maxLayerCount != -1 && layerCount == next.maxLayerCount) ||
+                                (next.maxGlobalCount != -1 && globalCount == next.maxGlobalCount)) {
+                            // if the current predicate is used, move until the next free one
+                            int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
+                            if (newIndex >= predicate.common.size()) {
+                                GTLog.logger.warn("Failed to geneoirate default pattern, next simple predicate would have been out of bounds.", new IllegalStateException());
+                            }
+                            next = predicate.common.get(newIndex);
+                        }
+                    }
                 }
             }
         }
@@ -334,4 +400,7 @@ private GreggyBlockPos startPos(GreggyBlockPos controllerPos, EnumFacing frontFa
         offset.apply(start, frontFacing, upFacing, flip);
         return start;
     }
+
+    @Desugar
+    public record BlockInfoChar(BlockInfo info, char c) {}
 }
diff --git a/src/main/java/gregtech/api/util/BlockInfo.java b/src/main/java/gregtech/api/util/BlockInfo.java
index 2f2340db46d..725c10f0cd6 100644
--- a/src/main/java/gregtech/api/util/BlockInfo.java
+++ b/src/main/java/gregtech/api/util/BlockInfo.java
@@ -1,5 +1,7 @@
 package gregtech.api.util;
 
+import gregtech.api.pattern.TraceabilityPredicate;
+
 import net.minecraft.block.Block;
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.init.Blocks;
@@ -20,7 +22,7 @@ public class BlockInfo {
 
     private final IBlockState blockState;
     private final TileEntity tileEntity;
-    private final Object info;
+    private final TraceabilityPredicate predicate;
 
     public BlockInfo(Block block) {
         this(block.getDefaultState());
@@ -34,10 +36,12 @@ public BlockInfo(IBlockState blockState, TileEntity tileEntity) {
         this(blockState, tileEntity, null);
     }
 
-    public BlockInfo(IBlockState blockState, TileEntity tileEntity, Object info) {
+    public BlockInfo(IBlockState blockState, TileEntity tileEntity, TraceabilityPredicate predicate) {
+        // the predicate is an extremely scuffed way of displaying candidates during preview
+        // ideally you would bind the predicate to the char in the code but uh yeah
         this.blockState = blockState;
         this.tileEntity = tileEntity;
-        this.info = info;
+        this.predicate = predicate;
         Preconditions.checkArgument(tileEntity == null || blockState.getBlock().hasTileEntity(blockState),
                 "Cannot create block info with tile entity for block not having it");
     }
@@ -50,8 +54,8 @@ public TileEntity getTileEntity() {
         return tileEntity;
     }
 
-    public Object getInfo() {
-        return info;
+    public TraceabilityPredicate getPredicate() {
+        return predicate;
     }
 
     public void apply(World world, BlockPos pos) {
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index c03251b80d0..c1b88853021 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -6,6 +6,10 @@
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.util.GTUtility;
 
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+
 import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.item.Item;
@@ -22,6 +26,7 @@
 
 import org.jetbrains.annotations.NotNull;
 
+import java.util.Collections;
 import java.util.List;
 
 public class MultiblockBuilderBehavior implements IItemBehaviour {
@@ -42,7 +47,7 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
             if (!multiblock.isStructureFormed("MAIN")) {
-                // multiblock.structurePatterns[0].autoBuild(player, multiblock);
+                 multiblock.getBuildableShapes(new Object2IntOpenHashMap<>(), false);
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
index 810f6b2469f..aa06de607fa 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
@@ -253,7 +253,7 @@ private void removeController(@NotNull MultiblockControllerBase controller) {
         controllers.remove(index);
 
         // if the last controller changed, sync it
-        if (index == (controllers.size() - 1)) {
+        if (index == controllers.size()) {
             syncLastController();
         }
 
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index ee0a1de18a1..13f2c18e0f1 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -429,7 +429,6 @@ public boolean handleClick(@NotNull Minecraft minecraft, int mouseX, int mouseY,
                 TraceabilityPredicate predicate = patterns[currentRendererPage].predicateMap.get(this.selected);
                 if (predicate != null) {
                     predicates.addAll(predicate.common);
-                    predicates.addAll(predicate.limited);
                     predicates.removeIf(p -> p.candidates == null);
                     this.father = predicate;
                     setItemStackGroup();

From 9fd82025937e4d48d150f68c62c83fe5cda59a83 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 18 Aug 2024 00:23:01 -0700
Subject: [PATCH 32/64] arch code is like an onion......

---
 .../multiblock/MultiblockControllerBase.java  |  16 +--
 .../api/pattern/MultiblockShapeInfo.java      |  68 ++++++++++-
 .../api/pattern/pattern/BlockPattern.java     | 112 +++++++++++++-----
 .../api/pattern/pattern/PatternAisle.java     |  11 +-
 .../java/gregtech/api/util/BlockInfo.java     |   6 +
 .../electric/MetaTileEntityCleanroom.java     |   2 +-
 .../MetaTileEntityElectricBlastFurnace.java   |   2 +-
 .../MetaTileEntityPowerSubstation.java        |   2 +-
 .../MetaTileEntityCentralMonitor.java         |   3 +-
 .../MultiblockInfoRecipeWrapper.java          |  30 ++---
 10 files changed, 180 insertions(+), 72 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 41112d6b72a..1f96eff4b87 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -463,17 +463,12 @@ public void checkStructurePattern(String name) {
                 // parts *should* not have this controller added
                 multiblockParts.add(part);
                 part.addToMultiBlock(this, name);
-                // if (part instanceof IMultiblockAbilityPartabilityPart) {
-                // // noinspection unchecked
-                // registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
-                // }
                 return true;
             });
 
-            // todo this is maybe a bandaid fix, maybe use NavigableSet instead of using List and
-            // relying on the NavigableSet of multiblockParts?
+            // maybe bandaid fix
             for (IMultiblockPart part : multiblockParts) {
-                if (name.equals(part.getSubstructureName()) && part instanceof IMultiblockAbilityPartabilityPart) {
+                if (name.equals(part.getSubstructureName()) && part instanceof IMultiblockAbilityPart abilityPart) {
                     // noinspection unchecked
                     registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
                 }
@@ -682,9 +677,7 @@ public boolean isStructureFormed() {
     }
 
     public boolean isStructureFormed(String name) {
-        if (getWorld() == null) return false;
-
-        return getSubstructure(name).getPatternState().isFormed();
+        return getWorld() != null && getSubstructure(name) != null && getSubstructure(name).getPatternState().isFormed();
     }
 
     @Override
@@ -787,6 +780,9 @@ public List getBuildableShapes(@Nullable Object2IntMap symb
         this.aisles = aisles;
         this.symbols = symbols;
         this.directions = directions;
+        symbols.defaultReturnValue(BlockInfo.EMPTY);
+    }
+
+    /**
+     * Gets a map of where blocks should be placed, note that the controller is always facing south(and up facing NORTH).
+     * Unlike BlockPattern, the first char in the first string in the first aisle always starts at the origin, instead
+     * of being relative to the controller.
+     */
+    public Map getMap() {
+        // seems like MultiblockInfoRecipeWrapper wants the controller to be facing south
+        EnumFacing absoluteAisle =  directions[0].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
+        EnumFacing absoluteString =  directions[1].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
+        EnumFacing absoluteChar =  directions[2].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
+
+        int aisleCount = aisles.length;
+        int stringCount = aisles[0].getStringCount();
+        int charCount = aisles[0].getCharCount();
+
+        GreggyBlockPos pos = new GreggyBlockPos();
+        Map map = new HashMap<>();
+
+        for (int aisleI = 0; aisleI < aisleCount; aisleI++) {
+            for (int stringI = 0; stringI < stringCount; stringI++) {
+                for (int charI = 0; charI < charCount; charI++) {
+                    char c = aisles[aisleI].charAt(stringI, charI);
+                    pos.zero().offset(absoluteAisle, aisleI).offset(absoluteString, stringI).offset(absoluteChar, charI);
+                    if (symbols.get(c).getTileEntity() instanceof MetaTileEntityHolder holder) {
+                        MetaTileEntityHolder mteHolder = new MetaTileEntityHolder();
+                        mteHolder.setMetaTileEntity(holder.getMetaTileEntity());
+                        mteHolder.getMetaTileEntity().onPlacement();
+                        mteHolder.getMetaTileEntity().setFrontFacing(holder.getMetaTileEntity().getFrontFacing());
+                        map.put(pos.immutable(), new BlockInfo(mteHolder.getMetaTileEntity().getBlock().getDefaultState(), mteHolder));
+                    } else {
+                        map.put(pos.immutable(), symbols.get(c));
+                    }
+                }
+            }
+        }
+
+        if (true) return map;
+
+        // scuffed but tries to make hatches face out the structure
+        for (Map.Entry entry : map.entrySet()) {
+            BlockInfo info = entry.getValue();
+            if (info.getTileEntity() != null && info.getTileEntity() instanceof MetaTileEntityHolder holder) {
+                MetaTileEntity mte = holder.getMetaTileEntity();
+                for (EnumFacing facing : EnumFacing.VALUES) {
+                    BlockPos offset = entry.getKey().offset(facing);
+                    boolean isOutside = !map.containsKey(offset);
+                    if (isOutside && mte.isValidFrontFacing(facing)) {
+                        MetaTileEntityHolder mteHolder = new MetaTileEntityHolder();
+                        mteHolder.setMetaTileEntity(mte);
+                        mteHolder.getMetaTileEntity().onPlacement();
+                        mteHolder.getMetaTileEntity().setFrontFacing(facing);
+                        entry.setValue(new BlockInfo(mte.getBlock().getDefaultState(), mteHolder));
+                    }
+                }
+            }
+        }
+
+        return map;
     }
 
     public static Builder builder(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
@@ -34,7 +99,8 @@ public static Builder builder(RelativeDirection aisleDir, RelativeDirection stri
     }
 
     public static Builder builder() {
-        return builder(RelativeDirection.BACK, RelativeDirection.UP, RelativeDirection.RIGHT);
+        // this is front now because idk the old code somehow reversed it and im not about to look into it
+        return builder(RelativeDirection.FRONT, RelativeDirection.UP, RelativeDirection.RIGHT);
     }
 
     public static class Builder {
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 87a44b0c61d..62e363bc75c 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -1,7 +1,5 @@
 package gregtech.api.pattern.pattern;
 
-import com.github.bsideup.jabel.Desugar;
-
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
@@ -12,32 +10,28 @@
 import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
-import it.unimi.dsi.fastutil.chars.Char2IntMap;
-import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap;
-import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
-import it.unimi.dsi.fastutil.chars.CharIterator;
-import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
-
 import net.minecraft.block.state.IBlockState;
+import net.minecraft.init.Blocks;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
 
+import it.unimi.dsi.fastutil.chars.Char2IntMap;
+import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
+import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2CharMap;
+import it.unimi.dsi.fastutil.objects.Object2CharOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
-import org.apache.commons.lang3.tuple.Pair;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.function.Predicate;
 
 public class BlockPattern implements IBlockPattern {
 
@@ -267,7 +261,6 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
         for (int stringI = 0; stringI < dimensions[1]; stringI++) {
             for (int charI = 0; charI < dimensions[2]; charI++) {
                 worldState.setPos(charPos);
-                if (flip) GTLog.logger.info("checked with flip true at " + charPos);
                 TraceabilityPredicate predicate = predicates.get(aisle.charAt(stringI, charI));
 
                 if (predicate != TraceabilityPredicate.ANY) {
@@ -318,7 +311,7 @@ public MultiblockShapeInfo getDefaultShape() {
         // candidates to be passed into MultiblockShapeInfo
         Char2ObjectMap candidates = new Char2ObjectOpenHashMap<>();
         // cache for candidates
-        Map infos = new HashMap<>();
+        Object2CharMap infos = new Object2CharOpenHashMap<>();
         Object2IntMap globalCache = new Object2IntOpenHashMap<>();
         Object2IntMap layerCache = new Object2IntOpenHashMap<>();
 
@@ -326,7 +319,9 @@ public MultiblockShapeInfo getDefaultShape() {
 
         // 0 is reserved as a null for char
         char currentChar = 1;
+        int aisleOffset = 0;
 
+        // first pass fills in all the minimum counts
         for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
             for (int repeats = 1; repeats <= aisles[aisleI].minRepeats; repeats++) {
                 pattern.add(new char[dimensions[1]][dimensions[2]]);
@@ -335,6 +330,8 @@ public MultiblockShapeInfo getDefaultShape() {
                     for (int charI = 0; charI < dimensions[2]; charI++) {
                         char c = aisles[aisleI].charAt(stringI, charI);
                         TraceabilityPredicate predicate = predicates.get(c);
+                        // we used up all the simple predicates, just let the second pass fill them in
+                        if (predicateIndex.get(c) >= predicate.common.size()) continue;
                         TraceabilityPredicate.SimplePredicate simple = predicate.common.get(predicateIndex.get(c));
 
                         if (simple.candidates == null) continue;
@@ -342,41 +339,95 @@ public MultiblockShapeInfo getDefaultShape() {
                         // cache all the BlockInfo, so that we only need to get it once per simple predicate
                         if (!infos.containsKey(simple)) {
                             BlockInfo info = simple.candidates.get()[0];
-                            infos.put(simple, new BlockInfoChar(info, currentChar));
+                            infos.put(simple, currentChar);
                             candidates.put(currentChar, info);
                             currentChar++;
                         }
 
-                        BlockInfoChar info = infos.get(simple);
-                        int layerCount = layerCache.getInt(simple) + 1;
-                        int globalCount = globalCache.getInt(simple) + 1;
+                        char info = infos.getChar(simple);
+                        int layerCount = layerCache.put(simple, layerCache.getInt(simple) + 1) + 1;
+                        int globalCount = globalCache.put(simple, globalCache.getInt(simple) + 1) + 1;
 
-                        pattern.get(aisleI + repeats - 1)[stringI][charI] = info.c;
-                        layerCache.put(simple, layerCount);
-                        globalCache.put(simple, globalCount);
+                        // replace all air with 0 instead of whatever char
+                        pattern.get(aisleOffset)[stringI][charI] = candidates.get(info).getBlockState() ==
+                                Blocks.AIR.getDefaultState() ? 0 : info;
 
                         TraceabilityPredicate.SimplePredicate next = simple;
 
                         // don't need inequalities since everything is incremented once at a time
-                        // min layer reached OR min global reached OR preview reached OR max layer reached OR max global reached
-                        while ((next.minLayerCount != -1 && layerCount == next.minLayerCount) ||
-                                (next.minGlobalCount != -1 && globalCount == next.minGlobalCount) ||
-                                (next.previewCount != -1 && globalCount == next.previewCount) ||
+                        // only put the minimum amount of parts possible
+                        // missing parts will be filled in the second pass
+                        while ((next.previewCount == -1 || globalCount == next.previewCount) &&
+                                (next.minLayerCount == -1 || layerCount == next.minLayerCount) &&
+                                (next.minGlobalCount == -1 || globalCount == next.minGlobalCount)) {
+                            // if the current predicate is used, move until the next free one
+                            int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
+                            if (newIndex >= predicate.common.size()) break;
+                            next = predicate.common.get(newIndex);
+                            globalCount = globalCache.getInt(next);
+                            layerCount = layerCache.getInt(next);
+                        }
+                    }
+                }
+                aisleOffset++;
+            }
+        }
+
+        predicateIndex.clear();
+        aisleOffset = 0;
+
+        // second pass fills everything else
+        for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
+            for (int repeats = 1; repeats <= aisles[aisleI].minRepeats; repeats++) {
+                layerCache.clear();
+                for (int stringI = 0; stringI < dimensions[1]; stringI++) {
+                    for (int charI = 0; charI < dimensions[2]; charI++) {
+                        // skip if populated by first pass
+                        if (pattern.get(aisleOffset)[stringI][charI] != 0) continue;
+
+                        char c = aisles[aisleI].charAt(stringI, charI);
+                        TraceabilityPredicate predicate = predicates.get(c);
+                        TraceabilityPredicate.SimplePredicate next = predicate.common.get(predicateIndex.get(c));
+
+                        int layerCount = layerCache.getInt(next);
+                        int globalCount = globalCache.getInt(next);
+
+                        // don't need inequalities since everything is incremented once at a time
+                        // do this first because the first pass could have left some predicates already used
+                        while ((next.previewCount != -1 && globalCount == next.previewCount) ||
                                 (next.maxLayerCount != -1 && layerCount == next.maxLayerCount) ||
                                 (next.maxGlobalCount != -1 && globalCount == next.maxGlobalCount)) {
                             // if the current predicate is used, move until the next free one
                             int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
-                            if (newIndex >= predicate.common.size()) {
-                                GTLog.logger.warn("Failed to geneoirate default pattern, next simple predicate would have been out of bounds.", new IllegalStateException());
-                            }
+                            if (newIndex >= predicate.common.size())
+                                GTLog.logger.warn("Failed to generate default structure pattern.", new IllegalStateException());
                             next = predicate.common.get(newIndex);
+                            globalCount = globalCache.getInt(next);
+                            layerCount = layerCache.getInt(next);
                         }
+
+                        if (next.candidates == null) continue;
+
+                        if (!infos.containsKey(next)) {
+                            BlockInfo info = next.candidates.get()[0];
+                            infos.put(next, currentChar);
+                            candidates.put(currentChar, info);
+                            currentChar++;
+                        }
+
+                        char info = infos.getChar(next);
+                        layerCache.put(next, layerCount + 1);
+                        globalCache.put(next, globalCount + 1);
+
+                        pattern.get(aisleOffset)[stringI][charI] = candidates.get(info).getBlockState() ==
+                                Blocks.AIR.getDefaultState() ? 0 : info;
                     }
                 }
+                aisleOffset++;
             }
         }
 
-        return null;
+        return new MultiblockShapeInfo(pattern.stream().map(a -> new PatternAisle(1, a)).toArray(PatternAisle[]::new), candidates, directions);
     }
 
     @Override
@@ -400,7 +451,4 @@ private GreggyBlockPos startPos(GreggyBlockPos controllerPos, EnumFacing frontFa
         offset.apply(start, frontFacing, upFacing, flip);
         return start;
     }
-
-    @Desugar
-    public record BlockInfoChar(BlockInfo info, char c) {}
 }
diff --git a/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java b/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
index 717e3fb2444..bff4158588a 100644
--- a/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
+++ b/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
@@ -7,6 +7,14 @@ public class PatternAisle {
     protected int minRepeats, maxRepeats, actualRepeats;
     protected final String[] pattern;
 
+    public PatternAisle(int repeats, char[][] pattern) {
+        this.pattern = new String[pattern.length];
+        for (int i = 0; i < pattern.length; i++) {
+            this.pattern[i] = new String(pattern[i]);
+        }
+        this.minRepeats = this.maxRepeats = repeats;
+    }
+
     public PatternAisle(int minRepeats, int maxRepeats, String[] pattern) {
         this.minRepeats = minRepeats;
         this.maxRepeats = maxRepeats;
@@ -14,8 +22,7 @@ public PatternAisle(int minRepeats, int maxRepeats, String[] pattern) {
     }
 
     public PatternAisle(int repeats, String[] pattern) {
-        this.minRepeats = this.maxRepeats = repeats;
-        this.pattern = pattern;
+        this(repeats, repeats, pattern);
     }
 
     public void setRepeats(int minRepeats, int maxRepeats) {
diff --git a/src/main/java/gregtech/api/util/BlockInfo.java b/src/main/java/gregtech/api/util/BlockInfo.java
index 725c10f0cd6..75282aaa5d9 100644
--- a/src/main/java/gregtech/api/util/BlockInfo.java
+++ b/src/main/java/gregtech/api/util/BlockInfo.java
@@ -23,6 +23,7 @@ public class BlockInfo {
     private final IBlockState blockState;
     private final TileEntity tileEntity;
     private final TraceabilityPredicate predicate;
+    private boolean manualFacing;
 
     public BlockInfo(Block block) {
         this(block.getDefaultState());
@@ -46,6 +47,11 @@ public BlockInfo(IBlockState blockState, TileEntity tileEntity, TraceabilityPred
                 "Cannot create block info with tile entity for block not having it");
     }
 
+    public BlockInfo manualFacing() {
+        this.manualFacing = true;
+        return this;
+    }
+
     public IBlockState getBlockState() {
         return blockState;
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index 2f369dcaab2..f02bf606806 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -732,7 +732,7 @@ public List getMatchingShapes() {
         GregTechAPI.CLEANROOM_FILTERS.entrySet().stream()
                 .filter(entry -> entry.getValue().getCleanroomType() != null)
                 .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
-                .forEach(entry -> shapeInfo.add(builder.where('F', entry.getKey()).build()));
+                .forEach(entry -> shapeInfo.add(builder.shallowCopy().where('F', entry.getKey()).build()));
 
         return shapeInfo;
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index a356974ac87..ec826950ff5 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -196,7 +196,7 @@ public List getMatchingShapes() {
                         MetaBlocks.METAL_CASING.getState(MetalCasingType.INVAR_HEATPROOF), EnumFacing.NORTH);
         GregTechAPI.HEATING_COILS.entrySet().stream()
                 .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
-                .forEach(entry -> shapeInfo.add(builder.where('C', entry.getKey()).build()));
+                .forEach(entry -> shapeInfo.add(builder.shallowCopy().where('C', entry.getKey()).build()));
         return shapeInfo;
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index 5f0e95f8ac7..7ec03dacc08 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -275,7 +275,7 @@ public List getMatchingShapes() {
                 // allowed in the predicate (so you can see them on right-click)
                 .filter(entry -> entry.getValue().getCapacity() > 0)
                 .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
-                .forEach(entry -> shapeInfo.add(builder.where('B', entry.getKey()).build()));
+                .forEach(entry -> shapeInfo.add(builder.shallowCopy().where('B', entry.getKey()).build()));
 
         return shapeInfo;
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index 95d9c132932..944269d80d1 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -441,8 +441,7 @@ protected void formStructure(String name) {
         width = width / height;
         screens = new MetaTileEntityMonitorScreen[width][height];
         for (IMultiblockPart part : this.getMultiblockParts()) {
-            if (part instanceof MetaTileEntityMonitorScreen) {
-                MetaTileEntityMonitorScreen screen = (MetaTileEntityMonitorScreen) part;
+            if (part instanceof MetaTileEntityMonitorScreen screen) {
                 screens[screen.getX()][screen.getY()] = screen;
             }
         }
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 13f2c18e0f1..790c3e44e8d 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -123,7 +123,7 @@ public MBPattern(final WorldSceneRenderer sceneRenderer, final List p
     public MultiblockInfoRecipeWrapper(@NotNull MultiblockControllerBase controller) {
         this.controller = controller;
         Set drops = new ObjectOpenCustomHashSet<>(ItemStackHashStrategy.comparingAllButCount());
-        this.patterns = controller.getMatchingShapes().stream()
+        this.patterns = controller.getBuildableShapes(null, false).stream()
                 .map(it -> initializePattern(it, drops))
                 .toArray(MBPattern[]::new);
         allItemStackInputs.addAll(drops);
@@ -566,30 +566,16 @@ private static Collection gatherStructureBlocks(World world, @NotNull
     @SuppressWarnings("NewExpressionSideOnly")
     @NotNull
     private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @NotNull Set parts) {
-        Map blockMap = new HashMap<>();
-        MultiblockControllerBase controllerBase = null;
-        BlockInfo[][][] blocks = null;
-        // todo fix
-        for (int x = 0; x < blocks.length; x++) {
-            BlockInfo[][] aisle = blocks[x];
-            for (int y = 0; y < aisle.length; y++) {
-                BlockInfo[] column = aisle[y];
-                for (int z = 0; z < column.length; z++) {
-                    if (column[z].getTileEntity() instanceof IGregTechTileEntity &&
-                            ((IGregTechTileEntity) column[z].getTileEntity())
-                                    .getMetaTileEntity() instanceof MultiblockControllerBase) {
-                        controllerBase = (MultiblockControllerBase) ((IGregTechTileEntity) column[z].getTileEntity())
-                                .getMetaTileEntity();
-                    }
-                    blockMap.put(new BlockPos(x, y, z), column[z]);
-                }
-            }
-        }
+        Map blockMap = shapeInfo.getMap();
 
         TrackedDummyWorld world = new TrackedDummyWorld();
         ImmediateWorldSceneRenderer worldSceneRenderer = new ImmediateWorldSceneRenderer(world);
         worldSceneRenderer.setClearColor(ConfigHolder.client.multiblockPreviewColor);
-        world.addBlocks(blockMap);
+        try {
+            world.addBlocks(blockMap);
+        } catch (Exception e) {
+            throw e;
+        }
 
         Vector3f size = world.getSize();
         Vector3f minPos = world.getMinPos();
@@ -613,7 +599,7 @@ private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @Not
                 pos -> worldSceneRenderer.renderedBlocksMap.keySet().stream().anyMatch(c -> c.contains(pos)));
 
         Map predicateMap = new HashMap<>();
-        if (controllerBase != null) {
+        if (false) {
             // if (controllerBase.structurePattern == null) {
             // controllerBase.reinitializeStructurePattern();
             // }

From ec3f7f834ca7a14d5387dbf83c8766944173e813 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 18 Aug 2024 00:25:08 -0700
Subject: [PATCH 33/64] zpotlezz

---
 .../multiblock/MultiblockControllerBase.java  |  6 ++---
 .../api/pattern/MultiblockShapeInfo.java      | 23 +++++++++++--------
 .../api/pattern/TraceabilityPredicate.java    | 15 ++++++++----
 .../api/pattern/pattern/BlockPattern.java     |  6 +++--
 .../api/pattern/pattern/IBlockPattern.java    |  3 ++-
 .../behaviors/MultiblockBuilderBehavior.java  |  8 ++-----
 .../MetaTileEntityCharcoalPileIgniter.java    | 19 ++++++++-------
 7 files changed, 45 insertions(+), 35 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 1f96eff4b87..7cdee98a8b0 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -75,7 +75,6 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
-import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -468,7 +467,7 @@ public void checkStructurePattern(String name) {
 
             // maybe bandaid fix
             for (IMultiblockPart part : multiblockParts) {
-                if (name.equals(part.getSubstructureName()) && part instanceof IMultiblockAbilityPart abilityPart) {
+                if (name.equals(part.getSubstructureName()) && part instanceof IMultiblockAbilityPartabilityPart) {
                     // noinspection unchecked
                     registerMultiblockAbility((IMultiblockAbilityPart) abilityPart);
                 }
@@ -677,7 +676,8 @@ public boolean isStructureFormed() {
     }
 
     public boolean isStructureFormed(String name) {
-        return getWorld() != null && getSubstructure(name) != null && getSubstructure(name).getPatternState().isFormed();
+        return getWorld() != null && getSubstructure(name) != null &&
+                getSubstructure(name).getPatternState().isFormed();
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index 3735fe0ca33..3a987e81487 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -4,18 +4,16 @@
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.pattern.pattern.PatternAisle;
 import gregtech.api.util.BlockInfo;
-
 import gregtech.api.util.RelativeDirection;
 
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
+import net.minecraft.util.math.BlockPos;
 
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
 
-import net.minecraft.util.math.BlockPos;
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -23,11 +21,13 @@
 import java.util.function.Supplier;
 
 public class MultiblockShapeInfo {
+
     protected final PatternAisle[] aisles;
     protected final Char2ObjectMap symbols;
     protected final RelativeDirection[] directions;
 
-    public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symbols, RelativeDirection[] directions) {
+    public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symbols,
+                               RelativeDirection[] directions) {
         this.aisles = aisles;
         this.symbols = symbols;
         this.directions = directions;
@@ -35,15 +35,16 @@ public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symb
     }
 
     /**
-     * Gets a map of where blocks should be placed, note that the controller is always facing south(and up facing NORTH).
+     * Gets a map of where blocks should be placed, note that the controller is always facing south(and up facing
+     * NORTH).
      * Unlike BlockPattern, the first char in the first string in the first aisle always starts at the origin, instead
      * of being relative to the controller.
      */
     public Map getMap() {
         // seems like MultiblockInfoRecipeWrapper wants the controller to be facing south
-        EnumFacing absoluteAisle =  directions[0].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
-        EnumFacing absoluteString =  directions[1].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
-        EnumFacing absoluteChar =  directions[2].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
+        EnumFacing absoluteAisle = directions[0].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
+        EnumFacing absoluteString = directions[1].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
+        EnumFacing absoluteChar = directions[2].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
 
         int aisleCount = aisles.length;
         int stringCount = aisles[0].getStringCount();
@@ -56,13 +57,15 @@ public Map getMap() {
             for (int stringI = 0; stringI < stringCount; stringI++) {
                 for (int charI = 0; charI < charCount; charI++) {
                     char c = aisles[aisleI].charAt(stringI, charI);
-                    pos.zero().offset(absoluteAisle, aisleI).offset(absoluteString, stringI).offset(absoluteChar, charI);
+                    pos.zero().offset(absoluteAisle, aisleI).offset(absoluteString, stringI).offset(absoluteChar,
+                            charI);
                     if (symbols.get(c).getTileEntity() instanceof MetaTileEntityHolder holder) {
                         MetaTileEntityHolder mteHolder = new MetaTileEntityHolder();
                         mteHolder.setMetaTileEntity(holder.getMetaTileEntity());
                         mteHolder.getMetaTileEntity().onPlacement();
                         mteHolder.getMetaTileEntity().setFrontFacing(holder.getMetaTileEntity().getFrontFacing());
-                        map.put(pos.immutable(), new BlockInfo(mteHolder.getMetaTileEntity().getBlock().getDefaultState(), mteHolder));
+                        map.put(pos.immutable(),
+                                new BlockInfo(mteHolder.getMetaTileEntity().getBlock().getDefaultState(), mteHolder));
                     } else {
                         map.put(pos.immutable(), symbols.get(c));
                     }
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index 72999d0ab1b..6c59fbaf2ea 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -90,7 +90,8 @@ public boolean isCenter() {
 
     public TraceabilityPredicate sort() {
         // reverse so that all min layer and global counts are at the front
-        common.sort(Collections.reverseOrder(Comparator.comparingInt(a -> ((a.minLayerCount + 1) * 100 + a.minGlobalCount))));
+        common.sort(Collections
+                .reverseOrder(Comparator.comparingInt(a -> ((a.minLayerCount + 1) * 100 + a.minGlobalCount))));
         return this;
     }
 
@@ -201,9 +202,11 @@ public TraceabilityPredicate setPreviewCount(int count) {
         return this;
     }
 
-    public boolean test(BlockWorldState worldState, PatternState patternState, Object2IntMap globalCache,
+    public boolean test(BlockWorldState worldState, PatternState patternState,
+                        Object2IntMap globalCache,
                         Object2IntMap layerCache) {
-        return common.stream().anyMatch(predicate -> predicate.testLimited(worldState, patternState, globalCache, layerCache));
+        return common.stream()
+                .anyMatch(predicate -> predicate.testLimited(worldState, patternState, globalCache, layerCache));
     }
 
     public TraceabilityPredicate or(TraceabilityPredicate other) {
@@ -293,7 +296,8 @@ public boolean testLimited(BlockWorldState worldState, PatternState patternState
 
         public boolean testGlobal(BlockWorldState worldState, PatternState patternState,
                                   Object2IntMap cache) {
-            if (minGlobalCount == -1 && maxGlobalCount == -1 || cache == null) return predicate.test(worldState, patternState);
+            if (minGlobalCount == -1 && maxGlobalCount == -1 || cache == null)
+                return predicate.test(worldState, patternState);
 
             boolean base = predicate.test(worldState, patternState);
             int count = cache.getInt(this);
@@ -306,7 +310,8 @@ public boolean testGlobal(BlockWorldState worldState, PatternState patternState,
 
         public boolean testLayer(BlockWorldState worldState, PatternState patternState,
                                  Object2IntMap cache) {
-            if (minLayerCount == -1 && maxLayerCount == -1 || cache == null) return predicate.test(worldState, patternState);
+            if (minLayerCount == -1 && maxLayerCount == -1 || cache == null)
+                return predicate.test(worldState, patternState);
 
             boolean base = predicate.test(worldState, patternState);
             int count = cache.getInt(this);
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 62e363bc75c..50dc264e1d9 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -400,7 +400,8 @@ public MultiblockShapeInfo getDefaultShape() {
                             // if the current predicate is used, move until the next free one
                             int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
                             if (newIndex >= predicate.common.size())
-                                GTLog.logger.warn("Failed to generate default structure pattern.", new IllegalStateException());
+                                GTLog.logger.warn("Failed to generate default structure pattern.",
+                                        new IllegalStateException());
                             next = predicate.common.get(newIndex);
                             globalCount = globalCache.getInt(next);
                             layerCount = layerCache.getInt(next);
@@ -427,7 +428,8 @@ public MultiblockShapeInfo getDefaultShape() {
             }
         }
 
-        return new MultiblockShapeInfo(pattern.stream().map(a -> new PatternAisle(1, a)).toArray(PatternAisle[]::new), candidates, directions);
+        return new MultiblockShapeInfo(pattern.stream().map(a -> new PatternAisle(1, a)).toArray(PatternAisle[]::new),
+                candidates, directions);
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index e83c60f13dd..9e9f1b3b31b 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -69,7 +69,8 @@ default void clearCache() {
     }
 
     /**
-     * Gets the cache, do not modify. Note that the cache stores everything in the AABB of the substructure, except for any() TraceabilityPredicates.
+     * Gets the cache, do not modify. Note that the cache stores everything in the AABB of the substructure, except for
+     * any() TraceabilityPredicates.
      * 
      * @return The cache for rapid pattern checking.
      */
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index c1b88853021..1f7b0f1639b 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -6,10 +6,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.util.GTUtility;
 
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-
-import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
-
 import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.item.Item;
@@ -24,9 +20,9 @@
 import net.minecraft.util.text.TextFormatting;
 import net.minecraft.world.World;
 
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.Collections;
 import java.util.List;
 
 public class MultiblockBuilderBehavior implements IItemBehaviour {
@@ -47,7 +43,7 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
             if (!multiblock.isStructureFormed("MAIN")) {
-                 multiblock.getBuildableShapes(new Object2IntOpenHashMap<>(), false);
+                multiblock.getBuildableShapes(new Object2IntOpenHashMap<>(), false);
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index 36752340e37..1723d967579 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -188,8 +188,10 @@ protected IBlockPattern createStructurePattern() {
 
     @NotNull
     private TraceabilityPredicate logPredicate() {
-        return new TraceabilityPredicate((worldState, patternState) -> worldState.getBlockState().getBlock().isWood(worldState.getWorld(),
-                worldState.getPos()) || worldState.getBlockState().equals(MetaBlocks.BRITTLE_CHARCOAL.getDefaultState()));
+        return new TraceabilityPredicate(
+                (worldState, patternState) -> worldState.getBlockState().getBlock().isWood(worldState.getWorld(),
+                        worldState.getPos()) ||
+                        worldState.getBlockState().equals(MetaBlocks.BRITTLE_CHARCOAL.getDefaultState()));
     }
 
     private void setActive(boolean active) {
@@ -249,7 +251,8 @@ protected void updateFacingMap() {
     }
 
     @Override
-    public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing, CuboidRayTraceResult hitResult) {
+    public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing,
+                                      CuboidRayTraceResult hitResult) {
         // todo add general direction lang(applies for cleanroom as well)
         if (!playerIn.isSneaking()) {
             if (getWorld().isRemote) return true;
@@ -357,11 +360,11 @@ public void receiveInitialSyncData(PacketBuffer buf) {
     @Override
     public void receiveCustomData(int dataId, PacketBuffer buf) {
         super.receiveCustomData(dataId, buf);
-        if (dataId == GregtechDataCodes.UPDATE_STRUCTURE_SIZE) {
-        } else if (dataId == GregtechDataCodes.WORKABLE_ACTIVE) {
-            this.isActive = buf.readBoolean();
-            scheduleRenderUpdate();
-        }
+        if (dataId == GregtechDataCodes.UPDATE_STRUCTURE_SIZE) {} else
+            if (dataId == GregtechDataCodes.WORKABLE_ACTIVE) {
+                this.isActive = buf.readBoolean();
+                scheduleRenderUpdate();
+            }
     }
 
     /**

From 68426476cbdc344c10cb5bea3ad968988725496f Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 18 Aug 2024 12:57:06 -0700
Subject: [PATCH 34/64] predicate tooltips and candidates

---
 .../multiblock/MultiblockControllerBase.java  |  1 +
 .../api/pattern/MultiblockShapeInfo.java      | 23 ++++++--
 .../api/pattern/TraceabilityPredicate.java    |  4 ++
 .../java/gregtech/api/util/BlockInfo.java     |  6 --
 .../MultiblockInfoRecipeWrapper.java          | 57 +++++++------------
 5 files changed, 44 insertions(+), 47 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 7cdee98a8b0..19fbc69cf15 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -154,6 +154,7 @@ private void validateStructurePatterns() {
             }
         }
 
+        // todo remove this
         if (!failures.isEmpty()) {
             throw new IllegalStateException("Structure patterns " + Arrays.toString(failures.toArray()) +
                     " needs some legacy updating");
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index 3a987e81487..6af1cccbae9 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -2,6 +2,7 @@
 
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
+import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.pattern.PatternAisle;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.RelativeDirection;
@@ -15,7 +16,6 @@
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Supplier;
@@ -39,8 +39,14 @@ public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symb
      * NORTH).
      * Unlike BlockPattern, the first char in the first string in the first aisle always starts at the origin, instead
      * of being relative to the controller.
+     * The passed in map is populated.
+     *
+     * @return A block in the pattern that has the same class as the argument class.
      */
-    public Map getMap() {
+    // this is currently here so that multiblocks can have other multiblocks in their structure without messing
+    // everything up
+    public MultiblockControllerBase getMap(Class controllerClass,
+                                           Map map) {
         // seems like MultiblockInfoRecipeWrapper wants the controller to be facing south
         EnumFacing absoluteAisle = directions[0].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
         EnumFacing absoluteString = directions[1].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
@@ -51,7 +57,8 @@ public Map getMap() {
         int charCount = aisles[0].getCharCount();
 
         GreggyBlockPos pos = new GreggyBlockPos();
-        Map map = new HashMap<>();
+
+        MultiblockControllerBase controller = null;
 
         for (int aisleI = 0; aisleI < aisleCount; aisleI++) {
             for (int stringI = 0; stringI < stringCount; stringI++) {
@@ -64,6 +71,11 @@ public Map getMap() {
                         mteHolder.setMetaTileEntity(holder.getMetaTileEntity());
                         mteHolder.getMetaTileEntity().onPlacement();
                         mteHolder.getMetaTileEntity().setFrontFacing(holder.getMetaTileEntity().getFrontFacing());
+
+                        if (mteHolder.getMetaTileEntity().getClass() == controllerClass) {
+                            controller = (MultiblockControllerBase) mteHolder.getMetaTileEntity();
+                        }
+
                         map.put(pos.immutable(),
                                 new BlockInfo(mteHolder.getMetaTileEntity().getBlock().getDefaultState(), mteHolder));
                     } else {
@@ -73,7 +85,8 @@ public Map getMap() {
             }
         }
 
-        if (true) return map;
+        // todo figure out how to fix the below code without returning here
+        if (true) return controller;
 
         // scuffed but tries to make hatches face out the structure
         for (Map.Entry entry : map.entrySet()) {
@@ -94,7 +107,7 @@ public Map getMap() {
             }
         }
 
-        return map;
+        return controller;
     }
 
     public static Builder builder(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index 6c59fbaf2ea..c1eaae76b42 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -288,6 +288,10 @@ public List getToolTips(TraceabilityPredicate predicates) {
             return result;
         }
 
+        public boolean testRaw(BlockWorldState worldState, PatternState patternState) {
+            return predicate.test(worldState, patternState);
+        }
+
         public boolean testLimited(BlockWorldState worldState, PatternState patternState,
                                    Object2IntMap globalCache,
                                    Object2IntMap layerCache) {
diff --git a/src/main/java/gregtech/api/util/BlockInfo.java b/src/main/java/gregtech/api/util/BlockInfo.java
index 75282aaa5d9..725c10f0cd6 100644
--- a/src/main/java/gregtech/api/util/BlockInfo.java
+++ b/src/main/java/gregtech/api/util/BlockInfo.java
@@ -23,7 +23,6 @@ public class BlockInfo {
     private final IBlockState blockState;
     private final TileEntity tileEntity;
     private final TraceabilityPredicate predicate;
-    private boolean manualFacing;
 
     public BlockInfo(Block block) {
         this(block.getDefaultState());
@@ -47,11 +46,6 @@ public BlockInfo(IBlockState blockState, TileEntity tileEntity, TraceabilityPred
                 "Cannot create block info with tile entity for block not having it");
     }
 
-    public BlockInfo manualFacing() {
-        this.manualFacing = true;
-        return this;
-    }
-
     public IBlockState getBlockState() {
         return blockState;
     }
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 790c3e44e8d..a37d452188b 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -4,8 +4,10 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.pattern.pattern.PatternState;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTUtility;
 import gregtech.api.util.GregFakePlayer;
@@ -350,27 +352,18 @@ public void drawInfo(@NotNull Minecraft minecraft, int recipeWidth, int recipeHe
             TraceabilityPredicate predicates = patterns[currentRendererPage].predicateMap
                     .get(rayTraceResult.getBlockPos());
             if (predicates != null) {
-                //
-                // StructureInfo info = new StructureInfo(new PatternMatchContext(), null);
-                // BlockWorldState worldState = new BlockWorldState(info);
-                //
-                // worldState.setWorld(renderer.world);
-                // worldState.setPos(rayTraceResult.getBlockPos());
-                //
-                // for (TraceabilityPredicate.SimplePredicate common : predicates.common) {
-                // if (common.test(worldState, info)) {
-                // predicateTips = common.getToolTips(predicates);
-                // break;
-                // }
-                // }
-                // if (predicateTips == null) {
-                // for (TraceabilityPredicate.SimplePredicate limit : predicates.limited) {
-                // if (limit.test(worldState, info)) {
-                // predicateTips = limit.getToolTips(predicates);
-                // break;
-                // }
-                // }
-                // }
+                BlockWorldState worldState = new BlockWorldState();
+                PatternState patternState = new PatternState();
+
+                worldState.setWorld(renderer.world);
+                worldState.setPos(rayTraceResult.getBlockPos());
+
+                for (TraceabilityPredicate.SimplePredicate common : predicates.common) {
+                    if (common.testRaw(worldState, patternState)) {
+                        predicateTips = common.getToolTips(predicates);
+                        break;
+                    }
+                }
             }
             if (!itemStack.isEmpty()) {
                 tooltipBlockStack = itemStack;
@@ -563,19 +556,15 @@ private static Collection gatherStructureBlocks(World world, @NotNull
         return partsMap.values();
     }
 
-    @SuppressWarnings("NewExpressionSideOnly")
     @NotNull
     private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @NotNull Set parts) {
-        Map blockMap = shapeInfo.getMap();
+        Map blockMap = new HashMap<>();
+        MultiblockControllerBase controller = shapeInfo.getMap(this.controller.getClass(), blockMap);
 
         TrackedDummyWorld world = new TrackedDummyWorld();
         ImmediateWorldSceneRenderer worldSceneRenderer = new ImmediateWorldSceneRenderer(world);
         worldSceneRenderer.setClearColor(ConfigHolder.client.multiblockPreviewColor);
-        try {
-            world.addBlocks(blockMap);
-        } catch (Exception e) {
-            throw e;
-        }
+        world.addBlocks(blockMap);
 
         Vector3f size = world.getSize();
         Vector3f minPos = world.getMinPos();
@@ -599,14 +588,10 @@ private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @Not
                 pos -> worldSceneRenderer.renderedBlocksMap.keySet().stream().anyMatch(c -> c.contains(pos)));
 
         Map predicateMap = new HashMap<>();
-        if (false) {
-            // if (controllerBase.structurePattern == null) {
-            // controllerBase.reinitializeStructurePattern();
-            // }
-            // if (controllerBase.structurePattern != null) {
-            // controllerBase.structurePattern.cache.forEach((pos, blockInfo) -> predicateMap
-            // .put(BlockPos.fromLong(pos), (TraceabilityPredicate) blockInfo.getInfo()));
-            // }
+
+        if (controller.isStructureFormed("MAIN")) {
+            controller.getSubstructure("MAIN").getCache().forEach((pos, blockInfo) -> predicateMap
+                    .put(BlockPos.fromLong(pos), blockInfo.getPredicate()));
         }
 
         List sortedParts = gatherStructureBlocks(worldSceneRenderer.world, blockMap, parts).stream()

From 0ba18a37650a2b538786ce9a7ddd98c4806e6f64 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 18 Aug 2024 15:04:16 -0700
Subject: [PATCH 35/64] in world preview part 1

---
 .../api/pattern/MultiblockShapeInfo.java      | 37 +++++++--
 .../handler/MultiblockPreviewRenderer.java    | 83 +++++--------------
 .../MultiblockInfoRecipeWrapper.java          |  4 +-
 3 files changed, 50 insertions(+), 74 deletions(-)

diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index 6af1cccbae9..cc7fc29304f 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -41,16 +41,16 @@ public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symb
      * of being relative to the controller.
      * The passed in map is populated.
      *
-     * @return A block in the pattern that has the same class as the argument class.
+     * @return A pos that can be looked up in the given map to find the controller that has the same class as the argument.
      */
     // this is currently here so that multiblocks can have other multiblocks in their structure without messing
     // everything up
-    public MultiblockControllerBase getMap(Class controllerClass,
+    public BlockPos getMap(MultiblockControllerBase src, BlockPos start, EnumFacing frontFacing, EnumFacing upFacing,
                                            Map map) {
         // seems like MultiblockInfoRecipeWrapper wants the controller to be facing south
-        EnumFacing absoluteAisle = directions[0].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
-        EnumFacing absoluteString = directions[1].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
-        EnumFacing absoluteChar = directions[2].getRelativeFacing(EnumFacing.SOUTH, EnumFacing.NORTH, false);
+        EnumFacing absoluteAisle = directions[0].getRelativeFacing(frontFacing, upFacing, false);
+        EnumFacing absoluteString = directions[1].getRelativeFacing(frontFacing, upFacing, false);
+        EnumFacing absoluteChar = directions[2].getRelativeFacing(frontFacing, upFacing, false);
 
         int aisleCount = aisles.length;
         int stringCount = aisles[0].getStringCount();
@@ -58,13 +58,13 @@ public MultiblockControllerBase getMap(Class
 
         GreggyBlockPos pos = new GreggyBlockPos();
 
-        MultiblockControllerBase controller = null;
+        BlockPos controller = null;
 
         for (int aisleI = 0; aisleI < aisleCount; aisleI++) {
             for (int stringI = 0; stringI < stringCount; stringI++) {
                 for (int charI = 0; charI < charCount; charI++) {
                     char c = aisles[aisleI].charAt(stringI, charI);
-                    pos.zero().offset(absoluteAisle, aisleI).offset(absoluteString, stringI).offset(absoluteChar,
+                    pos.from(start).offset(absoluteAisle, aisleI).offset(absoluteString, stringI).offset(absoluteChar,
                             charI);
                     if (symbols.get(c).getTileEntity() instanceof MetaTileEntityHolder holder) {
                         MetaTileEntityHolder mteHolder = new MetaTileEntityHolder();
@@ -72,8 +72,8 @@ public MultiblockControllerBase getMap(Class
                         mteHolder.getMetaTileEntity().onPlacement();
                         mteHolder.getMetaTileEntity().setFrontFacing(holder.getMetaTileEntity().getFrontFacing());
 
-                        if (mteHolder.getMetaTileEntity().getClass() == controllerClass) {
-                            controller = (MultiblockControllerBase) mteHolder.getMetaTileEntity();
+                        if (mteHolder.getMetaTileEntity().getClass() == src.getClass()) {
+                            controller = pos.immutable();
                         }
 
                         map.put(pos.immutable(),
@@ -110,6 +110,25 @@ public MultiblockControllerBase getMap(Class
         return controller;
     }
 
+    public int getUpCount(EnumFacing frontFacing, EnumFacing upFacing) {
+
+        int index = 0;
+        for (int i = 0; i < 3; i++) {
+            EnumFacing facing = directions[i].getRelativeFacing(frontFacing, upFacing, false);
+            if (facing == EnumFacing.UP || facing == EnumFacing.DOWN) {
+                index = i;
+                break;
+            }
+        }
+
+        return switch (index) {
+            case 0 -> aisles.length;
+            case 1 -> aisles[0].getStringCount();
+            case 2 -> aisles[0].getCharCount();
+            default -> throw new IllegalStateException();
+        };
+    }
+
     public static Builder builder(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
         return new Builder(aisleDir, stringDir, charDir);
     }
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index e64d7ec31f6..56f1949d265 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -1,8 +1,10 @@
 package gregtech.client.renderer.handler;
 
 import gregtech.api.metatileentity.MetaTileEntity;
+import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.util.BlockInfo;
 import gregtech.client.utils.TrackedDummyWorld;
@@ -81,7 +83,6 @@ public static void renderMultiBlockPreview(MultiblockControllerBase controller,
             layer++;
         }
         resetMultiblockRender();
-        mbpPos = controller.getPos();
         mbpEndTime = System.currentTimeMillis() + durTimeMillis;
         opList = GLAllocation.generateDisplayLists(1); // allocate op list
         GlStateManager.glNewList(opList, GL11.GL_COMPILE);
@@ -101,87 +102,40 @@ public static void resetMultiblockRender() {
 
     public static void renderControllerInList(MultiblockControllerBase controllerBase, MultiblockShapeInfo shapeInfo,
                                               int layer) {
-        BlockPos mbpPos = controllerBase.getPos();
-        EnumFacing frontFacing, previewFacing;
-        previewFacing = controllerBase.getFrontFacing();
-        BlockPos controllerPos = BlockPos.ORIGIN;
-        MultiblockControllerBase mte = null;
-        // todo fix
-        BlockInfo[][][] blocks = null;
-        // yay unreachable statements
-        if (true) return;
         Map blockMap = new HashMap<>();
-        int maxY = 0;
-        for (int x = 0; x < blocks.length; x++) {
-            BlockInfo[][] aisle = blocks[x];
-            maxY = Math.max(maxY, aisle.length);
-            for (int y = 0; y < aisle.length; y++) {
-                BlockInfo[] column = aisle[y];
-                for (int z = 0; z < column.length; z++) {
-                    blockMap.put(new BlockPos(x, y, z), column[z]);
-                    MetaTileEntity metaTE = column[z].getTileEntity() instanceof IGregTechTileEntity ?
-                            ((IGregTechTileEntity) column[z].getTileEntity()).getMetaTileEntity() : null;
-                    if (metaTE instanceof MultiblockControllerBase &&
-                            metaTE.metaTileEntityId.equals(controllerBase.metaTileEntityId)) {
-                        controllerPos = new BlockPos(x, y, z);
-                        previewFacing = metaTE.getFrontFacing();
-                        mte = (MultiblockControllerBase) metaTE;
-                        break;
-                    }
-                }
-            }
-        }
-        TrackedDummyWorld world = new TrackedDummyWorld();
-        world.addBlocks(blockMap);
-        int finalMaxY = layer % (maxY + 1);
-        world.setRenderFilter(pos -> pos.getY() + 1 == finalMaxY || finalMaxY == 0);
+        BlockPos controllerPos = shapeInfo.getMap(controllerBase, BlockPos.ORIGIN, controllerBase.getFrontFacing(), controllerBase.getUpwardsFacing(), blockMap);
+        MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap.get(controllerPos).getTileEntity()).getMetaTileEntity();
 
         EnumFacing facing = controllerBase.getFrontFacing();
         EnumFacing upwardsFacing = controllerBase.getUpwardsFacing();
 
-        frontFacing = facing.getYOffset() == 0 ? facing :
-                facing.getYOffset() < 0 ? upwardsFacing : upwardsFacing.getOpposite();
-        Rotation rotatePreviewBy = Rotation
-                .values()[(4 + frontFacing.getHorizontalIndex() - previewFacing.getHorizontalIndex()) % 4];
+        TrackedDummyWorld world = new TrackedDummyWorld();
+        world.addBlocks(blockMap);
+        int finalMaxY = layer % (shapeInfo.getUpCount(facing, upwardsFacing) + 1);
+        world.setRenderFilter(pos -> pos.getY() + 1 == finalMaxY || finalMaxY == 0);
 
         Minecraft mc = Minecraft.getMinecraft();
         BlockRendererDispatcher brd = mc.getBlockRendererDispatcher();
         Tessellator tes = Tessellator.getInstance();
         BufferBuilder buff = tes.getBuffer();
-        GlStateManager.pushMatrix();
-        GlStateManager.translate(mbpPos.getX(), mbpPos.getY(), mbpPos.getZ());
-        GlStateManager.translate(0.5, 0, 0.5);
-        GlStateManager.rotate(rotatePreviewBy.ordinal() * 90, 0, -1, 0);
-        GlStateManager.translate(-0.5, 0, -0.5);
-
-        if (facing == EnumFacing.UP) {
-            GlStateManager.translate(0.5, 0.5, 0.5);
-            GlStateManager.rotate(90, -previewFacing.getZOffset(), 0, previewFacing.getXOffset());
-            GlStateManager.translate(-0.5, -0.5, -0.5);
-        } else if (facing == EnumFacing.DOWN) {
-            GlStateManager.translate(0.5, 0.5, 0.5);
-            GlStateManager.rotate(90, previewFacing.getZOffset(), 0, -previewFacing.getXOffset());
-            GlStateManager.translate(-0.5, -0.5, -0.5);
-        } else {
-            int degree = 90 * (upwardsFacing == EnumFacing.EAST ? -1 :
-                    upwardsFacing == EnumFacing.SOUTH ? 2 : upwardsFacing == EnumFacing.WEST ? 1 : 0);
-            GlStateManager.translate(0.5, 0.5, 0.5);
-            GlStateManager.rotate(degree, previewFacing.getXOffset(), 0, previewFacing.getZOffset());
-            GlStateManager.translate(-0.5, -0.5, -0.5);
-        }
 
-        if (mte != null) {
-            mte.checkStructurePattern();
+        // don't check structure when only a layer is displayed to prevent controller from unforming
+        if (controller != null && finalMaxY == 0) {
+            controller.checkStructurePattern();
         }
 
         BlockRenderLayer oldLayer = MinecraftForgeClient.getRenderLayer();
-
         TargetBlockAccess targetBA = new TargetBlockAccess(world, BlockPos.ORIGIN);
+        GreggyBlockPos greg = new GreggyBlockPos();
+        GreggyBlockPos offset = new GreggyBlockPos(controllerPos);
+        GreggyBlockPos temp = new GreggyBlockPos();
+
         for (BlockPos pos : blockMap.keySet()) {
             targetBA.setPos(pos);
+            greg.from(controllerBase.getPos()).add(temp.from(pos)).subtract(offset);
+
             GlStateManager.pushMatrix();
-            BlockPos.MutableBlockPos tPos = new BlockPos.MutableBlockPos(pos.subtract(controllerPos));
-            GlStateManager.translate(tPos.getX(), tPos.getY(), tPos.getZ());
+            GlStateManager.translate(greg.x(), greg.y(), greg.z());
             GlStateManager.translate(0.125, 0.125, 0.125);
             GlStateManager.scale(0.75, 0.75, 0.75);
 
@@ -201,6 +155,7 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas
         GlStateManager.popMatrix();
     }
 
+    // todo maybe remove??? who knows what this does but it looks like nothing useful
     @SideOnly(Side.CLIENT)
     private static class TargetBlockAccess implements IBlockAccess {
 
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index a37d452188b..363db3a51cb 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -2,6 +2,7 @@
 
 import gregtech.api.gui.GuiTextures;
 import gregtech.api.metatileentity.MetaTileEntity;
+import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.BlockWorldState;
@@ -559,7 +560,8 @@ private static Collection gatherStructureBlocks(World world, @NotNull
     @NotNull
     private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @NotNull Set parts) {
         Map blockMap = new HashMap<>();
-        MultiblockControllerBase controller = shapeInfo.getMap(this.controller.getClass(), blockMap);
+        BlockPos controllerPos = shapeInfo.getMap(this.controller, new BlockPos(0, 128, 0), EnumFacing.SOUTH, EnumFacing.NORTH, blockMap);
+        MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap.get(controllerPos).getTileEntity()).getMetaTileEntity();
 
         TrackedDummyWorld world = new TrackedDummyWorld();
         ImmediateWorldSceneRenderer worldSceneRenderer = new ImmediateWorldSceneRenderer(world);

From b8ce93f4d9857516ae0111783f6b85d1e751b8c3 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 18 Aug 2024 16:47:08 -0700
Subject: [PATCH 36/64] part 2 ft. extended rotation

---
 .../api/pattern/MultiblockShapeInfo.java      | 44 +++++++++++++++++--
 .../handler/MultiblockPreviewRenderer.java    |  8 +---
 .../MultiblockInfoRecipeWrapper.java          |  1 +
 3 files changed, 44 insertions(+), 9 deletions(-)

diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index cc7fc29304f..b2c574b5e7b 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -16,16 +16,42 @@
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Supplier;
 
 public class MultiblockShapeInfo {
 
+    /**
+     * Default front facing for jei preview(and what patterns are most likely made for).
+     */
+    public static final EnumFacing DEFAULT_FRONT = EnumFacing.SOUTH;
+
+    /**
+     * Default up facing for jei preview(and what patterns are most likely made for).
+     */
+    public static final EnumFacing DEFAULT_UP = EnumFacing.NORTH;
+
+    /**
+     * Unmodifiable reverse map from facing to relative direction, using DEFAULT_FRONT and DEFAULT_UP.
+     * For example, EnumFacing.NORTH -> RelativeDirection.BACK, since with the defaults the relative back of the controller is north.
+     */
+    public static final Map FACING_MAP;
     protected final PatternAisle[] aisles;
     protected final Char2ObjectMap symbols;
     protected final RelativeDirection[] directions;
 
+    static {
+        EnumMap facingMap = new EnumMap<>(EnumFacing.class);
+        for (RelativeDirection dir : RelativeDirection.VALUES) {
+            facingMap.put(dir.getRelativeFacing(DEFAULT_FRONT, DEFAULT_UP, false), dir);
+        }
+
+        FACING_MAP = Collections.unmodifiableMap(facingMap);
+    }
+
     public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symbols,
                                RelativeDirection[] directions) {
         this.aisles = aisles;
@@ -47,6 +73,7 @@ public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symb
     // everything up
     public BlockPos getMap(MultiblockControllerBase src, BlockPos start, EnumFacing frontFacing, EnumFacing upFacing,
                                            Map map) {
+        // todo update cleanroom and charcoal pile igniter with enummap instead of hashmap
         // seems like MultiblockInfoRecipeWrapper wants the controller to be facing south
         EnumFacing absoluteAisle = directions[0].getRelativeFacing(frontFacing, upFacing, false);
         EnumFacing absoluteString = directions[1].getRelativeFacing(frontFacing, upFacing, false);
@@ -70,10 +97,21 @@ public BlockPos getMap(MultiblockControllerBase src, BlockPos start, EnumFacing
                         MetaTileEntityHolder mteHolder = new MetaTileEntityHolder();
                         mteHolder.setMetaTileEntity(holder.getMetaTileEntity());
                         mteHolder.getMetaTileEntity().onPlacement();
-                        mteHolder.getMetaTileEntity().setFrontFacing(holder.getMetaTileEntity().getFrontFacing());
 
-                        if (mteHolder.getMetaTileEntity().getClass() == src.getClass()) {
-                            controller = pos.immutable();
+                        // get the relative direction from the part facing, then use that to get the real enum facing
+                        EnumFacing newFacing = FACING_MAP.get(holder.getMetaTileEntity().getFrontFacing()).getRelativeFacing(frontFacing, upFacing, false);
+                        mteHolder.getMetaTileEntity().setFrontFacing(newFacing);
+
+
+                        if (mteHolder.getMetaTileEntity() instanceof MultiblockControllerBase base) {
+                            // there is no way to determine upwards facing with only a front facing
+                            // so if you want to have a multiblock with an upward facings that isn't UP
+                            // use the (IBlockState, TileEntity) ctor for BlockInfo and set upwardsFacing there
+                            // currently this just sets the controller's upwards facing
+                            if (base.getClass() == src.getClass()) {
+                                controller = pos.immutable();
+                                base.setUpwardsFacing(upFacing);
+                            }
                         }
 
                         map.put(pos.immutable(),
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index 56f1949d265..4a2e363207d 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -83,6 +83,7 @@ public static void renderMultiBlockPreview(MultiblockControllerBase controller,
             layer++;
         }
         resetMultiblockRender();
+        mbpPos = controller.getPos();
         mbpEndTime = System.currentTimeMillis() + durTimeMillis;
         opList = GLAllocation.generateDisplayLists(1); // allocate op list
         GlStateManager.glNewList(opList, GL11.GL_COMPILE);
@@ -119,10 +120,7 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas
         Tessellator tes = Tessellator.getInstance();
         BufferBuilder buff = tes.getBuffer();
 
-        // don't check structure when only a layer is displayed to prevent controller from unforming
-        if (controller != null && finalMaxY == 0) {
-            controller.checkStructurePattern();
-        }
+        if (controller != null) controller.checkStructurePattern();
 
         BlockRenderLayer oldLayer = MinecraftForgeClient.getRenderLayer();
         TargetBlockAccess targetBA = new TargetBlockAccess(world, BlockPos.ORIGIN);
@@ -151,8 +149,6 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas
             GlStateManager.popMatrix();
         }
         ForgeHooksClient.setRenderLayer(oldLayer);
-
-        GlStateManager.popMatrix();
     }
 
     // todo maybe remove??? who knows what this does but it looks like nothing useful
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 363db3a51cb..63a6d8b242f 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -560,6 +560,7 @@ private static Collection gatherStructureBlocks(World world, @NotNull
     @NotNull
     private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @NotNull Set parts) {
         Map blockMap = new HashMap<>();
+        // absolutely dog way of doing this, just setting the center at 128 so that both patterns going down and up can work
         BlockPos controllerPos = shapeInfo.getMap(this.controller, new BlockPos(0, 128, 0), EnumFacing.SOUTH, EnumFacing.NORTH, blockMap);
         MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap.get(controllerPos).getTileEntity()).getMetaTileEntity();
 

From d0abbd8060911a8744cb521a7ad008f60d0c7c9a Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 18 Aug 2024 17:01:28 -0700
Subject: [PATCH 37/64] cleanup

---
 .../multiblock/MultiblockControllerBase.java             | 4 +---
 .../items/behaviors/MultiblockBuilderBehavior.java       | 2 +-
 .../centralmonitor/MetaTileEntityCentralMonitor.java     | 9 +--------
 .../jei/multiblock/MultiblockInfoRecipeWrapper.java      | 2 +-
 4 files changed, 4 insertions(+), 13 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 19fbc69cf15..fab24355975 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -771,12 +771,10 @@ public List getMatchingShapes() {
      *
      * @param keyMap  A map for autobuild, or null if it is an in world or jei preview. Note that for in world and jei
      *                previews you can return a singleton list(only the first element will be used anyway).
-     * @param hatches This is whether you should put hatches, JEI previews need hatches, but autobuild and in world
-     *                previews shouldn't(unless the hatch is necessary and only has one valid spot, such as EBF muffler)
      */
     // todo add use for the keyMap with the multiblock builder
     // todo maybe add name arg for building substructures
-    public List getBuildableShapes(@Nullable Object2IntMap keyMap, boolean hatches) {
+    public List getBuildableShapes(@Nullable Object2IntMap keyMap) {
         List infos = getMatchingShapes();
 
         // if there is no overriden getMatchingShapes() just return the default one
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index 1f7b0f1639b..55c7e336588 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -43,7 +43,7 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
             if (!multiblock.isStructureFormed("MAIN")) {
-                multiblock.getBuildableShapes(new Object2IntOpenHashMap<>(), false);
+                multiblock.getBuildableShapes(new Object2IntOpenHashMap<>());
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index 944269d80d1..d52c7929a8f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -431,14 +431,7 @@ protected void formStructure(String name) {
         netCovers = new HashSet<>();
         remoteCovers = new HashSet<>();
         inputEnergy = new EnergyContainerList(this.getAbilities(MultiblockAbility.INPUT_ENERGY));
-        width = 0;
-        checkCovers();
-        for (IMultiblockPart part : this.getMultiblockParts()) {
-            if (part instanceof MetaTileEntityMonitorScreen) {
-                width++;
-            }
-        }
-        width = width / height;
+        width = ((BlockPattern) getSubstructure("MAIN")).getRepetitionCount(1);
         screens = new MetaTileEntityMonitorScreen[width][height];
         for (IMultiblockPart part : this.getMultiblockParts()) {
             if (part instanceof MetaTileEntityMonitorScreen screen) {
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 63a6d8b242f..40c284a9959 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -126,7 +126,7 @@ public MBPattern(final WorldSceneRenderer sceneRenderer, final List p
     public MultiblockInfoRecipeWrapper(@NotNull MultiblockControllerBase controller) {
         this.controller = controller;
         Set drops = new ObjectOpenCustomHashSet<>(ItemStackHashStrategy.comparingAllButCount());
-        this.patterns = controller.getBuildableShapes(null, false).stream()
+        this.patterns = controller.getBuildableShapes(null).stream()
                 .map(it -> initializePattern(it, drops))
                 .toArray(MBPattern[]::new);
         allItemStackInputs.addAll(drops);

From 33f2c96dc40d2546a9f000a9ab472b4efad6cfb7 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Mon, 19 Aug 2024 21:24:01 -0700
Subject: [PATCH 38/64] relative direction refactor(central monitor doesnt work
 again, as expected)

---
 .../multiblock/MultiblockControllerBase.java  |  48 ++++--
 .../api/pattern/MultiblockShapeInfo.java      |   2 +-
 .../java/gregtech/api/util/GTUtility.java     |  14 ++
 .../gregtech/api/util/RelativeDirection.java  | 146 ++++--------------
 .../MultiblockInfoRecipeWrapper.java          |   2 +-
 5 files changed, 75 insertions(+), 137 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index fab24355975..4262a39734c 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -1,5 +1,6 @@
 package gregtech.api.metatileentity.multiblock;
 
+import gregtech.api.GTValues;
 import gregtech.api.GregTechAPI;
 import gregtech.api.block.VariantActiveBlock;
 import gregtech.api.capability.GregtechCapabilities;
@@ -94,7 +95,7 @@ public abstract class MultiblockControllerBase extends MetaTileEntity implements
     // prioritize the manually specified sorter first, defaulting to the hashcode for tiebreakers
     private final NavigableSet multiblockParts = new TreeSet<>(partComparator);
 
-    protected EnumFacing upwardsFacing = EnumFacing.NORTH;
+    protected EnumFacing upwardsFacing = EnumFacing.UP;
     protected final Object2ObjectMap structures = new Object2ObjectOpenHashMap<>();
 
     public MultiblockControllerBase(ResourceLocation metaTileEntityId) {
@@ -167,7 +168,7 @@ public EnumFacing getUpwardsFacing() {
 
     public void setUpwardsFacing(EnumFacing upwardsFacing) {
         if (!allowsExtendedFacing()) return;
-        if (upwardsFacing == null || upwardsFacing == EnumFacing.UP || upwardsFacing == EnumFacing.DOWN) {
+        if (upwardsFacing == null || upwardsFacing.getAxis() == frontFacing.getAxis()) {
             GTLog.logger.error("Tried to set upwards facing to invalid facing {}! Skipping", upwardsFacing);
             return;
         }
@@ -351,17 +352,22 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
             baseTexture.render(renderState, translation, pipeline);
         }
 
+        // todo fix
         if (allowsExtendedFacing()) {
-            double degree = Math.PI / 2 * (upwardsFacing == EnumFacing.EAST ? -1 :
-                    upwardsFacing == EnumFacing.SOUTH ? 2 : upwardsFacing == EnumFacing.WEST ? 1 : 0);
-            Rotation rotation = new Rotation(degree, frontFacing.getXOffset(), frontFacing.getYOffset(),
-                    frontFacing.getZOffset());
-            translation.translate(0.5, 0.5, 0.5);
-            if (frontFacing == EnumFacing.DOWN && upwardsFacing.getAxis() == EnumFacing.Axis.Z) {
-                translation.apply(new Rotation(Math.PI, 0, 1, 0));
+            double rad = 0;
+            if (frontFacing.getAxis() == EnumFacing.Axis.Y) {
+                rad = -Math.PI / 2 * (upwardsFacing.getHorizontalIndex() - 2);
+                if (frontFacing == EnumFacing.DOWN) rad += Math.PI;
+            } else {
+                EnumFacing rotated = EnumFacing.UP.rotateAround(frontFacing.getAxis());
+
+                if (upwardsFacing == EnumFacing.DOWN) rad = Math.PI;
+                else if (upwardsFacing == rotated) rad = Math.PI / 2;
+                else if (upwardsFacing == rotated.getOpposite()) rad = -Math.PI / 2;
             }
-            translation.apply(rotation);
-            translation.scale(1.0000f);
+
+            translation.translate(0.5, 0.5, 0.5);
+            translation.rotate(new Rotation(rad, frontFacing.getXOffset(), frontFacing.getYOffset(), frontFacing.getZOffset()));
             translation.translate(-0.5, -0.5, -0.5);
         }
     }
@@ -433,7 +439,7 @@ public void checkStructurePattern(String name) {
                         return true;
                     });
 
-                    // another bandaid fix, see below todo
+                    // another bandaid fix
                     addedParts.sort(partComparator);
 
                     for (IMultiblockAbilityPart part : addedParts) {
@@ -607,7 +613,18 @@ public NavigableSet getMultiblockParts() {
     public void readFromNBT(NBTTagCompound data) {
         super.readFromNBT(data);
         if (data.hasKey("UpwardsFacing")) {
-            this.upwardsFacing = EnumFacing.VALUES[data.getByte("UpwardsFacing")];
+             this.upwardsFacing = EnumFacing.VALUES[data.getByte("UpwardsFacing")];
+             // old up facing is absolute when front facing is up/down
+             if (frontFacing.getAxis() != EnumFacing.Axis.Y) {
+                 this.upwardsFacing = switch (upwardsFacing) {
+                     case NORTH -> EnumFacing.UP;
+                     case SOUTH -> EnumFacing.DOWN;
+                     case EAST -> frontFacing.rotateYCCW();
+                     default -> frontFacing.rotateY();
+                 };
+             }
+        } else if (data.hasKey("UpFacing")) {
+            this.upwardsFacing = EnumFacing.VALUES[data.getByte("UpFacing")];
         }
         this.reinitializeStructurePattern();
     }
@@ -615,7 +632,7 @@ public void readFromNBT(NBTTagCompound data) {
     @Override
     public NBTTagCompound writeToNBT(NBTTagCompound data) {
         super.writeToNBT(data);
-        data.setByte("UpwardsFacing", (byte) upwardsFacing.getIndex());
+        data.setByte("UpFacing", (byte) upwardsFacing.getIndex());
         return data;
     }
 
@@ -737,7 +754,8 @@ public boolean onWrenchClick(EntityPlayer playerIn, EnumHand hand, EnumFacing wr
                                  CuboidRayTraceResult hitResult) {
         if (wrenchSide == getFrontFacing() && allowsExtendedFacing()) {
             if (!getWorld().isRemote) {
-                setUpwardsFacing(playerIn.isSneaking() ? upwardsFacing.rotateYCCW() : upwardsFacing.rotateY());
+                EnumFacing.Axis axis = getFrontFacing().getAxis();
+                setUpwardsFacing(playerIn.isSneaking() ? upwardsFacing.rotateAround(axis).getOpposite() : upwardsFacing.rotateAround(axis));
             }
             return true;
         }
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index b2c574b5e7b..988bd50b729 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -32,7 +32,7 @@ public class MultiblockShapeInfo {
     /**
      * Default up facing for jei preview(and what patterns are most likely made for).
      */
-    public static final EnumFacing DEFAULT_UP = EnumFacing.NORTH;
+    public static final EnumFacing DEFAULT_UP = EnumFacing.UP;
 
     /**
      * Unmodifiable reverse map from facing to relative direction, using DEFAULT_FRONT and DEFAULT_UP.
diff --git a/src/main/java/gregtech/api/util/GTUtility.java b/src/main/java/gregtech/api/util/GTUtility.java
index 39b4a6a0d65..4749be1788d 100644
--- a/src/main/java/gregtech/api/util/GTUtility.java
+++ b/src/main/java/gregtech/api/util/GTUtility.java
@@ -911,6 +911,20 @@ public double getAsDouble() {
         return Pair.of(supplier1, supplier2);
     }
 
+    /**
+     * Gets the cross product of 2 facings. Null is returned if the result is a zero vector(facings are on the same axis).
+     */
+    @Nullable
+    public static EnumFacing cross(EnumFacing a, EnumFacing b) {
+        if (a.getAxis() == b.getAxis()) return null;
+
+        return EnumFacing.getFacingFromVector(
+                a.getYOffset() * b.getZOffset() - a.getZOffset() * b.getYOffset(),
+                a.getZOffset() * b.getXOffset() - a.getXOffset() * b.getZOffset(),
+                a.getXOffset() * b.getYOffset() - a.getYOffset() * b.getXOffset()
+        );
+    }
+
     /**
      * Safely cast a Long to an Int without overflow.
      *
diff --git a/src/main/java/gregtech/api/util/RelativeDirection.java b/src/main/java/gregtech/api/util/RelativeDirection.java
index 6ca739f6594..03cf87bad16 100644
--- a/src/main/java/gregtech/api/util/RelativeDirection.java
+++ b/src/main/java/gregtech/api/util/RelativeDirection.java
@@ -3,8 +3,8 @@
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.EnumFacing.Axis;
 import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.math.Vec3i;
 
+import java.util.function.BinaryOperator;
 import java.util.function.Function;
 
 /**
@@ -12,34 +12,22 @@
  */
 public enum RelativeDirection {
 
-    UP(f -> EnumFacing.UP),
-    DOWN(f -> EnumFacing.DOWN),
-    LEFT(EnumFacing::rotateYCCW),
-    RIGHT(EnumFacing::rotateY),
-    FRONT(Function.identity()),
-    BACK(EnumFacing::getOpposite);
+    UP((f, u) -> u),
+    DOWN((f, u) -> u.getOpposite()),
+    LEFT((f, u) -> GTUtility.cross(f, u).getOpposite()),
+    RIGHT(GTUtility::cross),
+    FRONT((f, u) -> f),
+    BACK((f, u) -> f.getOpposite());
 
-    final Function actualFacing;
+    final BinaryOperator facingFunction;
 
     /**
      * do not mutate this unless you want your house to explode
      */
     public static final RelativeDirection[] VALUES = values();
 
-    RelativeDirection(Function actualFacing) {
-        this.actualFacing = actualFacing;
-    }
-
-    public EnumFacing getActualFacing(EnumFacing facing) {
-        return actualFacing.apply(facing);
-    }
-
-    public EnumFacing apply(EnumFacing facing) {
-        return actualFacing.apply(facing);
-    }
-
-    public Vec3i applyVec3i(EnumFacing facing) {
-        return apply(facing).getDirectionVec();
+    RelativeDirection(BinaryOperator facingFunction) {
+        this.facingFunction = facingFunction;
     }
 
     /**
@@ -57,71 +45,15 @@ public int oppositeOrdinal() {
         return (ordinal() / 2) * 2 + (1 - ordinal() % 2);
     }
 
+    public EnumFacing getRelativeFacing(EnumFacing frontFacing, EnumFacing upFacing) {
+        if (frontFacing.getAxis() == upFacing.getAxis()) {
+            throw new IllegalArgumentException("Front facing and up facing must be on different axes!");
+        }
+        return facingFunction.apply(frontFacing, upFacing);
+    }
+
     public EnumFacing getRelativeFacing(EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped) {
-        EnumFacing.Axis frontAxis = frontFacing.getAxis();
-        return switch (this) {
-            case UP -> {
-                if (frontAxis == Axis.Y) {
-                    // same direction as upwards facing
-                    yield upwardsFacing;
-                } else {
-                    // transform the upwards facing into a real facing
-                    yield switch (upwardsFacing) {
-                        case NORTH -> EnumFacing.UP;
-                        case SOUTH -> EnumFacing.DOWN;
-                        case EAST -> frontFacing.rotateYCCW();
-                        default -> frontFacing.rotateY(); // WEST
-                    };
-                }
-            }
-            case DOWN -> {
-                if (frontAxis == Axis.Y) {
-                    // opposite direction as upwards facing
-                    yield upwardsFacing.getOpposite();
-                } else {
-                    // transform the upwards facing into a real facing
-                    yield switch (upwardsFacing) {
-                        case NORTH -> EnumFacing.DOWN;
-                        case SOUTH -> EnumFacing.UP;
-                        case EAST -> frontFacing.rotateY();
-                        default -> frontFacing.rotateYCCW(); // WEST
-                    };
-                }
-            }
-            case LEFT -> {
-                EnumFacing facing;
-                if (frontAxis == Axis.Y) {
-                    facing = upwardsFacing.rotateY();
-                } else {
-                    facing = switch (upwardsFacing) {
-                        case NORTH -> frontFacing.rotateYCCW();
-                        case SOUTH -> frontFacing.rotateY();
-                        case EAST -> EnumFacing.DOWN;
-                        default -> EnumFacing.UP; // WEST
-                    };
-                }
-                yield isFlipped ? facing.getOpposite() : facing;
-            }
-            case RIGHT -> {
-                EnumFacing facing;
-                if (frontAxis == Axis.Y) {
-                    facing = upwardsFacing.rotateYCCW();
-                } else {
-                    facing = switch (upwardsFacing) {
-                        case NORTH -> frontFacing.rotateY();
-                        case SOUTH -> frontFacing.rotateYCCW();
-                        case EAST -> EnumFacing.UP;
-                        default -> EnumFacing.DOWN; // WEST
-                    };
-                }
-                // invert if flipped
-                yield isFlipped ? facing.getOpposite() : facing;
-            }
-            // same direction as front facing, upwards facing doesn't matter
-            case FRONT -> frontFacing;
-            // opposite direction as front facing, upwards facing doesn't matter
-            case BACK -> frontFacing.getOpposite();
-        };
+        return (isFlipped && (this == LEFT || this == RIGHT)) ? getRelativeFacing(frontFacing, upwardsFacing).getOpposite() : getRelativeFacing(frontFacing, upwardsFacing);
     }
 
     public Function getSorter(EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped) {
@@ -146,48 +78,22 @@ public Function getSorter(EnumFacing frontFacing, EnumFacing
      */
     public static EnumFacing simulateAxisRotation(EnumFacing newFrontFacing, EnumFacing oldFrontFacing,
                                                   EnumFacing upwardsFacing) {
-        if (newFrontFacing == oldFrontFacing) return upwardsFacing;
+        // 180 degree flip
+        if (newFrontFacing.getAxis() == oldFrontFacing.getAxis()) return upwardsFacing;
 
-        EnumFacing.Axis newAxis = newFrontFacing.getAxis();
-        EnumFacing.Axis oldAxis = oldFrontFacing.getAxis();
+        // axis of rotation
+        Axis rotAxis = GTUtility.cross(newFrontFacing, oldFrontFacing).getAxis();
 
-        if (newAxis != Axis.Y && oldAxis != Axis.Y) {
-            // no change needed
-            return upwardsFacing;
-        } else if (newAxis == Axis.Y && oldAxis != Axis.Y) {
-            // going from horizontal to vertical axis
-            EnumFacing newUpwardsFacing = switch (upwardsFacing) {
-                case NORTH -> oldFrontFacing.getOpposite();
-                case SOUTH -> oldFrontFacing;
-                case EAST -> oldFrontFacing.rotateYCCW();
-                default -> oldFrontFacing.rotateY(); // WEST
-            };
-            return newFrontFacing == EnumFacing.DOWN && upwardsFacing.getAxis() == Axis.Z ?
-                    newUpwardsFacing.getOpposite() : newUpwardsFacing;
-        } else if (newAxis != Axis.Y) {
-            // going from vertical to horizontal axis
-            EnumFacing newUpwardsFacing;
-            if (upwardsFacing == newFrontFacing.getOpposite()) {
-                newUpwardsFacing = EnumFacing.NORTH;
-            } else if (upwardsFacing == newFrontFacing) {
-                newUpwardsFacing = EnumFacing.SOUTH;
-            } else if (upwardsFacing == newFrontFacing.rotateY()) {
-                newUpwardsFacing = EnumFacing.WEST;
-            } else { // rotateYCCW
-                newUpwardsFacing = EnumFacing.EAST;
-            }
-            return oldFrontFacing == EnumFacing.DOWN && newUpwardsFacing.getAxis() == Axis.Z ?
-                    newUpwardsFacing.getOpposite() : newUpwardsFacing;
-        } else {
-            // was on vertical axis and still is. Must have flipped from up to down or vice versa
-            return upwardsFacing.getOpposite();
-        }
+        if (rotAxis == upwardsFacing.getAxis()) return upwardsFacing;
+
+        return oldFrontFacing.getOpposite();
     }
 
     /**
      * Offset a BlockPos relatively in any direction by any amount. Pass negative values to offset down, right or
      * backwards.
      */
+    // todo rework/remove this also
     public static BlockPos offsetPos(BlockPos pos, EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped,
                                      int upOffset, int leftOffset, int forwardOffset) {
         if (upOffset == 0 && leftOffset == 0 && forwardOffset == 0) {
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 40c284a9959..58446b1cecf 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -561,7 +561,7 @@ private static Collection gatherStructureBlocks(World world, @NotNull
     private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @NotNull Set parts) {
         Map blockMap = new HashMap<>();
         // absolutely dog way of doing this, just setting the center at 128 so that both patterns going down and up can work
-        BlockPos controllerPos = shapeInfo.getMap(this.controller, new BlockPos(0, 128, 0), EnumFacing.SOUTH, EnumFacing.NORTH, blockMap);
+        BlockPos controllerPos = shapeInfo.getMap(this.controller, new BlockPos(0, 128, 0), EnumFacing.SOUTH, EnumFacing.UP, blockMap);
         MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap.get(controllerPos).getTileEntity()).getMetaTileEntity();
 
         TrackedDummyWorld world = new TrackedDummyWorld();

From 82d30ed4e7786df25262d2391555c83abdac23c7 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Tue, 20 Aug 2024 18:30:26 -0700
Subject: [PATCH 39/64] central monitor but it works(and half the things that
 didn't work before now works)

---
 .../multiblock/MultiblockControllerBase.java  | 16 ++--
 .../gregtech/api/pattern/GreggyBlockPos.java  | 12 +++
 .../api/pattern/MultiblockShapeInfo.java      | 13 ++-
 .../gregtech/api/util/RelativeDirection.java  | 12 ++-
 .../handler/MultiblockPreviewRenderer.java    |  4 +-
 .../MetaTileEntityCentralMonitor.java         | 21 ++---
 .../MetaTileEntityMonitorScreen.java          | 81 +++++--------------
 7 files changed, 66 insertions(+), 93 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 4262a39734c..cff2054d6aa 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -16,6 +16,7 @@
 import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.pattern.pattern.PatternState;
 import gregtech.api.pipenet.tile.IPipeTile;
+import gregtech.api.unification.OreDictUnifier;
 import gregtech.api.unification.material.Material;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTLog;
@@ -360,10 +361,11 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
                 if (frontFacing == EnumFacing.DOWN) rad += Math.PI;
             } else {
                 EnumFacing rotated = EnumFacing.UP.rotateAround(frontFacing.getAxis());
+                if (frontFacing.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE) rotated = rotated.getOpposite();
 
                 if (upwardsFacing == EnumFacing.DOWN) rad = Math.PI;
-                else if (upwardsFacing == rotated) rad = Math.PI / 2;
-                else if (upwardsFacing == rotated.getOpposite()) rad = -Math.PI / 2;
+                else if (upwardsFacing == rotated) rad = -Math.PI / 2;
+                else if (upwardsFacing == rotated.getOpposite()) rad = Math.PI / 2;
             }
 
             translation.translate(0.5, 0.5, 0.5);
@@ -405,7 +407,7 @@ public void checkStructurePattern() {
 
     public void checkStructurePattern(String name) {
         IBlockPattern pattern = getSubstructure(name);
-        if (!pattern.getPatternState().shouldUpdate()) return;
+        if (!pattern.getPatternState().shouldUpdate() || getWorld() == null) return;
 
         long time = System.nanoTime();
         PatternState result = pattern.checkPatternFastAt(getWorld(), getPos(),
@@ -754,8 +756,12 @@ public boolean onWrenchClick(EntityPlayer playerIn, EnumHand hand, EnumFacing wr
                                  CuboidRayTraceResult hitResult) {
         if (wrenchSide == getFrontFacing() && allowsExtendedFacing()) {
             if (!getWorld().isRemote) {
-                EnumFacing.Axis axis = getFrontFacing().getAxis();
-                setUpwardsFacing(playerIn.isSneaking() ? upwardsFacing.rotateAround(axis).getOpposite() : upwardsFacing.rotateAround(axis));
+                EnumFacing rot = upwardsFacing.rotateAround(getFrontFacing().getAxis());
+                if (frontFacing.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE ^ playerIn.isSneaking()) {
+                    rot = rot.getOpposite();
+                }
+
+                setUpwardsFacing(rot);
             }
             return true;
         }
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index e428b5c038c..9494e6f25c4 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -421,4 +421,16 @@ protected GreggyBlockPos computeNext() {
             }
         };
     }
+
+    /**
+     * BlockPos version of {@link GreggyBlockPos#get(EnumFacing.Axis)}, for if the operation is small enough
+     * allocating more BlockPos is acceptable.
+     */
+    public static int getAxis(BlockPos pos, EnumFacing.Axis axis) {
+        return switch (axis) {
+            case X -> pos.getX();
+            case Y -> pos.getY();
+            case Z -> pos.getZ();
+        };
+    }
 }
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index 988bd50b729..8426c2fe137 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -103,15 +103,14 @@ public BlockPos getMap(MultiblockControllerBase src, BlockPos start, EnumFacing
                         mteHolder.getMetaTileEntity().setFrontFacing(newFacing);
 
 
-                        if (mteHolder.getMetaTileEntity() instanceof MultiblockControllerBase base) {
-                            // there is no way to determine upwards facing with only a front facing
-                            // so if you want to have a multiblock with an upward facings that isn't UP
-                            // use the (IBlockState, TileEntity) ctor for BlockInfo and set upwardsFacing there
-                            // currently this just sets the controller's upwards facing
-                            if (base.getClass() == src.getClass()) {
+                        if (holder.getMetaTileEntity() instanceof MultiblockControllerBase holderBase) {
+                            MultiblockControllerBase mteBase = (MultiblockControllerBase) mteHolder.getMetaTileEntity();
+
+                            EnumFacing newUpFacing = FACING_MAP.get(holderBase.getUpwardsFacing()).getRelativeFacing(frontFacing, upFacing, false);
+                            if (holderBase.getClass() == src.getClass()) {
                                 controller = pos.immutable();
-                                base.setUpwardsFacing(upFacing);
                             }
+                            mteBase.setUpwardsFacing(newUpFacing);
                         }
 
                         map.put(pos.immutable(),
diff --git a/src/main/java/gregtech/api/util/RelativeDirection.java b/src/main/java/gregtech/api/util/RelativeDirection.java
index 03cf87bad16..dc0cb60efcf 100644
--- a/src/main/java/gregtech/api/util/RelativeDirection.java
+++ b/src/main/java/gregtech/api/util/RelativeDirection.java
@@ -81,12 +81,16 @@ public static EnumFacing simulateAxisRotation(EnumFacing newFrontFacing, EnumFac
         // 180 degree flip
         if (newFrontFacing.getAxis() == oldFrontFacing.getAxis()) return upwardsFacing;
 
-        // axis of rotation
-        Axis rotAxis = GTUtility.cross(newFrontFacing, oldFrontFacing).getAxis();
+        EnumFacing cross = GTUtility.cross(newFrontFacing, oldFrontFacing);
 
-        if (rotAxis == upwardsFacing.getAxis()) return upwardsFacing;
+        if (cross.getAxis() == upwardsFacing.getAxis()) return upwardsFacing;
 
-        return oldFrontFacing.getOpposite();
+        // clockwise rotation
+        if (oldFrontFacing.rotateAround(cross.getAxis()) == newFrontFacing) {
+            return oldFrontFacing.getOpposite();
+        }
+
+        return oldFrontFacing;
     }
 
     /**
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index 4a2e363207d..ee03ca39bb6 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -104,7 +104,7 @@ public static void resetMultiblockRender() {
     public static void renderControllerInList(MultiblockControllerBase controllerBase, MultiblockShapeInfo shapeInfo,
                                               int layer) {
         Map blockMap = new HashMap<>();
-        BlockPos controllerPos = shapeInfo.getMap(controllerBase, BlockPos.ORIGIN, controllerBase.getFrontFacing(), controllerBase.getUpwardsFacing(), blockMap);
+        BlockPos controllerPos = shapeInfo.getMap(controllerBase, new BlockPos(0, 128, 0), controllerBase.getFrontFacing(), controllerBase.getUpwardsFacing(), blockMap);
         MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap.get(controllerPos).getTileEntity()).getMetaTileEntity();
 
         EnumFacing facing = controllerBase.getFrontFacing();
@@ -113,7 +113,7 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas
         TrackedDummyWorld world = new TrackedDummyWorld();
         world.addBlocks(blockMap);
         int finalMaxY = layer % (shapeInfo.getUpCount(facing, upwardsFacing) + 1);
-        world.setRenderFilter(pos -> pos.getY() + 1 == finalMaxY || finalMaxY == 0);
+        world.setRenderFilter(pos -> pos.getY() - 127 == finalMaxY || finalMaxY == 0);
 
         Minecraft mc = Minecraft.getMinecraft();
         BlockRendererDispatcher brd = mc.getBlockRendererDispatcher();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index d52c7929a8f..7b3d31814eb 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -16,11 +16,14 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
+import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.pipenet.tile.TileEntityPipeBase;
 import gregtech.api.util.FacingPos;
+import gregtech.api.util.GTLog;
+import gregtech.api.util.RelativeDirection;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.client.utils.RenderUtil;
@@ -521,9 +524,7 @@ public void renderMetaTileEntity(double x, double y, double z, float partialTick
                     for (BlockPos pos : parts) {
                         TileEntity tileEntity = getWorld().getTileEntity(pos);
                         if (tileEntity instanceof IGregTechTileEntity && ((IGregTechTileEntity) tileEntity)
-                                .getMetaTileEntity() instanceof MetaTileEntityMonitorScreen) {
-                            MetaTileEntityMonitorScreen screen = (MetaTileEntityMonitorScreen) ((IGregTechTileEntity) tileEntity)
-                                    .getMetaTileEntity();
+                                .getMetaTileEntity() instanceof MetaTileEntityMonitorScreen screen) {
                             screen.addToMultiBlock(this, "MAIN");
                             int sx = screen.getX(), sy = screen.getY();
                             if (sx < 0 || sx >= width || sy < 0 || sy >= height) {
@@ -591,22 +592,16 @@ protected ModularUI createUI(EntityPlayer entityPlayer) {
             }
             if (!this.getWorld().isRemote) {
                 this.getMultiblockParts().forEach(part -> {
-                    if (part instanceof MetaTileEntityMonitorScreen) {
-                        int x = ((MetaTileEntityMonitorScreen) part).getX();
-                        int y = ((MetaTileEntityMonitorScreen) part).getY();
-                        screenGrids[x][y].setScreen((MetaTileEntityMonitorScreen) part);
+                    if (part instanceof MetaTileEntityMonitorScreen screen) {
+                        screenGrids[screen.getX()][screen.getY()].setScreen(screen);
                     }
                 });
             } else {
                 parts.forEach(partPos -> {
                     TileEntity tileEntity = this.getWorld().getTileEntity(partPos);
                     if (tileEntity instanceof IGregTechTileEntity && ((IGregTechTileEntity) tileEntity)
-                            .getMetaTileEntity() instanceof MetaTileEntityMonitorScreen) {
-                        MetaTileEntityMonitorScreen part = (MetaTileEntityMonitorScreen) ((IGregTechTileEntity) tileEntity)
-                                .getMetaTileEntity();
-                        int x = part.getX();
-                        int y = part.getY();
-                        screenGrids[x][y].setScreen(part);
+                            .getMetaTileEntity() instanceof MetaTileEntityMonitorScreen screen) {
+                        screenGrids[screen.getX()][screen.getY()].setScreen(screen);
                     }
                 });
             }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java
index 0758efb8e70..78db95c4da6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java
@@ -15,6 +15,7 @@
 import gregtech.api.metatileentity.MetaTileEntityUIFactory;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.util.FacingPos;
 import gregtech.api.util.GTLog;
@@ -62,6 +63,9 @@
 import java.io.IOException;
 import java.util.*;
 
+import static gregtech.api.util.RelativeDirection.LEFT;
+import static gregtech.api.util.RelativeDirection.RIGHT;
+
 public class MetaTileEntityMonitorScreen extends MetaTileEntityMultiblockPart {
 
     // run-time data
@@ -202,71 +206,24 @@ private void updateProxyPlugin() {
     }
 
     public int getX() {
-        MultiblockControllerBase controller = this.getController();
-        if (controller != null) {
-            EnumFacing spin = controller.getUpwardsFacing();
-            switch (controller.getFrontFacing().getAxis()) {
-                case Y -> {
-                    if (spin.getAxis() == EnumFacing.Axis.X)
-                        return Math.abs(this.getController().getPos().getZ() - this.getPos().getZ()) - 1;
-                    else
-                        return Math.abs(this.getController().getPos().getX() - this.getPos().getX()) - 1;
-                }
-                case X -> {
-                    if (spin.getAxis() == EnumFacing.Axis.Z)
-                        return Math.abs(this.getController().getPos().getZ() - this.getPos().getZ()) - 1;
-                    else
-                        return Math.abs(this.getController().getPos().getY() - this.getPos().getY()) - 1;
-                }
-                default -> {
-                    if (spin.getAxis() == EnumFacing.Axis.Z)
-                        return Math.abs(this.getController().getPos().getX() - this.getPos().getX()) - 1;
-                    else
-                        return Math.abs(this.getController().getPos().getY() - this.getPos().getY()) - 1;
-                }
-            }
-        }
-        return -1;
+        MultiblockControllerBase controller = getController();
+        if (controller == null) return -1;
+
+        EnumFacing.Axis lrAxis = RIGHT.getRelativeFacing(controller.getFrontFacing(), controller.getUpwardsFacing()).getAxis();
+        return Math.abs(GreggyBlockPos.getAxis(getPos(), lrAxis) - GreggyBlockPos.getAxis(controller.getPos(), lrAxis)) - 1;
     }
 
     public int getY() {
-        MultiblockControllerBase controller = this.getController();
-        if (controller != null) {
-            EnumFacing spin = controller.getUpwardsFacing();
-            EnumFacing facing = controller.getFrontFacing();
-            int height = ((MetaTileEntityCentralMonitor) this.getController()).height;
-            switch (facing.getAxis()) {
-                case Y -> {
-                    if (spin.getAxis() == EnumFacing.Axis.X)
-                        return height -
-                                (Math.abs(controller.getPos().getX() - spin.getXOffset() - this.getPos().getX())) - 1;
-                    else
-                        return height -
-                                (Math.abs(controller.getPos().getZ() - spin.getZOffset() - this.getPos().getZ())) - 1;
-                }
-                case X -> {
-                    if (spin.getAxis() == EnumFacing.Axis.Z)
-                        return height -
-                                (Math.abs(controller.getPos().getY() + spin.getZOffset() - this.getPos().getY())) - 1;
-                    else
-                        return height - (Math.abs(
-                                controller.getPos().getZ() + spin.getXOffset() * facing.rotateY().getZOffset() -
-                                        this.getPos().getZ())) -
-                                1;
-                }
-                default -> {
-                    if (spin.getAxis() == EnumFacing.Axis.Z)
-                        return height -
-                                (Math.abs(controller.getPos().getY() + spin.getZOffset() - this.getPos().getY())) - 1;
-                    else
-                        return height - (Math.abs(
-                                controller.getPos().getX() + spin.getXOffset() * facing.rotateY().getXOffset() -
-                                        this.getPos().getX())) -
-                                1;
-                }
-            }
-        }
-        return -1;
+        MultiblockControllerBase controller = getController();
+        if (controller == null) return -1;
+
+        EnumFacing.Axis udAxis = controller.getUpwardsFacing().getAxis();
+        // top left corner of screen
+        GreggyBlockPos pos = new GreggyBlockPos(controller.getPos());
+        pos.offset(controller.getUpwardsFacing(),  ((MetaTileEntityCentralMonitor) controller).height - 2);
+        pos.offset(LEFT.getRelativeFacing(controller.getFrontFacing(), controller.getUpwardsFacing()));
+
+        return Math.abs(pos.get(udAxis) - GreggyBlockPos.getAxis(getPos(), udAxis));
     }
 
     public boolean isActive() {

From 5f49bc75bb8f45650bdb7c3c3fb6fcdcc76678b3 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 25 Aug 2024 19:51:33 -0700
Subject: [PATCH 40/64] autobuild but it doesn't work(and dot blocks)

---
 .../multiblock/MultiblockControllerBase.java  |  33 ++--
 .../api/pattern/MultiblockShapeInfo.java      | 157 ++++++++++++++++-
 .../api/pattern/PatternStringError.java       |   4 +-
 .../api/pattern/pattern/BlockPattern.java     |   2 +-
 .../api/pattern/pattern/PatternState.java     |   4 +
 .../java/gregtech/api/util/GTUtility.java     |  43 +++++
 .../handler/MultiblockPreviewRenderer.java    |  20 ++-
 .../behaviors/MultiblockBuilderBehavior.java  | 161 ++++++++++++++++--
 .../electric/MetaTileEntityCleanroom.java     |   3 +-
 .../electric/MetaTileEntityCrackingUnit.java  |   2 +-
 .../MetaTileEntityElectricBlastFurnace.java   |  12 +-
 .../electric/MetaTileEntityMultiSmelter.java  |   2 +-
 .../electric/MetaTileEntityPyrolyseOven.java  |   2 +-
 .../MultiblockInfoRecipeWrapper.java          |  39 +----
 14 files changed, 400 insertions(+), 84 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index cff2054d6aa..e51aae5ef63 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -12,6 +12,7 @@
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.MultiblockShapeInfo;
+import gregtech.api.pattern.PatternStringError;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.pattern.pattern.PatternState;
@@ -137,7 +138,7 @@ public void update() {
     /**
      * @return structure pattern of this multiblock
      */
-    // todo fix central monitor, and vacuum freezer
+    // todo fix vacuum freezer
     @NotNull
     protected abstract IBlockPattern createStructurePattern();
 
@@ -317,20 +318,25 @@ public static TraceabilityPredicate heatingCoils() {
 
     /**
      * Ensures that all the blockstates that are in the map are the same type. Returns the type if all match, or null if
-     * they don't(or none match).
-     * Example: {@code allSameType(GregTechAPI.HEATING_COILS, getSubstructure("MAIN").getCache())}
+     * they don't(or none match). Because this method sets an error in the structure, the pattern state will be INVALID_CACHED,
+     * so the pattern will not have its cache cleared, and the controller will not attempt to form the pattern again unless the cache is invalidated(either through code or through it failing).
+     * Example: {@code allSameType(GregTechAPI.HEATING_COILS, getSubstructure("MAIN"))}
      * 
      * @param info  The info, such as GregTechAPI.HEATING_COILS
-     * @param cache The cache for the pattern.
+     * @param pattern Pattern, used to get the cache. It will also be used to set the error.
+     * @param error The error, this is only set if the types don't match using {@link gregtech.api.pattern.PatternStringError}.
      */
-    public static  V allSameType(Object2ObjectMap info, Long2ObjectMap cache) {
+    public static  V allSameType(Object2ObjectMap info, IBlockPattern pattern, String error) {
         V type = null;
-        for (BlockInfo blockInfo : cache.values()) {
-            V state = info.get(blockInfo.getBlockState());
+        for (Long2ObjectMap.Entry entry : pattern.getCache().long2ObjectEntrySet()) {
+            V state = info.get(entry.getValue().getBlockState());
             if (state != null) {
                 if (type != state) {
                     if (type == null) type = state;
-                    else return null;
+                    else {
+                        pattern.getPatternState().setError(new PatternStringError(BlockPos.fromLong(entry.getLongKey()), error));
+                        return null;
+                    }
                 }
             }
         }
@@ -353,7 +359,6 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
             baseTexture.render(renderState, translation, pipeline);
         }
 
-        // todo fix
         if (allowsExtendedFacing()) {
             double rad = 0;
             if (frontFacing.getAxis() == EnumFacing.Axis.Y) {
@@ -428,7 +433,7 @@ public void checkStructurePattern(String name) {
                         // this part is already added, so igore it
                         if (multiblockParts.contains(part)) return true;
 
-                        // todo maybe move below into separate check?
+                        // todo move below into separate check
                         if (part.isAttachedToMultiBlock() && !part.canPartShare(this, name)) {
                             invalidateStructure(name);
                             return false;
@@ -745,7 +750,7 @@ public boolean onRightClick(EntityPlayer playerIn, EnumHand hand, EnumFacing fac
 
         if (this.getWorld().isRemote && !this.isStructureFormed("MAIN") && playerIn.isSneaking() &&
                 playerIn.getHeldItem(hand).isEmpty()) {
-            MultiblockPreviewRenderer.renderMultiBlockPreview(this, 60000);
+            MultiblockPreviewRenderer.renderMultiBlockPreview(this, playerIn, 60000);
             return true;
         }
         return false;
@@ -798,15 +803,15 @@ public List getMatchingShapes() {
      */
     // todo add use for the keyMap with the multiblock builder
     // todo maybe add name arg for building substructures
-    public List getBuildableShapes(@Nullable Object2IntMap keyMap) {
+    public List getBuildableShapes(String substructureName, @Nullable Object2IntMap keyMap) {
         List infos = getMatchingShapes();
 
         // if there is no overriden getMatchingShapes() just return the default one
         if (infos.isEmpty()) {
             // for jei and stuff
-            if (getSubstructure("MAIN") == null) structures.put("MAIN", createStructurePattern());
+            if (getSubstructure(substructureName) == null) createStructurePatterns();
 
-            MultiblockShapeInfo info = getSubstructure("MAIN").getDefaultShape();
+            MultiblockShapeInfo info = getSubstructure(substructureName).getDefaultShape();
             if (info == null) return Collections.emptyList();
             return Collections.singletonList(info);
         }
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index 8426c2fe137..e4239c23ff7 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -1,13 +1,27 @@
 package gregtech.api.pattern;
 
+import com.github.bsideup.jabel.Desugar;
+
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.pattern.PatternAisle;
 import gregtech.api.util.BlockInfo;
+import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
+import it.unimi.dsi.fastutil.chars.Char2IntMap;
+
+import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap;
+
+import it.unimi.dsi.fastutil.chars.Char2ObjectRBTreeMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+
 import net.minecraft.block.state.IBlockState;
+import net.minecraft.client.resources.I18n;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.init.Blocks;
+import net.minecraft.item.ItemStack;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
@@ -15,8 +29,14 @@
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
 
+import net.minecraft.util.text.TextComponentString;
+import net.minecraft.util.text.TextComponentTranslation;
+import net.minecraft.world.World;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
@@ -41,6 +61,7 @@ public class MultiblockShapeInfo {
     public static final Map FACING_MAP;
     protected final PatternAisle[] aisles;
     protected final Char2ObjectMap symbols;
+    protected final Char2ObjectMap dotMap;
     protected final RelativeDirection[] directions;
 
     static {
@@ -52,17 +73,54 @@ public class MultiblockShapeInfo {
         FACING_MAP = Collections.unmodifiableMap(facingMap);
     }
 
-    public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symbols,
+    public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symbols, Char2ObjectMap dotMap,
                                RelativeDirection[] directions) {
         this.aisles = aisles;
         this.symbols = symbols;
+        this.dotMap = dotMap;
         this.directions = directions;
         symbols.defaultReturnValue(BlockInfo.EMPTY);
     }
 
     /**
-     * Gets a map of where blocks should be placed, note that the controller is always facing south(and up facing
-     * NORTH).
+     * Builds the given multiblock. The world is gotten from the player's world variable.
+     * @param src The multiblock in world to build from.
+     * @param player The player autobuilding and whos inventory will be used.
+     * @param partial If true, if the player does not have enough materials, the multiblock will be built as much as possible until they run out of a block to place.
+     *                If false, the autobuild will only succeed if the player can build the entire multiblock at once.
+     * @return Whether the autobuild was successful. If partial is false, returns true only if the entire multiblock was build. If partial is true, returns false only if no blocks were placed.
+     */
+    public boolean autoBuild(MultiblockControllerBase src, EntityPlayer player, boolean partial) {
+        World world = player.world;
+        GreggyBlockPos pos = whereController(src.getClass());
+
+        EnumFacing frontFacing = src.getFrontFacing();
+        EnumFacing upFacing = src.getUpwardsFacing();
+
+        EnumFacing absoluteAisle = directions[0].getRelativeFacing(frontFacing, upFacing, false);
+        EnumFacing absoluteString = directions[1].getRelativeFacing(frontFacing, upFacing, false);
+        EnumFacing absoluteChar = directions[2].getRelativeFacing(frontFacing, upFacing, false);
+
+        GreggyBlockPos start = new GreggyBlockPos(src.getPos())
+                .offset(absoluteAisle, pos.x())
+                .offset(absoluteString, pos.y())
+                .offset(absoluteChar, pos.y());
+
+        Object2IntMap materials;
+        if (!partial) {
+            Char2IntMap count = getChars();
+            for (Char2IntMap.Entry entry : count.char2IntEntrySet()) {
+                BlockInfo info = symbols.get(entry.getCharKey());
+
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Gets a map of where blocks should be placed, note that the controller is expected to be front facing SOUTH(and up facing
+     * UP).
      * Unlike BlockPattern, the first char in the first string in the first aisle always starts at the origin, instead
      * of being relative to the controller.
      * The passed in map is populated.
@@ -71,9 +129,11 @@ public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symb
      */
     // this is currently here so that multiblocks can have other multiblocks in their structure without messing
     // everything up
-    public BlockPos getMap(MultiblockControllerBase src, BlockPos start, EnumFacing frontFacing, EnumFacing upFacing,
-                                           Map map) {
+    public BlockPos getMap(MultiblockControllerBase src, BlockPos start, Map map) {
+        EnumFacing frontFacing = src.getFrontFacing();
+        EnumFacing upFacing = src.getUpwardsFacing();
         // todo update cleanroom and charcoal pile igniter with enummap instead of hashmap
+        // todo replace the start argument such that now it signifies where the map should be looked up to find the controller, and thus auto detect pattern start
         // seems like MultiblockInfoRecipeWrapper wants the controller to be facing south
         EnumFacing absoluteAisle = directions[0].getRelativeFacing(frontFacing, upFacing, false);
         EnumFacing absoluteString = directions[1].getRelativeFacing(frontFacing, upFacing, false);
@@ -93,6 +153,12 @@ public BlockPos getMap(MultiblockControllerBase src, BlockPos start, EnumFacing
                     char c = aisles[aisleI].charAt(stringI, charI);
                     pos.from(start).offset(absoluteAisle, aisleI).offset(absoluteString, stringI).offset(absoluteChar,
                             charI);
+
+                    if (dotMap.containsKey(c)) {
+                        map.put(pos.immutable(), new BlockInfo(Blocks.DIAMOND_BLOCK));
+                        continue;
+                    }
+
                     if (symbols.get(c).getTileEntity() instanceof MetaTileEntityHolder holder) {
                         MetaTileEntityHolder mteHolder = new MetaTileEntityHolder();
                         mteHolder.setMetaTileEntity(holder.getMetaTileEntity());
@@ -147,6 +213,49 @@ public BlockPos getMap(MultiblockControllerBase src, BlockPos start, EnumFacing
         return controller;
     }
 
+    /**
+     * Gets where the controller is in the pattern.
+     * @param clazz The class of the controller.
+     * @return A pos where {@code aisles[pos.x()].getCharAt(pos.y(), pos.z())} would return where the controller char was.
+     */
+    protected GreggyBlockPos whereController(Class clazz) {
+        char c = 'S';
+        for (Char2ObjectMap.Entry entry : symbols.char2ObjectEntrySet()) {
+            if (entry.getValue().getTileEntity() instanceof MetaTileEntityHolder holder && holder.getMetaTileEntity() instanceof MultiblockControllerBase controller && controller.getClass() == clazz) {
+                c = entry.getCharKey();
+                break;
+            }
+        }
+
+        for (int aisleI = 0; aisleI < aisles.length; aisleI++) {
+            for (int stringI = 0; stringI < aisles[0].getStringCount(); stringI++) {
+                for (int charI = 0; charI < aisles[0].getCharCount(); charI++) {
+                    if (aisles[aisleI].charAt(stringI, charI) == c) return new GreggyBlockPos(aisleI, stringI, charI);
+                }
+            }
+        }
+
+        // maybe throw instead?
+        return new GreggyBlockPos(0, 0, 0);
+    }
+
+    /**
+     * Gets how many times each char occurs in the aisles.
+     */
+    protected Char2IntMap getChars() {
+        Char2IntMap map = new Char2IntOpenHashMap();
+        for (PatternAisle aisle : aisles) {
+            for (int stringI = 0; stringI < aisles[0].getStringCount(); stringI++) {
+                for (int charI = 0; charI < aisles[0].getCharCount(); charI++) {
+                    char c = aisle.charAt(stringI, charI);
+                    map.put(c, map.get(c) + 1);
+                }
+            }
+        }
+
+        return map;
+    }
+
     public int getUpCount(EnumFacing frontFacing, EnumFacing upFacing) {
 
         int index = 0;
@@ -166,6 +275,16 @@ public int getUpCount(EnumFacing frontFacing, EnumFacing upFacing) {
         };
     }
 
+    public void sendDotMessage(EntityPlayer player) {
+        Dot[] dots = dotMap.values().toArray(new Dot[0]);
+        Arrays.sort(dots, Comparator.comparingInt(Dot::dot));
+
+        for (Dot dot : dots) {
+            player.sendMessage(new TextComponentString("Dot Block " + dot.dot));
+            player.sendMessage(new TextComponentTranslation(dot.lang));
+        }
+    }
+
     public static Builder builder(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
         return new Builder(aisleDir, stringDir, charDir);
     }
@@ -179,6 +298,7 @@ public static class Builder {
 
         private List shape = new ArrayList<>();
         private Char2ObjectMap symbolMap = new Char2ObjectOpenHashMap<>();
+        private Char2ObjectMap dotMap = new Char2ObjectOpenHashMap<>();
         private final RelativeDirection[] directions = new RelativeDirection[3];
 
         public Builder(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
@@ -211,6 +331,11 @@ public Builder aisle(String... data) {
         }
 
         public Builder where(char symbol, BlockInfo value) {
+            if (symbolMap.containsKey(symbol)) {
+                GTLog.logger.warn("Tried to put symbol " + symbol + " when it was already registered in the as a dot block! Ignoring the call.");
+                return this;
+            }
+
             this.symbolMap.put(symbol, value);
             return this;
         }
@@ -244,15 +369,35 @@ public Builder where(char symbol, Supplier partSupplier, EnumFacing frontSide
                     "Supplier must supply either a MetaTileEntity or an IBlockState! Actual: " + part.getClass());
         }
 
+        /**
+         * Adds a dot block to represent the char.
+         * @param symbol The symbol in the pattern to put.
+         * @param dot The amount of dots on the block, 0-15
+         * @param lang The lang to show for the block, pass in the lang key and it will format.
+         */
+        public Builder dot(char symbol, int dot, String lang) {
+            if (symbolMap.containsKey(symbol)) {
+                GTLog.logger.warn("Tried to put symbol " + symbol + " as dot block " + dot + " when it was already registered in the symbol map! Ignoring the call.");
+                return this;
+            }
+
+            dotMap.put(symbol, new Dot(dot, lang));
+            return this;
+        }
+
         public Builder shallowCopy() {
             Builder builder = new Builder(directions[0], directions[1], directions[2]);
             builder.shape = new ArrayList<>(this.shape);
             builder.symbolMap = new Char2ObjectOpenHashMap<>(this.symbolMap);
+            builder.dotMap = new Char2ObjectOpenHashMap<>(this.dotMap);
             return builder;
         }
 
         public MultiblockShapeInfo build() {
-            return new MultiblockShapeInfo(shape.toArray(new PatternAisle[0]), symbolMap, directions);
+            return new MultiblockShapeInfo(shape.toArray(new PatternAisle[0]), symbolMap, dotMap, directions);
         }
     }
+
+    @Desugar
+    public record Dot(int dot, String lang) {}
 }
diff --git a/src/main/java/gregtech/api/pattern/PatternStringError.java b/src/main/java/gregtech/api/pattern/PatternStringError.java
index e15f9c1fd41..84e4b2b74de 100644
--- a/src/main/java/gregtech/api/pattern/PatternStringError.java
+++ b/src/main/java/gregtech/api/pattern/PatternStringError.java
@@ -9,8 +9,8 @@ public class PatternStringError extends PatternError {
 
     public final String translateKey;
 
-    public PatternStringError(String translateKey) {
-        super(new BlockPos(0, 0, 0), Collections.emptyList());
+    public PatternStringError(BlockPos pos, String translateKey) {
+        super(pos, Collections.emptyList());
         this.translateKey = translateKey;
     }
 
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 50dc264e1d9..05a56109579 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -429,7 +429,7 @@ public MultiblockShapeInfo getDefaultShape() {
         }
 
         return new MultiblockShapeInfo(pattern.stream().map(a -> new PatternAisle(1, a)).toArray(PatternAisle[]::new),
-                candidates, directions);
+                candidates, new Char2ObjectOpenHashMap<>(), directions);
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/pattern/pattern/PatternState.java b/src/main/java/gregtech/api/pattern/pattern/PatternState.java
index 04fa8d37ffa..fa4a3fd161f 100644
--- a/src/main/java/gregtech/api/pattern/pattern/PatternState.java
+++ b/src/main/java/gregtech/api/pattern/pattern/PatternState.java
@@ -54,6 +54,10 @@ public boolean hasError() {
         return error != null;
     }
 
+    public PatternError getError() {
+        return error;
+    }
+
     protected void setState(EnumCheckState state) {
         this.state = state;
     }
diff --git a/src/main/java/gregtech/api/util/GTUtility.java b/src/main/java/gregtech/api/util/GTUtility.java
index 4749be1788d..9716664eb10 100644
--- a/src/main/java/gregtech/api/util/GTUtility.java
+++ b/src/main/java/gregtech/api/util/GTUtility.java
@@ -20,6 +20,9 @@
 import gregtech.api.unification.OreDictUnifier;
 import gregtech.api.unification.ore.OrePrefix;
 
+import gregtech.integration.jei.multiblock.MultiblockInfoRecipeWrapper;
+
+import net.minecraft.block.Block;
 import net.minecraft.block.BlockRedstoneWire;
 import net.minecraft.block.BlockSnow;
 import net.minecraft.block.material.MapColor;
@@ -38,6 +41,8 @@
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.util.SoundCategory;
 import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.RayTraceResult;
+import net.minecraft.util.math.Vec3d;
 import net.minecraft.util.text.TextComponentTranslation;
 import net.minecraft.world.IBlockAccess;
 import net.minecraft.world.World;
@@ -653,6 +658,44 @@ public static ItemStack toItem(IBlockState state, int amount) {
         return new ItemStack(state.getBlock(), amount, state.getBlock().getMetaFromState(state));
     }
 
+    /**
+     * Converts the block at pos in the world to an item. First uses the MTE method if the block is a mte, then uses pick block, then uses the first of the block drops, then returns EMPTY.
+     */
+    @NotNull
+    public static ItemStack toItem(World world, BlockPos pos) {
+        IBlockState state = world.getBlockState(pos);
+        Block block = state.getBlock();
+
+        ItemStack stack = ItemStack.EMPTY;
+
+        // first check if the block is a GT machine
+        TileEntity tileEntity = world.getTileEntity(pos);
+        if (tileEntity instanceof IGregTechTileEntity) {
+            stack = ((IGregTechTileEntity) tileEntity).getMetaTileEntity().getStackForm();
+        }
+
+        if (stack.isEmpty()) {
+            // first, see what the block has to say for itself before forcing it to use a particular meta value
+            stack = block.getPickBlock(state, new RayTraceResult(Vec3d.ZERO, EnumFacing.UP, pos), world, pos,
+                    new GregFakePlayer(world));
+        }
+        if (stack.isEmpty()) {
+            // try the default itemstack constructor if we're not a GT machine
+            stack = GTUtility.toItem(state);
+        }
+
+        if (stack.isEmpty()) {
+            // add the first of the block's drops if the others didn't work
+            NonNullList list = NonNullList.create();
+            state.getBlock().getDrops(list, world, pos, state, 0);
+            if (!list.isEmpty()) {
+                return list.get(0);
+            }
+        }
+
+        return ItemStack.EMPTY;
+    }
+
     public static boolean isOre(ItemStack item) {
         OrePrefix orePrefix = OreDictUnifier.getPrefix(item);
         return orePrefix != null && orePrefix.name().startsWith("ore");
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index ee03ca39bb6..9f25e922f99 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -1,6 +1,5 @@
 package gregtech.client.renderer.handler;
 
-import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
@@ -11,15 +10,19 @@
 
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.Minecraft;
-import net.minecraft.client.renderer.*;
+import net.minecraft.client.renderer.BlockRendererDispatcher;
+import net.minecraft.client.renderer.BufferBuilder;
+import net.minecraft.client.renderer.GLAllocation;
+import net.minecraft.client.renderer.GlStateManager;
+import net.minecraft.client.renderer.Tessellator;
 import net.minecraft.client.renderer.texture.TextureMap;
 import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
 import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.init.Blocks;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.BlockRenderLayer;
 import net.minecraft.util.EnumFacing;
-import net.minecraft.util.Rotation;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.IBlockAccess;
 import net.minecraft.world.WorldType;
@@ -75,7 +78,7 @@ public static void renderWorldLastEvent(RenderWorldLastEvent event) {
         }
     }
 
-    public static void renderMultiBlockPreview(MultiblockControllerBase controller, long durTimeMillis) {
+    public static void renderMultiBlockPreview(MultiblockControllerBase controller, EntityPlayer player, long durTimeMillis) {
         if (!controller.getPos().equals(mbpPos)) {
             layer = 0;
         } else {
@@ -88,7 +91,10 @@ public static void renderMultiBlockPreview(MultiblockControllerBase controller,
         opList = GLAllocation.generateDisplayLists(1); // allocate op list
         GlStateManager.glNewList(opList, GL11.GL_COMPILE);
         List shapes = controller.getMatchingShapes();
-        if (!shapes.isEmpty()) renderControllerInList(controller, shapes.get(0), layer);
+        if (!shapes.isEmpty()) {
+            renderControllerInList(controller, shapes.get(0), layer);
+            shapes.get(0).sendDotMessage(player);
+        }
         GlStateManager.glEndList();
     }
 
@@ -104,7 +110,7 @@ public static void resetMultiblockRender() {
     public static void renderControllerInList(MultiblockControllerBase controllerBase, MultiblockShapeInfo shapeInfo,
                                               int layer) {
         Map blockMap = new HashMap<>();
-        BlockPos controllerPos = shapeInfo.getMap(controllerBase, new BlockPos(0, 128, 0), controllerBase.getFrontFacing(), controllerBase.getUpwardsFacing(), blockMap);
+        BlockPos controllerPos = shapeInfo.getMap(controllerBase, new BlockPos(0, 128, 0), blockMap);
         MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap.get(controllerPos).getTileEntity()).getMetaTileEntity();
 
         EnumFacing facing = controllerBase.getFrontFacing();
@@ -124,6 +130,7 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas
 
         BlockRenderLayer oldLayer = MinecraftForgeClient.getRenderLayer();
         TargetBlockAccess targetBA = new TargetBlockAccess(world, BlockPos.ORIGIN);
+
         GreggyBlockPos greg = new GreggyBlockPos();
         GreggyBlockPos offset = new GreggyBlockPos(controllerPos);
         GreggyBlockPos temp = new GreggyBlockPos();
@@ -151,7 +158,6 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas
         ForgeHooksClient.setRenderLayer(oldLayer);
     }
 
-    // todo maybe remove??? who knows what this does but it looks like nothing useful
     @SideOnly(Side.CLIENT)
     private static class TargetBlockAccess implements IBlockAccess {
 
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index 55c7e336588..d5bcc52ea74 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -1,21 +1,41 @@
 package gregtech.common.items.behaviors;
 
+import com.cleanroommc.modularui.api.drawable.IKey;
+import com.cleanroommc.modularui.factory.HandGuiData;
+import com.cleanroommc.modularui.screen.ModularPanel;
+import com.cleanroommc.modularui.value.sync.GuiSyncManager;
+import com.cleanroommc.modularui.value.sync.StringSyncValue;
+import com.cleanroommc.modularui.widgets.SortableListWidget;
+import com.cleanroommc.modularui.widgets.layout.Row;
+import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget;
+import com.github.bsideup.jabel.Desugar;
+
+import gregtech.api.items.gui.ItemUIFactory;
 import gregtech.api.items.metaitem.stats.IItemBehaviour;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.mui.GTGuiTextures;
+import gregtech.api.mui.GTGuis;
+import gregtech.api.mui.factory.MetaItemGuiFactory;
+import gregtech.api.pattern.PatternError;
 import gregtech.api.util.GTUtility;
 
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+
 import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.ActionResult;
 import net.minecraft.util.EnumActionResult;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.EnumHand;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.text.Style;
+import net.minecraft.util.text.TextComponentString;
 import net.minecraft.util.text.TextComponentTranslation;
 import net.minecraft.util.text.TextFormatting;
 import net.minecraft.world.World;
@@ -24,8 +44,124 @@
 import org.jetbrains.annotations.NotNull;
 
 import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class MultiblockBuilderBehavior implements IItemBehaviour, ItemUIFactory {
+    public static final int MAX_KEYS = 16;
+    @Override
+    public ActionResult onItemRightClick(World world, EntityPlayer player, EnumHand hand) {
+        ItemStack heldItem = player.getHeldItem(hand);
+        initNBT(heldItem);
+        if (!world.isRemote) {
+            MetaItemGuiFactory.open(player, hand);
+        }
+        return ActionResult.newResult(EnumActionResult.SUCCESS, heldItem);
+    }
+
+    public static Object2IntMap getMap(ItemStack target) {
+        if (!target.hasTagCompound()) return new Object2IntOpenHashMap<>();
+
+        NBTTagCompound tag = target.getTagCompound().getCompoundTag("MultiblockBuilder");
+        Object2IntMap result = new Object2IntOpenHashMap<>();
 
-public class MultiblockBuilderBehavior implements IItemBehaviour {
+        for (String str : tag.getKeySet()) {
+            NBTTagCompound entry = tag.getCompoundTag(str);
+            String key = entry.getString("Key");
+            String val = entry.getString("Value");
+            if (key.isEmpty() || val.isEmpty()) continue;
+
+            result.put(key, Integer.parseInt(val));
+        }
+
+        return result;
+    }
+
+    protected static void initNBT(ItemStack target) {
+        if (target.hasTagCompound()) return;
+
+        NBTTagCompound tag = new NBTTagCompound();
+        tag.setTag("MultiblockBuilder", new NBTTagCompound());
+        target.setTagCompound(tag);
+    }
+
+    protected static NBTData loadKeys(ItemStack target) {
+        NBTTagCompound tag = target.getTagCompound().getCompoundTag("MultiblockBuilder");
+
+        String[] keys = new String[MAX_KEYS];
+        String[] values = new String[MAX_KEYS];
+
+        for (int i = 0; i < MAX_KEYS; i++) {
+            String str = Integer.toString(i);
+            if (!tag.hasKey(str)) {
+                keys[i] = values[i] = "";
+            }
+            keys[i] = tag.getCompoundTag(str).getString("Key");
+            values[i] = tag.getCompoundTag(str).getString("Value");
+        }
+
+        return new NBTData(keys, values);
+    }
+
+    protected static void setKey(int id, String key, String value, ItemStack target) {
+        NBTTagCompound baseTag = target.getTagCompound().getCompoundTag("MultiblockBuilder");
+
+        NBTTagCompound tag = new NBTTagCompound();
+        tag.setString("Key", key);
+        tag.setString("Value", value);
+        baseTag.setTag(Integer.toString(id), tag);
+    }
+
+    @Override
+    public ModularPanel buildUI(HandGuiData guiData, GuiSyncManager guiSyncManager) {
+        initNBT(guiData.getUsedItemStack());
+
+        StringSyncValue[] keyValues = new StringSyncValue[MAX_KEYS];
+        StringSyncValue[] valueValues = new StringSyncValue[MAX_KEYS];
+
+        NBTData data = loadKeys(guiData.getUsedItemStack());
+        String[] keys = data.keys;
+        String[] values = data.values;
+
+        List total = IntStream.range(0, MAX_KEYS).boxed().collect(Collectors.toList());
+        List present = IntStream.range(0, MAX_KEYS).boxed().collect(Collectors.toList());
+
+        for (int i = 0; i < MAX_KEYS; i++) {
+            int finalI = i;
+            keyValues[i] = new StringSyncValue(() -> keys[finalI], s -> {
+                keys[finalI] = s;
+                setKey(finalI, s, values[finalI], guiData.getUsedItemStack());
+            });
+
+            valueValues[i] = new StringSyncValue(() -> values[finalI], s -> {
+                values[finalI] = s;
+                setKey(finalI, keys[finalI], s, guiData.getUsedItemStack());
+            });
+        }
+
+        SortableListWidget> list = SortableListWidget
+                .sortableBuilder(total, present,
+                        s -> new SortableListWidget.Item<>(s, new Row()
+                                .size(8 * 18, 18)
+                                .child(new TextFieldWidget()
+                                        .left(0).width(4 * 18)
+                                        .setValidator(str -> str.replaceAll("\\W", ""))
+                                        .value(keyValues[s])
+                                        .background(GTGuiTextures.DISPLAY))
+                                .child(new TextFieldWidget()
+                                        .left(4 * 18).width(4 * 18)
+                                        .setValidator(str -> str.replaceAll("\\D", ""))
+                                        .value(valueValues[s])
+                                        .background(GTGuiTextures.DISPLAY))));
+
+        return GTGuis.createPanel(guiData.getUsedItemStack(), 8 * 18 + 2 * 7 + 4, 8 * 18 + 8)
+                .child(IKey.str("Test").asWidget().pos(5, 5))
+                .child(new Row()
+                        .pos(7, 18).coverChildren()
+                        .child(IKey.str("Key").asWidget().pos(0, 0).size(4 * 18, 18))
+                        .child(IKey.str("Value").asWidget().pos(4 * 18, 0).size(4 * 18, 18)))
+                .child(list.pos(7, 36).height(6 * 18).width(8 * 18));
+    }
 
     @Override
     public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPos pos, EnumFacing side, float hitX,
@@ -34,29 +170,29 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
         TileEntity tileEntity = world.getTileEntity(pos);
         if (!(tileEntity instanceof IGregTechTileEntity)) return EnumActionResult.PASS;
         MetaTileEntity mte = ((IGregTechTileEntity) tileEntity).getMetaTileEntity();
-        if (!(mte instanceof MultiblockControllerBase)) return EnumActionResult.PASS;
+        if (!(mte instanceof MultiblockControllerBase multiblock)) return EnumActionResult.PASS;
         if (!player.canPlayerEdit(pos, side, player.getHeldItem(hand))) return EnumActionResult.FAIL;
-        MultiblockControllerBase multiblock = (MultiblockControllerBase) mte;
         if (world.isRemote) return EnumActionResult.SUCCESS;
 
         if (player.isSneaking()) {
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
             if (!multiblock.isStructureFormed("MAIN")) {
-                multiblock.getBuildableShapes(new Object2IntOpenHashMap<>());
+                multiblock.getBuildableShapes("MAIN", getMap(player.getHeldItem(hand)));
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
         } else {
+            // todo full substructure debug and autobuild support
             // If not sneaking, try to show structure debug info (if any) in chat.
             if (!multiblock.isStructureFormed("MAIN")) {
-                // PatternError error = multiblock.structurePatterns[0].getError();
-                // if (error != null) {
-                // player.sendMessage(
-                // new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header"));
-                // player.sendMessage(new TextComponentString(error.getErrorInfo()));
-                // return EnumActionResult.SUCCESS;
-                // }
+                 PatternError error = multiblock.getSubstructure("MAIN").getPatternState().getError();
+                 if (error != null) {
+                     player.sendMessage(
+                     new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header"));
+                     player.sendMessage(new TextComponentString(error.getErrorInfo()));
+                     return EnumActionResult.SUCCESS;
+                 }
             }
             player.sendMessage(new TextComponentTranslation("gregtech.multiblock.pattern.no_errors")
                     .setStyle(new Style().setColor(TextFormatting.GREEN)));
@@ -74,4 +210,7 @@ public void addPropertyOverride(@NotNull Item item) {
     public void addInformation(ItemStack itemStack, List lines) {
         lines.add(I18n.format("metaitem.tool.multiblock_builder.tooltip2"));
     }
+
+    @Desugar
+    public record NBTData(String[] keys, String[] values) {}
 }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index f02bf606806..be895422e8e 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -144,7 +144,7 @@ protected void formStructure(String name) {
         renderingAABB = false;
         writeCustomData(GregtechDataCodes.RENDER_UPDATE, buf -> buf.writeBoolean(false));
 
-        ICleanroomFilter type = allSameType(GregTechAPI.CLEANROOM_FILTERS, getSubstructure(name).getCache());
+        ICleanroomFilter type = allSameType(GregTechAPI.CLEANROOM_FILTERS, getSubstructure("MAIN"), "gregtech.multiblock.pattern.error.filters");
         if (type == null) {
             invalidateStructure(name);
             return;
@@ -170,6 +170,7 @@ protected void formStructure(String name) {
         // max progress is based on the dimensions of the structure: (x^3)-(x^2)
         // taller cleanrooms take longer than wider ones
         // minimum of 100 is a 5x5x5 cleanroom: 125-25=100 ticks
+        // todo fix
         // this.cleanroomLogic.setMaxProgress(Math.max(100,
         // ((lDist + rDist + 1) * (bDist + fDist + 1) * hDist) - ((lDist + rDist + 1) * (bDist + fDist + 1))));
         this.cleanroomLogic.setMaxProgress(100);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
index 57cc6ce1afa..d778d79d9e5 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
@@ -124,7 +124,7 @@ protected ICubeRenderer getFrontOverlay() {
     @Override
     protected void formStructure(String name) {
         super.formStructure(name);
-        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name).getCache());
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name), "gregtech.multiblock.pattern.error.coils");
         if (type == null) {
             invalidateStructure(name);
         } else {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index ec826950ff5..bc3120d9ea5 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -93,7 +93,7 @@ protected void addDisplayText(List textList) {
     @Override
     protected void formStructure(String name) {
         super.formStructure(name);
-        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name).getCache());
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name), "gregtech.multiblock.pattern.error.coils");
         if (type == null) {
             invalidateStructure(name);
         } else {
@@ -193,10 +193,12 @@ public List getMatchingShapes() {
                 .where('D', MetaTileEntities.FLUID_EXPORT_HATCH[GTValues.LV], EnumFacing.EAST)
                 .where('H', MetaTileEntities.MUFFLER_HATCH[GTValues.LV], EnumFacing.UP)
                 .where('M', () -> ConfigHolder.machines.enableMaintenance ? MetaTileEntities.MAINTENANCE_HATCH :
-                        MetaBlocks.METAL_CASING.getState(MetalCasingType.INVAR_HEATPROOF), EnumFacing.NORTH);
-        GregTechAPI.HEATING_COILS.entrySet().stream()
-                .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
-                .forEach(entry -> shapeInfo.add(builder.shallowCopy().where('C', entry.getKey()).build()));
+                        MetaBlocks.METAL_CASING.getState(MetalCasingType.INVAR_HEATPROOF), EnumFacing.NORTH)
+                .dot('C', 1, "gregtech.material.tungsten_carbide");
+        shapeInfo.add(builder.build());
+//        GregTechAPI.HEATING_COILS.entrySet().stream()
+//                .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
+//                .forEach(entry -> shapeInfo.add(builder.shallowCopy().where('C', entry.getKey()).build()));
         return shapeInfo;
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
index 9c9a6f0e2ce..a8d15374f47 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
@@ -106,7 +106,7 @@ protected void addDisplayText(List textList) {
     @Override
     protected void formStructure(String name) {
         super.formStructure(name);
-        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name).getCache());
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name), "gregtech.multiblock.pattern.error.coils");
         if (type == null) {
             invalidateStructure(name);
         } else {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
index 75394c5e199..ecfe00e2766 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
@@ -90,7 +90,7 @@ protected ICubeRenderer getFrontOverlay() {
     @Override
     protected void formStructure(String name) {
         super.formStructure(name);
-        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name).getCache());
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name), "gregtech.multiblock.pattern.error.coils");
         if (type == null) {
             invalidateStructure(name);
         } else {
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 58446b1cecf..fdbcacd9c6f 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -126,7 +126,7 @@ public MBPattern(final WorldSceneRenderer sceneRenderer, final List p
     public MultiblockInfoRecipeWrapper(@NotNull MultiblockControllerBase controller) {
         this.controller = controller;
         Set drops = new ObjectOpenCustomHashSet<>(ItemStackHashStrategy.comparingAllButCount());
-        this.patterns = controller.getBuildableShapes(null).stream()
+        this.patterns = controller.getBuildableShapes("MAIN", null).stream()
                 .map(it -> initializePattern(it, drops))
                 .toArray(MBPattern[]::new);
         allItemStackInputs.addAll(drops);
@@ -510,37 +510,7 @@ private static Collection gatherStructureBlocks(World world, @NotNull
         Map partsMap = new Object2ObjectOpenCustomHashMap<>(
                 ItemStackHashStrategy.comparingAllButCount());
         for (Entry entry : blocks.entrySet()) {
-            BlockPos pos = entry.getKey();
-            IBlockState state = world.getBlockState(pos);
-            Block block = state.getBlock();
-
-            ItemStack stack = ItemStack.EMPTY;
-
-            // first check if the block is a GT machine
-            TileEntity tileEntity = world.getTileEntity(pos);
-            if (tileEntity instanceof IGregTechTileEntity) {
-                stack = ((IGregTechTileEntity) tileEntity).getMetaTileEntity().getStackForm();
-            }
-            if (stack.isEmpty()) {
-                // first, see what the block has to say for itself before forcing it to use a particular meta value
-                stack = block.getPickBlock(state, new RayTraceResult(Vec3d.ZERO, EnumFacing.UP, pos), world, pos,
-                        new GregFakePlayer(world));
-            }
-            if (stack.isEmpty()) {
-                // try the default itemstack constructor if we're not a GT machine
-                stack = GTUtility.toItem(state);
-            }
-            if (stack.isEmpty()) {
-                // add the first of the block's drops if the others didn't work
-                NonNullList list = NonNullList.create();
-                state.getBlock().getDrops(list, world, pos, state, 0);
-                if (!list.isEmpty()) {
-                    ItemStack is = list.get(0);
-                    if (!is.isEmpty()) {
-                        stack = is;
-                    }
-                }
-            }
+            ItemStack stack = GTUtility.toItem(world, entry.getKey());
 
             // if we got a stack, add it to the set and map
             if (!stack.isEmpty()) {
@@ -558,10 +528,11 @@ private static Collection gatherStructureBlocks(World world, @NotNull
     }
 
     @NotNull
+    // todo substructure support
     private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @NotNull Set parts) {
         Map blockMap = new HashMap<>();
         // absolutely dog way of doing this, just setting the center at 128 so that both patterns going down and up can work
-        BlockPos controllerPos = shapeInfo.getMap(this.controller, new BlockPos(0, 128, 0), EnumFacing.SOUTH, EnumFacing.UP, blockMap);
+        BlockPos controllerPos = shapeInfo.getMap(this.controller, new BlockPos(0, 128, 0), blockMap);
         MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap.get(controllerPos).getTileEntity()).getMetaTileEntity();
 
         TrackedDummyWorld world = new TrackedDummyWorld();
@@ -573,7 +544,7 @@ private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @Not
         Vector3f minPos = world.getMinPos();
         center = new Vector3f(minPos.x + size.x / 2, minPos.y + size.y / 2, minPos.z + size.z / 2);
 
-        worldSceneRenderer.addRenderedBlocks(world.renderedBlocks, null);
+        worldSceneRenderer.addRenderedBlocks(world.renderedBlocks, null);;
         worldSceneRenderer.setOnLookingAt(ray -> {});
 
         worldSceneRenderer.setAfterWorldRender(renderer -> {

From dd9455aac6b4dff9de9b0751749f9312c9cf5b5e Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Tue, 27 Aug 2024 19:01:59 -0700
Subject: [PATCH 41/64] spotless yet again

---
 .../multiblock/MultiblockControllerBase.java  | 49 +++++++------
 .../api/pattern/MultiblockShapeInfo.java      | 71 ++++++++++---------
 .../java/gregtech/api/util/GTUtility.java     | 11 ++-
 .../gregtech/api/util/RelativeDirection.java  |  5 +-
 .../handler/MultiblockPreviewRenderer.java    |  6 +-
 .../behaviors/MultiblockBuilderBehavior.java  | 38 +++++-----
 .../electric/MetaTileEntityCleanroom.java     |  3 +-
 .../electric/MetaTileEntityCrackingUnit.java  |  3 +-
 .../MetaTileEntityElectricBlastFurnace.java   | 10 +--
 .../electric/MetaTileEntityMultiSmelter.java  |  3 +-
 .../electric/MetaTileEntityPyrolyseOven.java  |  3 +-
 .../MetaTileEntityCentralMonitor.java         |  3 -
 .../MetaTileEntityMonitorScreen.java          |  8 ++-
 .../MultiblockInfoRecipeWrapper.java          |  9 ++-
 14 files changed, 119 insertions(+), 103 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index e51aae5ef63..c2d5a51745b 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -1,6 +1,5 @@
 package gregtech.api.metatileentity.multiblock;
 
-import gregtech.api.GTValues;
 import gregtech.api.GregTechAPI;
 import gregtech.api.block.VariantActiveBlock;
 import gregtech.api.capability.GregtechCapabilities;
@@ -17,7 +16,6 @@
 import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.pattern.pattern.PatternState;
 import gregtech.api.pipenet.tile.IPipeTile;
-import gregtech.api.unification.OreDictUnifier;
 import gregtech.api.unification.material.Material;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTLog;
@@ -318,13 +316,16 @@ public static TraceabilityPredicate heatingCoils() {
 
     /**
      * Ensures that all the blockstates that are in the map are the same type. Returns the type if all match, or null if
-     * they don't(or none match). Because this method sets an error in the structure, the pattern state will be INVALID_CACHED,
-     * so the pattern will not have its cache cleared, and the controller will not attempt to form the pattern again unless the cache is invalidated(either through code or through it failing).
+     * they don't(or none match). Because this method sets an error in the structure, the pattern state will be
+     * INVALID_CACHED,
+     * so the pattern will not have its cache cleared, and the controller will not attempt to form the pattern again
+     * unless the cache is invalidated(either through code or through it failing).
      * Example: {@code allSameType(GregTechAPI.HEATING_COILS, getSubstructure("MAIN"))}
      * 
-     * @param info  The info, such as GregTechAPI.HEATING_COILS
+     * @param info    The info, such as GregTechAPI.HEATING_COILS
      * @param pattern Pattern, used to get the cache. It will also be used to set the error.
-     * @param error The error, this is only set if the types don't match using {@link gregtech.api.pattern.PatternStringError}.
+     * @param error   The error, this is only set if the types don't match using
+     *                {@link gregtech.api.pattern.PatternStringError}.
      */
     public static  V allSameType(Object2ObjectMap info, IBlockPattern pattern, String error) {
         V type = null;
@@ -334,7 +335,8 @@ public static  V allSameType(Object2ObjectMap info, IBlockPat
                 if (type != state) {
                     if (type == null) type = state;
                     else {
-                        pattern.getPatternState().setError(new PatternStringError(BlockPos.fromLong(entry.getLongKey()), error));
+                        pattern.getPatternState()
+                                .setError(new PatternStringError(BlockPos.fromLong(entry.getLongKey()), error));
                         return null;
                     }
                 }
@@ -366,7 +368,8 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
                 if (frontFacing == EnumFacing.DOWN) rad += Math.PI;
             } else {
                 EnumFacing rotated = EnumFacing.UP.rotateAround(frontFacing.getAxis());
-                if (frontFacing.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE) rotated = rotated.getOpposite();
+                if (frontFacing.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE)
+                    rotated = rotated.getOpposite();
 
                 if (upwardsFacing == EnumFacing.DOWN) rad = Math.PI;
                 else if (upwardsFacing == rotated) rad = -Math.PI / 2;
@@ -374,7 +377,8 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
             }
 
             translation.translate(0.5, 0.5, 0.5);
-            translation.rotate(new Rotation(rad, frontFacing.getXOffset(), frontFacing.getYOffset(), frontFacing.getZOffset()));
+            translation.rotate(
+                    new Rotation(rad, frontFacing.getXOffset(), frontFacing.getYOffset(), frontFacing.getZOffset()));
             translation.translate(-0.5, -0.5, -0.5);
         }
     }
@@ -620,16 +624,16 @@ public NavigableSet getMultiblockParts() {
     public void readFromNBT(NBTTagCompound data) {
         super.readFromNBT(data);
         if (data.hasKey("UpwardsFacing")) {
-             this.upwardsFacing = EnumFacing.VALUES[data.getByte("UpwardsFacing")];
-             // old up facing is absolute when front facing is up/down
-             if (frontFacing.getAxis() != EnumFacing.Axis.Y) {
-                 this.upwardsFacing = switch (upwardsFacing) {
-                     case NORTH -> EnumFacing.UP;
-                     case SOUTH -> EnumFacing.DOWN;
-                     case EAST -> frontFacing.rotateYCCW();
-                     default -> frontFacing.rotateY();
-                 };
-             }
+            this.upwardsFacing = EnumFacing.VALUES[data.getByte("UpwardsFacing")];
+            // old up facing is absolute when front facing is up/down
+            if (frontFacing.getAxis() != EnumFacing.Axis.Y) {
+                this.upwardsFacing = switch (upwardsFacing) {
+                    case NORTH -> EnumFacing.UP;
+                    case SOUTH -> EnumFacing.DOWN;
+                    case EAST -> frontFacing.rotateYCCW();
+                    default -> frontFacing.rotateY();
+                };
+            }
         } else if (data.hasKey("UpFacing")) {
             this.upwardsFacing = EnumFacing.VALUES[data.getByte("UpFacing")];
         }
@@ -798,12 +802,13 @@ public List getMatchingShapes() {
      * {@link MultiblockControllerBase#getMatchingShapes()}, if not empty, to this. If getMatchingShapes is empty, uses
      * a default generated structure pattern, it's not very good which is why you should override this.
      *
-     * @param keyMap  A map for autobuild, or null if it is an in world or jei preview. Note that for in world and jei
-     *                previews you can return a singleton list(only the first element will be used anyway).
+     * @param keyMap A map for autobuild, or null if it is an in world or jei preview. Note that for in world and jei
+     *               previews you can return a singleton list(only the first element will be used anyway).
      */
     // todo add use for the keyMap with the multiblock builder
     // todo maybe add name arg for building substructures
-    public List getBuildableShapes(String substructureName, @Nullable Object2IntMap keyMap) {
+    public List getBuildableShapes(String substructureName,
+                                                        @Nullable Object2IntMap keyMap) {
         List infos = getMatchingShapes();
 
         // if there is no overriden getMatchingShapes() just return the default one
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index e4239c23ff7..5b3ec9c6cc6 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -1,7 +1,5 @@
 package gregtech.api.pattern;
 
-import com.github.bsideup.jabel.Desugar;
-
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
@@ -10,29 +8,24 @@
 import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
-import it.unimi.dsi.fastutil.chars.Char2IntMap;
-
-import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap;
-
-import it.unimi.dsi.fastutil.chars.Char2ObjectRBTreeMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-
 import net.minecraft.block.state.IBlockState;
-import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.init.Blocks;
 import net.minecraft.item.ItemStack;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
-
-import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
-import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
-
 import net.minecraft.util.text.TextComponentString;
 import net.minecraft.util.text.TextComponentTranslation;
 import net.minecraft.world.World;
 
+import com.github.bsideup.jabel.Desugar;
+import it.unimi.dsi.fastutil.chars.Char2IntMap;
+import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap;
+import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
+import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -56,7 +49,8 @@ public class MultiblockShapeInfo {
 
     /**
      * Unmodifiable reverse map from facing to relative direction, using DEFAULT_FRONT and DEFAULT_UP.
-     * For example, EnumFacing.NORTH -> RelativeDirection.BACK, since with the defaults the relative back of the controller is north.
+     * For example, EnumFacing.NORTH -> RelativeDirection.BACK, since with the defaults the relative back of the
+     * controller is north.
      */
     public static final Map FACING_MAP;
     protected final PatternAisle[] aisles;
@@ -84,11 +78,14 @@ public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symb
 
     /**
      * Builds the given multiblock. The world is gotten from the player's world variable.
-     * @param src The multiblock in world to build from.
-     * @param player The player autobuilding and whos inventory will be used.
-     * @param partial If true, if the player does not have enough materials, the multiblock will be built as much as possible until they run out of a block to place.
+     * 
+     * @param src     The multiblock in world to build from.
+     * @param player  The player autobuilding and whos inventory will be used.
+     * @param partial If true, if the player does not have enough materials, the multiblock will be built as much as
+     *                possible until they run out of a block to place.
      *                If false, the autobuild will only succeed if the player can build the entire multiblock at once.
-     * @return Whether the autobuild was successful. If partial is false, returns true only if the entire multiblock was build. If partial is true, returns false only if no blocks were placed.
+     * @return Whether the autobuild was successful. If partial is false, returns true only if the entire multiblock was
+     *         build. If partial is true, returns false only if no blocks were placed.
      */
     public boolean autoBuild(MultiblockControllerBase src, EntityPlayer player, boolean partial) {
         World world = player.world;
@@ -119,13 +116,15 @@ public boolean autoBuild(MultiblockControllerBase src, EntityPlayer player, bool
     }
 
     /**
-     * Gets a map of where blocks should be placed, note that the controller is expected to be front facing SOUTH(and up facing
+     * Gets a map of where blocks should be placed, note that the controller is expected to be front facing SOUTH(and up
+     * facing
      * UP).
      * Unlike BlockPattern, the first char in the first string in the first aisle always starts at the origin, instead
      * of being relative to the controller.
      * The passed in map is populated.
      *
-     * @return A pos that can be looked up in the given map to find the controller that has the same class as the argument.
+     * @return A pos that can be looked up in the given map to find the controller that has the same class as the
+     *         argument.
      */
     // this is currently here so that multiblocks can have other multiblocks in their structure without messing
     // everything up
@@ -133,7 +132,8 @@ public BlockPos getMap(MultiblockControllerBase src, BlockPos start, Map clazz) {
         char c = 'S';
         for (Char2ObjectMap.Entry entry : symbols.char2ObjectEntrySet()) {
-            if (entry.getValue().getTileEntity() instanceof MetaTileEntityHolder holder && holder.getMetaTileEntity() instanceof MultiblockControllerBase controller && controller.getClass() == clazz) {
+            if (entry.getValue().getTileEntity() instanceof MetaTileEntityHolder holder &&
+                    holder.getMetaTileEntity() instanceof MultiblockControllerBase controller &&
+                    controller.getClass() == clazz) {
                 c = entry.getCharKey();
                 break;
             }
@@ -257,7 +262,6 @@ protected Char2IntMap getChars() {
     }
 
     public int getUpCount(EnumFacing frontFacing, EnumFacing upFacing) {
-
         int index = 0;
         for (int i = 0; i < 3; i++) {
             EnumFacing facing = directions[i].getRelativeFacing(frontFacing, upFacing, false);
@@ -332,7 +336,8 @@ public Builder aisle(String... data) {
 
         public Builder where(char symbol, BlockInfo value) {
             if (symbolMap.containsKey(symbol)) {
-                GTLog.logger.warn("Tried to put symbol " + symbol + " when it was already registered in the as a dot block! Ignoring the call.");
+                GTLog.logger.warn("Tried to put symbol " + symbol +
+                        " when it was already registered in the as a dot block! Ignoring the call.");
                 return this;
             }
 
@@ -371,13 +376,15 @@ public Builder where(char symbol, Supplier partSupplier, EnumFacing frontSide
 
         /**
          * Adds a dot block to represent the char.
+         * 
          * @param symbol The symbol in the pattern to put.
-         * @param dot The amount of dots on the block, 0-15
-         * @param lang The lang to show for the block, pass in the lang key and it will format.
+         * @param dot    The amount of dots on the block, 0-15
+         * @param lang   The lang to show for the block, pass in the lang key and it will format.
          */
         public Builder dot(char symbol, int dot, String lang) {
             if (symbolMap.containsKey(symbol)) {
-                GTLog.logger.warn("Tried to put symbol " + symbol + " as dot block " + dot + " when it was already registered in the symbol map! Ignoring the call.");
+                GTLog.logger.warn("Tried to put symbol " + symbol + " as dot block " + dot +
+                        " when it was already registered in the symbol map! Ignoring the call.");
                 return this;
             }
 
diff --git a/src/main/java/gregtech/api/util/GTUtility.java b/src/main/java/gregtech/api/util/GTUtility.java
index 9716664eb10..1e331752f24 100644
--- a/src/main/java/gregtech/api/util/GTUtility.java
+++ b/src/main/java/gregtech/api/util/GTUtility.java
@@ -20,8 +20,6 @@
 import gregtech.api.unification.OreDictUnifier;
 import gregtech.api.unification.ore.OrePrefix;
 
-import gregtech.integration.jei.multiblock.MultiblockInfoRecipeWrapper;
-
 import net.minecraft.block.Block;
 import net.minecraft.block.BlockRedstoneWire;
 import net.minecraft.block.BlockSnow;
@@ -659,7 +657,8 @@ public static ItemStack toItem(IBlockState state, int amount) {
     }
 
     /**
-     * Converts the block at pos in the world to an item. First uses the MTE method if the block is a mte, then uses pick block, then uses the first of the block drops, then returns EMPTY.
+     * Converts the block at pos in the world to an item. First uses the MTE method if the block is a mte, then uses
+     * pick block, then uses the first of the block drops, then returns EMPTY.
      */
     @NotNull
     public static ItemStack toItem(World world, BlockPos pos) {
@@ -955,7 +954,8 @@ public double getAsDouble() {
     }
 
     /**
-     * Gets the cross product of 2 facings. Null is returned if the result is a zero vector(facings are on the same axis).
+     * Gets the cross product of 2 facings. Null is returned if the result is a zero vector(facings are on the same
+     * axis).
      */
     @Nullable
     public static EnumFacing cross(EnumFacing a, EnumFacing b) {
@@ -964,8 +964,7 @@ public static EnumFacing cross(EnumFacing a, EnumFacing b) {
         return EnumFacing.getFacingFromVector(
                 a.getYOffset() * b.getZOffset() - a.getZOffset() * b.getYOffset(),
                 a.getZOffset() * b.getXOffset() - a.getXOffset() * b.getZOffset(),
-                a.getXOffset() * b.getYOffset() - a.getYOffset() * b.getXOffset()
-        );
+                a.getXOffset() * b.getYOffset() - a.getYOffset() * b.getXOffset());
     }
 
     /**
diff --git a/src/main/java/gregtech/api/util/RelativeDirection.java b/src/main/java/gregtech/api/util/RelativeDirection.java
index dc0cb60efcf..b3fd9a763ff 100644
--- a/src/main/java/gregtech/api/util/RelativeDirection.java
+++ b/src/main/java/gregtech/api/util/RelativeDirection.java
@@ -1,7 +1,6 @@
 package gregtech.api.util;
 
 import net.minecraft.util.EnumFacing;
-import net.minecraft.util.EnumFacing.Axis;
 import net.minecraft.util.math.BlockPos;
 
 import java.util.function.BinaryOperator;
@@ -53,7 +52,9 @@ public EnumFacing getRelativeFacing(EnumFacing frontFacing, EnumFacing upFacing)
     }
 
     public EnumFacing getRelativeFacing(EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped) {
-        return (isFlipped && (this == LEFT || this == RIGHT)) ? getRelativeFacing(frontFacing, upwardsFacing).getOpposite() : getRelativeFacing(frontFacing, upwardsFacing);
+        return (isFlipped && (this == LEFT || this == RIGHT)) ?
+                getRelativeFacing(frontFacing, upwardsFacing).getOpposite() :
+                getRelativeFacing(frontFacing, upwardsFacing);
     }
 
     public Function getSorter(EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped) {
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index 9f25e922f99..bfaa395b377 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -78,7 +78,8 @@ public static void renderWorldLastEvent(RenderWorldLastEvent event) {
         }
     }
 
-    public static void renderMultiBlockPreview(MultiblockControllerBase controller, EntityPlayer player, long durTimeMillis) {
+    public static void renderMultiBlockPreview(MultiblockControllerBase controller, EntityPlayer player,
+                                               long durTimeMillis) {
         if (!controller.getPos().equals(mbpPos)) {
             layer = 0;
         } else {
@@ -111,7 +112,8 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas
                                               int layer) {
         Map blockMap = new HashMap<>();
         BlockPos controllerPos = shapeInfo.getMap(controllerBase, new BlockPos(0, 128, 0), blockMap);
-        MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap.get(controllerPos).getTileEntity()).getMetaTileEntity();
+        MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap
+                .get(controllerPos).getTileEntity()).getMetaTileEntity();
 
         EnumFacing facing = controllerBase.getFrontFacing();
         EnumFacing upwardsFacing = controllerBase.getUpwardsFacing();
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index d5bcc52ea74..141f98e03f9 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -1,15 +1,5 @@
 package gregtech.common.items.behaviors;
 
-import com.cleanroommc.modularui.api.drawable.IKey;
-import com.cleanroommc.modularui.factory.HandGuiData;
-import com.cleanroommc.modularui.screen.ModularPanel;
-import com.cleanroommc.modularui.value.sync.GuiSyncManager;
-import com.cleanroommc.modularui.value.sync.StringSyncValue;
-import com.cleanroommc.modularui.widgets.SortableListWidget;
-import com.cleanroommc.modularui.widgets.layout.Row;
-import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget;
-import com.github.bsideup.jabel.Desugar;
-
 import gregtech.api.items.gui.ItemUIFactory;
 import gregtech.api.items.metaitem.stats.IItemBehaviour;
 import gregtech.api.metatileentity.MetaTileEntity;
@@ -21,8 +11,6 @@
 import gregtech.api.pattern.PatternError;
 import gregtech.api.util.GTUtility;
 
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-
 import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.item.Item;
@@ -40,6 +28,16 @@
 import net.minecraft.util.text.TextFormatting;
 import net.minecraft.world.World;
 
+import com.cleanroommc.modularui.api.drawable.IKey;
+import com.cleanroommc.modularui.factory.HandGuiData;
+import com.cleanroommc.modularui.screen.ModularPanel;
+import com.cleanroommc.modularui.value.sync.GuiSyncManager;
+import com.cleanroommc.modularui.value.sync.StringSyncValue;
+import com.cleanroommc.modularui.widgets.SortableListWidget;
+import com.cleanroommc.modularui.widgets.layout.Row;
+import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget;
+import com.github.bsideup.jabel.Desugar;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import org.jetbrains.annotations.NotNull;
 
@@ -48,7 +46,9 @@
 import java.util.stream.IntStream;
 
 public class MultiblockBuilderBehavior implements IItemBehaviour, ItemUIFactory {
+
     public static final int MAX_KEYS = 16;
+
     @Override
     public ActionResult onItemRightClick(World world, EntityPlayer player, EnumHand hand) {
         ItemStack heldItem = player.getHeldItem(hand);
@@ -186,13 +186,13 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
             // todo full substructure debug and autobuild support
             // If not sneaking, try to show structure debug info (if any) in chat.
             if (!multiblock.isStructureFormed("MAIN")) {
-                 PatternError error = multiblock.getSubstructure("MAIN").getPatternState().getError();
-                 if (error != null) {
-                     player.sendMessage(
-                     new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header"));
-                     player.sendMessage(new TextComponentString(error.getErrorInfo()));
-                     return EnumActionResult.SUCCESS;
-                 }
+                PatternError error = multiblock.getSubstructure("MAIN").getPatternState().getError();
+                if (error != null) {
+                    player.sendMessage(
+                            new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header"));
+                    player.sendMessage(new TextComponentString(error.getErrorInfo()));
+                    return EnumActionResult.SUCCESS;
+                }
             }
             player.sendMessage(new TextComponentTranslation("gregtech.multiblock.pattern.no_errors")
                     .setStyle(new Style().setColor(TextFormatting.GREEN)));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index be895422e8e..09994f86b94 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -144,7 +144,8 @@ protected void formStructure(String name) {
         renderingAABB = false;
         writeCustomData(GregtechDataCodes.RENDER_UPDATE, buf -> buf.writeBoolean(false));
 
-        ICleanroomFilter type = allSameType(GregTechAPI.CLEANROOM_FILTERS, getSubstructure("MAIN"), "gregtech.multiblock.pattern.error.filters");
+        ICleanroomFilter type = allSameType(GregTechAPI.CLEANROOM_FILTERS, getSubstructure("MAIN"),
+                "gregtech.multiblock.pattern.error.filters");
         if (type == null) {
             invalidateStructure(name);
             return;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
index d778d79d9e5..facda2238bf 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
@@ -124,7 +124,8 @@ protected ICubeRenderer getFrontOverlay() {
     @Override
     protected void formStructure(String name) {
         super.formStructure(name);
-        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name), "gregtech.multiblock.pattern.error.coils");
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name),
+                "gregtech.multiblock.pattern.error.coils");
         if (type == null) {
             invalidateStructure(name);
         } else {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index bc3120d9ea5..839d73b8f3f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -47,7 +47,6 @@
 import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.List;
 
 import static gregtech.api.util.RelativeDirection.*;
@@ -93,7 +92,8 @@ protected void addDisplayText(List textList) {
     @Override
     protected void formStructure(String name) {
         super.formStructure(name);
-        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name), "gregtech.multiblock.pattern.error.coils");
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name),
+                "gregtech.multiblock.pattern.error.coils");
         if (type == null) {
             invalidateStructure(name);
         } else {
@@ -196,9 +196,9 @@ public List getMatchingShapes() {
                         MetaBlocks.METAL_CASING.getState(MetalCasingType.INVAR_HEATPROOF), EnumFacing.NORTH)
                 .dot('C', 1, "gregtech.material.tungsten_carbide");
         shapeInfo.add(builder.build());
-//        GregTechAPI.HEATING_COILS.entrySet().stream()
-//                .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
-//                .forEach(entry -> shapeInfo.add(builder.shallowCopy().where('C', entry.getKey()).build()));
+        // GregTechAPI.HEATING_COILS.entrySet().stream()
+        // .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
+        // .forEach(entry -> shapeInfo.add(builder.shallowCopy().where('C', entry.getKey()).build()));
         return shapeInfo;
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
index a8d15374f47..6c93be32502 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
@@ -106,7 +106,8 @@ protected void addDisplayText(List textList) {
     @Override
     protected void formStructure(String name) {
         super.formStructure(name);
-        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name), "gregtech.multiblock.pattern.error.coils");
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name),
+                "gregtech.multiblock.pattern.error.coils");
         if (type == null) {
             invalidateStructure(name);
         } else {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
index ecfe00e2766..558e2287d20 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
@@ -90,7 +90,8 @@ protected ICubeRenderer getFrontOverlay() {
     @Override
     protected void formStructure(String name) {
         super.formStructure(name);
-        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name), "gregtech.multiblock.pattern.error.coils");
+        IHeatingCoilBlockStats type = allSameType(GregTechAPI.HEATING_COILS, getSubstructure(name),
+                "gregtech.multiblock.pattern.error.coils");
         if (type == null) {
             invalidateStructure(name);
         } else {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index 7b3d31814eb..60a5610d3f6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -16,14 +16,11 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
-import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.pipenet.tile.IPipeTile;
 import gregtech.api.pipenet.tile.TileEntityPipeBase;
 import gregtech.api.util.FacingPos;
-import gregtech.api.util.GTLog;
-import gregtech.api.util.RelativeDirection;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
 import gregtech.client.utils.RenderUtil;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java
index 78db95c4da6..8321ee70cc2 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityMonitorScreen.java
@@ -209,8 +209,10 @@ public int getX() {
         MultiblockControllerBase controller = getController();
         if (controller == null) return -1;
 
-        EnumFacing.Axis lrAxis = RIGHT.getRelativeFacing(controller.getFrontFacing(), controller.getUpwardsFacing()).getAxis();
-        return Math.abs(GreggyBlockPos.getAxis(getPos(), lrAxis) - GreggyBlockPos.getAxis(controller.getPos(), lrAxis)) - 1;
+        EnumFacing.Axis lrAxis = RIGHT.getRelativeFacing(controller.getFrontFacing(), controller.getUpwardsFacing())
+                .getAxis();
+        return Math.abs(
+                GreggyBlockPos.getAxis(getPos(), lrAxis) - GreggyBlockPos.getAxis(controller.getPos(), lrAxis)) - 1;
     }
 
     public int getY() {
@@ -220,7 +222,7 @@ public int getY() {
         EnumFacing.Axis udAxis = controller.getUpwardsFacing().getAxis();
         // top left corner of screen
         GreggyBlockPos pos = new GreggyBlockPos(controller.getPos());
-        pos.offset(controller.getUpwardsFacing(),  ((MetaTileEntityCentralMonitor) controller).height - 2);
+        pos.offset(controller.getUpwardsFacing(), ((MetaTileEntityCentralMonitor) controller).height - 2);
         pos.offset(LEFT.getRelativeFacing(controller.getFrontFacing(), controller.getUpwardsFacing()));
 
         return Math.abs(pos.get(udAxis) - GreggyBlockPos.getAxis(getPos(), udAxis));
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index fdbcacd9c6f..ead5900a871 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -11,7 +11,6 @@
 import gregtech.api.pattern.pattern.PatternState;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTUtility;
-import gregtech.api.util.GregFakePlayer;
 import gregtech.api.util.ItemStackHashStrategy;
 import gregtech.client.renderer.scene.ImmediateWorldSceneRenderer;
 import gregtech.client.renderer.scene.WorldSceneRenderer;
@@ -35,11 +34,9 @@
 import net.minecraft.item.ItemStack;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
-import net.minecraft.util.NonNullList;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.math.RayTraceResult;
-import net.minecraft.util.math.Vec3d;
 import net.minecraft.util.text.TextFormatting;
 import net.minecraft.world.World;
 import net.minecraftforge.fml.relauncher.Side;
@@ -531,9 +528,11 @@ private static Collection gatherStructureBlocks(World world, @NotNull
     // todo substructure support
     private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @NotNull Set parts) {
         Map blockMap = new HashMap<>();
-        // absolutely dog way of doing this, just setting the center at 128 so that both patterns going down and up can work
+        // absolutely dog way of doing this, just setting the center at 128 so that both patterns going down and up can
+        // work
         BlockPos controllerPos = shapeInfo.getMap(this.controller, new BlockPos(0, 128, 0), blockMap);
-        MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap.get(controllerPos).getTileEntity()).getMetaTileEntity();
+        MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap
+                .get(controllerPos).getTileEntity()).getMetaTileEntity();
 
         TrackedDummyWorld world = new TrackedDummyWorld();
         ImmediateWorldSceneRenderer worldSceneRenderer = new ImmediateWorldSceneRenderer(world);

From 088d6ebd140ef603fdc971596357b1565e871233 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sat, 7 Sep 2024 18:43:10 -0700
Subject: [PATCH 42/64] fix conflicts, and part 1 of autobuild

---
 .../multiblock/MultiblockControllerBase.java  |  2 +-
 .../api/pattern/pattern/BlockPattern.java     | 25 ++++--
 .../pattern/pattern/ExpandablePattern.java    |  2 +-
 .../api/pattern/pattern/IBlockPattern.java    |  5 +-
 .../java/gregtech/api/util/GTUtility.java     | 10 +--
 .../gregtech/api/util/RelativeDirection.java  | 90 +------------------
 .../handler/MultiblockPreviewRenderer.java    |  2 +-
 .../behaviors/MultiblockBuilderBehavior.java  |  7 +-
 .../MetaTileEntityElectricBlastFurnace.java   |  4 +-
 .../electric/MetaTileEntityFusionReactor.java |  2 +-
 .../multi/electric/MetaTileEntityHPCA.java    |  2 +-
 .../MetaTileEntityLargeChemicalReactor.java   |  2 +-
 .../MetaTileEntityProcessingArray.java        |  4 -
 .../MetaTileEntityResearchStation.java        |  2 +-
 .../MetaTileEntityLargeCombustionEngine.java  |  4 +-
 .../MultiblockInfoRecipeWrapper.java          |  2 +-
 16 files changed, 45 insertions(+), 120 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index c2d5a51745b..db8dd0b10c9 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -816,7 +816,7 @@ public List getBuildableShapes(String substructureName,
             // for jei and stuff
             if (getSubstructure(substructureName) == null) createStructurePatterns();
 
-            MultiblockShapeInfo info = getSubstructure(substructureName).getDefaultShape();
+            MultiblockShapeInfo info = getSubstructure(substructureName).getDefaultShape(false);
             if (info == null) return Collections.emptyList();
             return Collections.singletonList(info);
         }
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 05a56109579..6b5ea02536f 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -1,5 +1,6 @@
 package gregtech.api.pattern.pattern;
 
+import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
@@ -20,6 +21,7 @@
 import it.unimi.dsi.fastutil.chars.Char2IntMap;
 import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
+import it.unimi.dsi.fastutil.chars.Char2ObjectMaps;
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
@@ -303,7 +305,7 @@ public int getRepetitionCount(int aisleI) {
     }
 
     @Override
-    public MultiblockShapeInfo getDefaultShape() {
+    public MultiblockShapeInfo getDefaultShape(boolean skipMTEs) {
         // for each symbol, which simple predicate is being used
         // this advances whenever a minimum has been satisfied(if any), or a maximum has been reached(if any)
         // preview counts are treated as exactly that many
@@ -317,7 +319,7 @@ public MultiblockShapeInfo getDefaultShape() {
 
         List pattern = new ArrayList<>(dimensions[0]);
 
-        // 0 is reserved as a null for char
+        // 0 is reserved for air
         char currentChar = 1;
         int aisleOffset = 0;
 
@@ -338,9 +340,20 @@ public MultiblockShapeInfo getDefaultShape() {
 
                         // cache all the BlockInfo, so that we only need to get it once per simple predicate
                         if (!infos.containsKey(simple)) {
-                            BlockInfo info = simple.candidates.get()[0];
+                            BlockInfo[] blockInfos = simple.candidates.get();
+                            int pointer = 0;
+                            if (blockInfos.length != 1 && skipMTEs) {
+                                // move the pointer to the last pos where there is no MTE
+                                try {
+                                    while ((blockInfos[pointer].getTileEntity() instanceof MetaTileEntityHolder))
+                                        pointer++;
+                                } catch (ArrayIndexOutOfBoundsException e) {
+                                    // every candidate is a MTE, just do first one
+                                    pointer = 0;
+                                }
+                            }
                             infos.put(simple, currentChar);
-                            candidates.put(currentChar, info);
+                            candidates.put(currentChar, blockInfos[pointer]);
                             currentChar++;
                         }
 
@@ -401,7 +414,7 @@ public MultiblockShapeInfo getDefaultShape() {
                             int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
                             if (newIndex >= predicate.common.size())
                                 GTLog.logger.warn("Failed to generate default structure pattern.",
-                                        new IllegalStateException());
+                                        new Throwable());
                             next = predicate.common.get(newIndex);
                             globalCount = globalCache.getInt(next);
                             layerCount = layerCache.getInt(next);
@@ -429,7 +442,7 @@ public MultiblockShapeInfo getDefaultShape() {
         }
 
         return new MultiblockShapeInfo(pattern.stream().map(a -> new PatternAisle(1, a)).toArray(PatternAisle[]::new),
-                candidates, new Char2ObjectOpenHashMap<>(), directions);
+                candidates, Char2ObjectMaps.emptyMap(), directions);
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index 68f4ea40c33..e1528a1ab7c 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -173,7 +173,7 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
     }
 
     @Override
-    public MultiblockShapeInfo getDefaultShape() {
+    public MultiblockShapeInfo getDefaultShape(boolean skipMTEs) {
         // todo undo
         return null;
     }
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index 9e9f1b3b31b..dc59f51344b 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -50,9 +50,12 @@ boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
     /**
      * Gets the default shape, if the multiblock does not specify one. Return null to represent the default shape does
      * not exist.
+     * 
+     * @param skipMTEs If enabled, does not place MTEs at positions unless all candidates are MTEs, in which case
+     *                 selects the first candidate.
      */
     @Nullable
-    MultiblockShapeInfo getDefaultShape();
+    MultiblockShapeInfo getDefaultShape(boolean skipMTEs);
 
     /**
      * Gets the internal pattern state, you should use the one returned from
diff --git a/src/main/java/gregtech/api/util/GTUtility.java b/src/main/java/gregtech/api/util/GTUtility.java
index 1e331752f24..497de86808c 100644
--- a/src/main/java/gregtech/api/util/GTUtility.java
+++ b/src/main/java/gregtech/api/util/GTUtility.java
@@ -670,18 +670,19 @@ public static ItemStack toItem(World world, BlockPos pos) {
         // first check if the block is a GT machine
         TileEntity tileEntity = world.getTileEntity(pos);
         if (tileEntity instanceof IGregTechTileEntity) {
-            stack = ((IGregTechTileEntity) tileEntity).getMetaTileEntity().getStackForm();
+            stack =  ((IGregTechTileEntity) tileEntity).getMetaTileEntity().getStackForm();
         }
 
         if (stack.isEmpty()) {
             // first, see what the block has to say for itself before forcing it to use a particular meta value
             stack = block.getPickBlock(state, new RayTraceResult(Vec3d.ZERO, EnumFacing.UP, pos), world, pos,
                     new GregFakePlayer(world));
-        }
+        } else return stack;
+
         if (stack.isEmpty()) {
             // try the default itemstack constructor if we're not a GT machine
             stack = GTUtility.toItem(state);
-        }
+        } else return stack;
 
         if (stack.isEmpty()) {
             // add the first of the block's drops if the others didn't work
@@ -690,7 +691,7 @@ public static ItemStack toItem(World world, BlockPos pos) {
             if (!list.isEmpty()) {
                 return list.get(0);
             }
-        }
+        } else return stack;
 
         return ItemStack.EMPTY;
     }
@@ -957,7 +958,6 @@ public double getAsDouble() {
      * Gets the cross product of 2 facings. Null is returned if the result is a zero vector(facings are on the same
      * axis).
      */
-    @Nullable
     public static EnumFacing cross(EnumFacing a, EnumFacing b) {
         if (a.getAxis() == b.getAxis()) return null;
 
diff --git a/src/main/java/gregtech/api/util/RelativeDirection.java b/src/main/java/gregtech/api/util/RelativeDirection.java
index b3fd9a763ff..608db72674b 100644
--- a/src/main/java/gregtech/api/util/RelativeDirection.java
+++ b/src/main/java/gregtech/api/util/RelativeDirection.java
@@ -40,8 +40,7 @@ public RelativeDirection getOpposite() {
      * Gets the ordinal of the RelativeDirection opposite to this. UP <-> DOWN, LEFT <-> RIGHT, FRONT <-> BACK.
      */
     public int oppositeOrdinal() {
-        // floor to nearest even + adjustment
-        return (ordinal() / 2) * 2 + (1 - ordinal() % 2);
+        return ordinal() ^ 1;
     }
 
     public EnumFacing getRelativeFacing(EnumFacing frontFacing, EnumFacing upFacing) {
@@ -123,91 +122,4 @@ public static BlockPos offsetPos(BlockPos pos, EnumFacing frontFacing, EnumFacin
 
         return pos.add(oX, oY, oZ);
     }
-
-    /**
-     * Offset a BlockPos relatively in any direction by any amount. Pass negative values to offset down, right or
-     * backwards.
-     */
-    public static BlockPos setActualRelativeOffset(int x, int y, int z, EnumFacing facing, EnumFacing upwardsFacing,
-                                                   boolean isFlipped, RelativeDirection[] structureDir) {
-        int[] c0 = new int[] { x, y, z }, c1 = new int[3];
-        if (facing == EnumFacing.UP || facing == EnumFacing.DOWN) {
-            EnumFacing of = facing == EnumFacing.DOWN ? upwardsFacing : upwardsFacing.getOpposite();
-            for (int i = 0; i < 3; i++) {
-                switch (structureDir[i].getActualFacing(of)) {
-                    case UP -> c1[1] = c0[i];
-                    case DOWN -> c1[1] = -c0[i];
-                    case WEST -> c1[0] = -c0[i];
-                    case EAST -> c1[0] = c0[i];
-                    case NORTH -> c1[2] = -c0[i];
-                    case SOUTH -> c1[2] = c0[i];
-                }
-            }
-            int xOffset = upwardsFacing.getXOffset();
-            int zOffset = upwardsFacing.getZOffset();
-            int tmp;
-            if (xOffset == 0) {
-                tmp = c1[2];
-                c1[2] = zOffset > 0 ? c1[1] : -c1[1];
-                c1[1] = zOffset > 0 ? -tmp : tmp;
-            } else {
-                tmp = c1[0];
-                c1[0] = xOffset > 0 ? c1[1] : -c1[1];
-                c1[1] = xOffset > 0 ? -tmp : tmp;
-            }
-            if (isFlipped) {
-                if (upwardsFacing == EnumFacing.NORTH || upwardsFacing == EnumFacing.SOUTH) {
-                    c1[0] = -c1[0]; // flip X-axis
-                } else {
-                    c1[2] = -c1[2]; // flip Z-axis
-                }
-            }
-        } else {
-            for (int i = 0; i < 3; i++) {
-                switch (structureDir[i].getActualFacing(facing)) {
-                    case UP -> c1[1] = c0[i];
-                    case DOWN -> c1[1] = -c0[i];
-                    case WEST -> c1[0] = -c0[i];
-                    case EAST -> c1[0] = c0[i];
-                    case NORTH -> c1[2] = -c0[i];
-                    case SOUTH -> c1[2] = c0[i];
-                }
-            }
-            if (upwardsFacing == EnumFacing.WEST || upwardsFacing == EnumFacing.EAST) {
-                int xOffset = upwardsFacing == EnumFacing.WEST ? facing.rotateY().getXOffset() :
-                        facing.rotateY().getOpposite().getXOffset();
-                int zOffset = upwardsFacing == EnumFacing.WEST ? facing.rotateY().getZOffset() :
-                        facing.rotateY().getOpposite().getZOffset();
-                int tmp;
-                if (xOffset == 0) {
-                    tmp = c1[2];
-                    c1[2] = zOffset > 0 ? -c1[1] : c1[1];
-                    c1[1] = zOffset > 0 ? tmp : -tmp;
-                } else {
-                    tmp = c1[0];
-                    c1[0] = xOffset > 0 ? -c1[1] : c1[1];
-                    c1[1] = xOffset > 0 ? tmp : -tmp;
-                }
-            } else if (upwardsFacing == EnumFacing.SOUTH) {
-                c1[1] = -c1[1];
-                if (facing.getXOffset() == 0) {
-                    c1[0] = -c1[0];
-                } else {
-                    c1[2] = -c1[2];
-                }
-            }
-            if (isFlipped) {
-                if (upwardsFacing == EnumFacing.NORTH || upwardsFacing == EnumFacing.SOUTH) {
-                    if (facing == EnumFacing.NORTH || facing == EnumFacing.SOUTH) {
-                        c1[0] = -c1[0]; // flip X-axis
-                    } else {
-                        c1[2] = -c1[2]; // flip Z-axis
-                    }
-                } else {
-                    c1[1] = -c1[1]; // flip Y-axis
-                }
-            }
-        }
-        return new BlockPos(c1[0], c1[1], c1[2]);
-    }
 }
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index bfaa395b377..9cbbb62086a 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -91,7 +91,7 @@ public static void renderMultiBlockPreview(MultiblockControllerBase controller,
         mbpEndTime = System.currentTimeMillis() + durTimeMillis;
         opList = GLAllocation.generateDisplayLists(1); // allocate op list
         GlStateManager.glNewList(opList, GL11.GL_COMPILE);
-        List shapes = controller.getMatchingShapes();
+        List shapes = controller.getBuildableShapes("MAIN", null);
         if (!shapes.isEmpty()) {
             renderControllerInList(controller, shapes.get(0), layer);
             shapes.get(0).sendDotMessage(player);
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index 141f98e03f9..b27f93dc12d 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -31,7 +31,7 @@
 import com.cleanroommc.modularui.api.drawable.IKey;
 import com.cleanroommc.modularui.factory.HandGuiData;
 import com.cleanroommc.modularui.screen.ModularPanel;
-import com.cleanroommc.modularui.value.sync.GuiSyncManager;
+import com.cleanroommc.modularui.value.sync.PanelSyncManager;
 import com.cleanroommc.modularui.value.sync.StringSyncValue;
 import com.cleanroommc.modularui.widgets.SortableListWidget;
 import com.cleanroommc.modularui.widgets.layout.Row;
@@ -41,6 +41,7 @@
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -113,7 +114,7 @@ protected static void setKey(int id, String key, String value, ItemStack target)
     }
 
     @Override
-    public ModularPanel buildUI(HandGuiData guiData, GuiSyncManager guiSyncManager) {
+    public ModularPanel buildUI(HandGuiData guiData, PanelSyncManager guiSyncManager) {
         initNBT(guiData.getUsedItemStack());
 
         StringSyncValue[] keyValues = new StringSyncValue[MAX_KEYS];
@@ -178,7 +179,7 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
             if (!multiblock.isStructureFormed("MAIN")) {
-                multiblock.getBuildableShapes("MAIN", getMap(player.getHeldItem(hand)));
+                multiblock.getBuildableShapes("MAIN", getMap(player.getHeldItem(hand))).get(0).getMap(multiblock, new BlockPos(0, 128, 0), new HashMap<>());
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index 839d73b8f3f..1bfb924c941 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -178,8 +178,8 @@ public SoundEvent getBreakdownSound() {
 
     @Override
     public List getMatchingShapes() {
-        ArrayList shapeInfo = new ArrayList<>();
-        MultiblockShapeInfo.Builder builder = MultiblockShapeInfo.builder(RIGHT, DOWN, FRONT)
+        List shapeInfo = new ArrayList<>();
+        MultiblockShapeInfo.Builder builder = MultiblockShapeInfo.builder()
                 .aisle("EEM", "CCC", "CCC", "XXX")
                 .aisle("FXD", "C#C", "C#C", "XHX")
                 .aisle("ISO", "CCC", "CCC", "XXX")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
index 34aa2e49219..54a68b95ee9 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
@@ -162,7 +162,7 @@ protected BlockPattern createStructurePattern() {
     public List getMatchingShapes() {
         List shapeInfos = new ArrayList<>();
 
-        MultiblockShapeInfo.Builder baseBuilder = MultiblockShapeInfo.builder(RIGHT, DOWN, FRONT)
+        MultiblockShapeInfo.Builder baseBuilder = MultiblockShapeInfo.builder()
                 .aisle("###############", "######WGW######", "###############")
                 .aisle("######DCD######", "####GG###GG####", "######UCU######")
                 .aisle("####CC###CC####", "###w##EGE##s###", "####CC###CC####")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
index 9b8dcd9ba05..9dfe5939489 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
@@ -230,7 +230,7 @@ private void consumeEnergy() {
     @Override
     public List getMatchingShapes() {
         List shapeInfo = new ArrayList<>();
-        MultiblockShapeInfo.Builder builder = MultiblockShapeInfo.builder(RIGHT, DOWN, FRONT)
+        MultiblockShapeInfo.Builder builder = MultiblockShapeInfo.builder()
                 .aisle("AA", "EC", "MC", "HC", "AA")
                 .aisle("VA", "6V", "3V", "0V", "VA")
                 .aisle("VA", "7V", "4V", "1V", "VA")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
index 63d09fc1a51..91985ceec83 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
@@ -71,7 +71,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     public List getMatchingShapes() {
         ArrayList shapeInfo = new ArrayList<>();
-        MultiblockShapeInfo.Builder baseBuilder = MultiblockShapeInfo.builder(RIGHT, DOWN, FRONT)
+        MultiblockShapeInfo.Builder baseBuilder = MultiblockShapeInfo.builder()
                 .where('S', MetaTileEntities.LARGE_CHEMICAL_REACTOR, EnumFacing.SOUTH)
                 .where('X', MetaBlocks.METAL_CASING.getState(BlockMetalCasing.MetalCasingType.PTFE_INERT_CASING))
                 .where('P',
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
index 86a261b4916..35beb44f6d6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
@@ -9,7 +9,6 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
-import gregtech.api.metatileentity.multiblock.*;
 import gregtech.api.metatileentity.multiblock.DummyCleanroom;
 import gregtech.api.metatileentity.multiblock.ICleanroomProvider;
 import gregtech.api.metatileentity.multiblock.ICleanroomReceiver;
@@ -17,9 +16,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.BlockPattern;
-import gregtech.api.pattern.FactoryBlockPattern;
-import gregtech.api.pattern.PatternMatchContext;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
index 6c5ecf6bd73..1c951296e28 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
@@ -144,7 +144,7 @@ protected BlockPattern createStructurePattern() {
 
     @Override
     public List getMatchingShapes() {
-        return Collections.singletonList(MultiblockShapeInfo.builder(RIGHT, DOWN, FRONT)
+        return Collections.singletonList(MultiblockShapeInfo.builder()
                 .aisle("XXX", "VVV", "POP", "PEP", "PMP", "VVV", "XXX")
                 .aisle("XXX", "VAV", "AAA", "AAA", "AAA", "VAV", "XXX")
                 .aisle("XXX", "VAV", "XAX", "XSX", "XAX", "VAV", "XXX")
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
index fbd958ca139..dfc609ab929 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
@@ -189,8 +189,8 @@ public boolean isStructureObstructed() {
     }
 
     @Override
-    protected void formStructure(PatternMatchContext context) {
-        super.formStructure(context);
+    protected void formStructure(String name) {
+        super.formStructure(name);
         IEnergyContainer energyContainer = getEnergyContainer();
         this.boostAllowed = energyContainer != null && energyContainer.getOutputVoltage() >= GTValues.V[this.tier + 1];
     }
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index ead5900a871..96c4acff970 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -172,7 +172,7 @@ public void setRecipeLayout(RecipeLayout layout, IGuiHelper guiHelper) {
             float max = Math.max(Math.max(Math.max(size.x, size.y), size.z), 1);
             this.zoom = (float) (3.5 * Math.sqrt(max));
             this.rotationYaw = 20.0f;
-            this.rotationPitch = 50f;
+            this.rotationPitch = 230f;
             this.currentRendererPage = 0;
             setNextLayer(-1);
         } else {

From 1b3368dfb2d86d6f0248a0720e0521fa6b789d32 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 22 Sep 2024 17:16:58 -0700
Subject: [PATCH 43/64] multi aisling

---
 .../multiblock/MultiblockControllerBase.java  |  19 ---
 .../gregtech/api/pattern/OriginOffset.java    |   3 +-
 .../api/pattern/pattern/AisleStrategy.java    |  52 +++++++
 .../pattern/pattern/BasicAisleStrategy.java   | 122 ++++++++++++++++
 .../api/pattern/pattern/BlockPattern.java     |  89 ++++++------
 .../pattern/pattern/FactoryBlockPattern.java  | 132 +++++++++---------
 .../api/pattern/pattern/IBlockPattern.java    |   9 --
 .../api/pattern/pattern/PatternAisle.java     |   4 +
 .../java/gregtech/api/util/GTUtility.java     |   2 +-
 .../behaviors/MultiblockBuilderBehavior.java  |   3 +-
 .../electric/MetaTileEntityAssemblyLine.java  |   2 +-
 .../MetaTileEntityDistillationTower.java      |   2 +-
 .../MetaTileEntityLargeChemicalReactor.java   |  27 ++++
 .../MetaTileEntityPowerSubstation.java        |   2 +-
 .../MetaTileEntityCentralMonitor.java         |   2 +-
 15 files changed, 327 insertions(+), 143 deletions(-)
 create mode 100644 src/main/java/gregtech/api/pattern/pattern/AisleStrategy.java
 create mode 100644 src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index db8dd0b10c9..8ca7386985d 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -110,7 +110,6 @@ public void onPlacement(EntityLivingBase placer) {
 
     public void reinitializeStructurePattern() {
         createStructurePatterns();
-        validateStructurePatterns();
     }
 
     @Override
@@ -144,24 +143,6 @@ protected void createStructurePatterns() {
         structures.put("MAIN", createStructurePattern());
     }
 
-    private void validateStructurePatterns() {
-        List failures = new ArrayList<>();
-
-        for (Object2ObjectMap.Entry pattern : structures.object2ObjectEntrySet()) {
-            if ("MAIN".equals(pattern.getKey())) continue;
-
-            if (pattern.getValue().legacyBuilderError()) {
-                failures.add(pattern.getKey());
-            }
-        }
-
-        // todo remove this
-        if (!failures.isEmpty()) {
-            throw new IllegalStateException("Structure patterns " + Arrays.toString(failures.toArray()) +
-                    " needs some legacy updating");
-        }
-    }
-
     public EnumFacing getUpwardsFacing() {
         return upwardsFacing;
     }
diff --git a/src/main/java/gregtech/api/pattern/OriginOffset.java b/src/main/java/gregtech/api/pattern/OriginOffset.java
index e35b3bf7b01..4684580cedb 100644
--- a/src/main/java/gregtech/api/pattern/OriginOffset.java
+++ b/src/main/java/gregtech/api/pattern/OriginOffset.java
@@ -10,7 +10,8 @@
 public class OriginOffset {
 
     /**
-     * 3 long because the opposite directions cancel out, it stores amount with the first direction in each pair(an even
+     * length 3 because the opposite directions cancel out, it stores amount with the first direction in each pair(an
+     * even
      * ordinal).
      */
     protected final int[] offset = new int[3];
diff --git a/src/main/java/gregtech/api/pattern/pattern/AisleStrategy.java b/src/main/java/gregtech/api/pattern/pattern/AisleStrategy.java
new file mode 100644
index 00000000000..e46832b6ebf
--- /dev/null
+++ b/src/main/java/gregtech/api/pattern/pattern/AisleStrategy.java
@@ -0,0 +1,52 @@
+package gregtech.api.pattern.pattern;
+
+import gregtech.api.pattern.GreggyBlockPos;
+import gregtech.api.util.GTLog;
+import gregtech.api.util.RelativeDirection;
+
+import net.minecraft.util.EnumFacing;
+
+import java.util.List;
+
+/**
+ * A strategy to how aisles should be checked in patterns.
+ */
+public abstract class AisleStrategy {
+
+    protected final int[] dimensions = new int[3];
+    protected final RelativeDirection[] directions = new RelativeDirection[3];
+
+    protected BlockPattern pattern;
+    protected GreggyBlockPos pos;
+    protected EnumFacing front, up;
+
+    /**
+     * Checks the aisles
+     * 
+     * @param flip Whether this is a flipped pattern check.
+     * @return Whether the pattern is formed after this.
+     */
+    public abstract boolean check(boolean flip);
+
+    /**
+     * Called at the start of a structure check.
+     */
+    protected void start(GreggyBlockPos pos, EnumFacing front, EnumFacing up) {
+        this.pos = pos;
+        this.front = front;
+        this.up = up;
+    }
+
+    /**
+     * No more aisles will be added. Check preconditions and throw exceptions here.
+     */
+    protected void finish(int[] dimensions, RelativeDirection[] directions, List aisles) {
+        System.arraycopy(dimensions, 0, this.dimensions, 0, 3);
+        System.arraycopy(directions, 0, this.directions, 0, 3);
+    }
+
+    protected boolean checkAisle(int index, int offset, boolean flip) {
+        GTLog.logger.info("Checked aisle {} with offset {} and flip {}", index, offset, flip);
+        return pattern.checkAisle(pos, front, up, index, offset, flip);
+    }
+}
diff --git a/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
new file mode 100644
index 00000000000..822649b281f
--- /dev/null
+++ b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
@@ -0,0 +1,122 @@
+package gregtech.api.pattern.pattern;
+
+import gregtech.api.util.GTLog;
+import gregtech.api.util.RelativeDirection;
+
+import com.google.common.base.Preconditions;
+
+import java.util.*;
+
+/**
+ * The default aisle strategy, supporting repeatable (multi) aisles.
+ */
+public class BasicAisleStrategy extends AisleStrategy {
+
+    protected final List multiAisles = new ArrayList<>();
+    protected final List aisles = new ArrayList<>();
+    protected final int[] result = new int[2];
+
+    @Override
+    public boolean check(boolean flip) {
+        int offset = 0;
+        for (int[] multiAisle : multiAisles) {
+            int result = checkMultiAisle(multiAisle, offset, flip);
+            if (result == -1) return false;
+            offset += result * (multiAisle[3] - multiAisle[2]);
+        }
+        return true;
+    }
+
+    public int getMultiAisleRepeats(int index) {
+        return multiAisles.get(index)[4];
+    }
+
+    protected int checkMultiAisle(int[] multi, int offset, boolean flip) {
+        int aisleOffset = 0;
+        // go from 0 to max repeat
+        for (int i = 1; i <= multi[1]; i++) {
+            // check all aisles in the multi aisle
+            for (int j = multi[2]; j < multi[3]; j++) {
+                int result = checkRepeatAisle(j, offset + aisleOffset, flip);
+                // same logic as normal aisle check
+                if (result == -1) {
+                    if (i <= multi[0]) return -1;
+                    return multi[4] = i - 1;
+                }
+                aisleOffset += result;
+            }
+        }
+
+        return multi[4] = multi[1];
+    }
+
+    protected int checkRepeatAisle(int index, int offset, boolean flip) {
+        PatternAisle aisle = aisles.get(index);
+        for (int i = 1; i <= aisle.maxRepeats; i++) {
+            boolean result = checkAisle(index, offset + i - 1, flip);
+            if (!result) {
+                if (i <= aisle.minRepeats) return -1;
+
+                return aisles.get(index).actualRepeats = i - 1;
+            }
+        }
+
+        return aisles.get(index).actualRepeats = aisle.maxRepeats;
+    }
+
+    @Override
+    protected void finish(int[] dimensions, RelativeDirection[] directions, List aisles) {
+        super.finish(dimensions, directions, aisles);
+
+        // maybe just set the reference? but then the field cant be final so idk
+        this.aisles.addAll(aisles);
+
+        BitSet covered = new BitSet(aisles.size());
+        int sum = 0;
+        for (int[] arr : multiAisles) {
+            covered.set(arr[2], arr[3]);
+            sum += arr[3] - arr[2];
+        }
+
+        if (sum != covered.cardinality()) {
+            GTLog.logger.error("Overlapping multiAisles. " +
+                    "Total of {} aisles in the multiAisles but only {} distinct aisles.", sum, covered.cardinality());
+            multiAisleError();
+        }
+        if (sum > aisles.size()) {
+            GTLog.logger.error("multiAisles out of bounds. Total of {} aisles but {} aisles in multiAisles.",
+                    aisles.size(), sum);
+            multiAisleError();
+        }
+
+        int i = covered.nextClearBit(0);
+        while ((i = covered.nextClearBit(i)) < aisles.size()) {
+            multiAisles.add(new int[] { 1, 1, i, i + 1, -1 });
+            covered.set(i);
+        }
+
+        multiAisles.sort(Comparator.comparingInt(a -> a[2]));
+        GTLog.logger.error("lawrence");
+        for (int[] arr : multiAisles) {
+            GTLog.logger.error(Arrays.toString(arr));
+        }
+    }
+
+    protected void multiAisleError() {
+        GTLog.logger.error(
+                "multiAisles in the pattern, formatted as [ minRepeats, maxRepeats, startInclusive, endExclusive ] ");
+        for (int[] arr : multiAisles) {
+            GTLog.logger.error(Arrays.toString(arr));
+        }
+        throw new IllegalStateException("Illegal multiAisles, check logs above.");
+    }
+
+    // resisting the urge to make this a generic type return to allow for inheritors,,,,,,
+    public BasicAisleStrategy multiAisle(int min, int max, int from, int to) {
+        Preconditions.checkArgument(max >= min, "max: %s is less than min: %s", max, min);
+        Preconditions.checkArgument(from >= 0, "from argument is negative: %s", from);
+        Preconditions.checkArgument(to > 0, "to argument is not positive: %s", to);
+        multiAisles.add(new int[] { min, max, from, to, -1 });
+        return this;
+    }
+}
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 6b5ea02536f..eba19073d1c 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -53,6 +53,7 @@ public class BlockPattern implements IBlockPattern {
      */
     protected final boolean hasStartOffset;
     protected final PatternAisle[] aisles;
+    protected final AisleStrategy aisleStrategy;
     protected final Char2ObjectMap predicates;
     protected final BlockWorldState worldState;
     protected final Object2IntMap globalCount = new Object2IntOpenHashMap<>();
@@ -61,11 +62,15 @@ public class BlockPattern implements IBlockPattern {
     protected final Long2ObjectMap cache = new Long2ObjectOpenHashMap<>();
 
     // how many not nulls to keep someone from not passing in null?
-    public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, int @NotNull [] dimensions,
+    public BlockPattern(@NotNull PatternAisle @NotNull [] aisles,
+                        @NotNull AisleStrategy aisleStrategy,
+                        int @NotNull [] dimensions,
                         @NotNull RelativeDirection @NotNull [] directions,
-                        @Nullable OriginOffset offset, @NotNull Char2ObjectMap predicates,
+                        @Nullable OriginOffset offset,
+                        @NotNull Char2ObjectMap<@NotNull TraceabilityPredicate> predicates,
                         char centerChar) {
         this.aisles = aisles;
+        this.aisleStrategy = aisleStrategy;
         this.dimensions = dimensions;
         this.directions = directions;
         this.predicates = predicates;
@@ -174,48 +179,52 @@ public Long2ObjectMap getCache() {
     @Override
     public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
                                   EnumFacing upwardsFacing, boolean isFlipped) {
+        // todo still need to rework this to have a temporary global cache
         this.globalCount.clear();
         this.layerCount.clear();
         cache.clear();
 
         worldState.setWorld(world);
 
-        int aisleOffset = -1;
         GreggyBlockPos controllerPos = new GreggyBlockPos(centerPos);
 
-        for (int aisleI = 0; aisleI < aisles.length; aisleI++) {
-            PatternAisle aisle = aisles[aisleI];
-
-            // check everything below min repeats to ensure its valid
-            // don't check aisle.minRepeats itself since its checked below
-            for (int repeats = 1; repeats < aisle.minRepeats; repeats++) {
-                boolean aisleResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI,
-                        aisleOffset + repeats, isFlipped);
-                if (!aisleResult) return false;
-            }
-
-            // if this doesn't get set in the inner loop, then it means all repeats passed
-            int actualRepeats = aisle.maxRepeats;
-
-            for (int repeats = aisle.minRepeats; repeats <= aisle.maxRepeats; repeats++) {
-                boolean aisleResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI,
-                        aisleOffset + repeats, isFlipped);
-
-                // greedy search, tries to make the current aisle repeat as much as possible
-                if (!aisleResult) {
-                    // if the min repetition is invalid then the whole pattern is invalid
-                    if (repeats == aisle.minRepeats) {
-                        return false;
-                    }
-                    // otherwise this is the max repeats
-                    actualRepeats = repeats - 1;
-                    break;
-                }
-            }
-
-            aisle.setActualRepeats(actualRepeats);
-            aisleOffset += actualRepeats;
-        }
+        aisleStrategy.pattern = this;
+        aisleStrategy.start(controllerPos, frontFacing, upwardsFacing);
+        if (!aisleStrategy.check(isFlipped)) return false;
+
+        // for (int aisleI = 0; aisleI < aisles.length; aisleI++) {
+        // PatternAisle aisle = aisles[aisleI];
+        //
+        // // check everything below min repeats to ensure its valid
+        // // don't check aisle.minRepeats itself since its checked below
+        // for (int repeats = 1; repeats < aisle.minRepeats; repeats++) {
+        // boolean aisleResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI,
+        // aisleOffset + repeats, isFlipped);
+        // if (!aisleResult) return false;
+        // }
+        //
+        // // if this doesn't get set in the inner loop, then it means all repeats passed
+        // int actualRepeats = aisle.maxRepeats;
+        //
+        // for (int repeats = aisle.minRepeats; repeats <= aisle.maxRepeats; repeats++) {
+        // boolean aisleResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI,
+        // aisleOffset + repeats, isFlipped);
+        //
+        // // greedy search, tries to make the current aisle repeat as much as possible
+        // if (!aisleResult) {
+        // // if the min repetition is invalid then the whole pattern is invalid
+        // if (repeats == aisle.minRepeats) {
+        // return false;
+        // }
+        // // otherwise this is the max repeats
+        // actualRepeats = repeats - 1;
+        // break;
+        // }
+        // }
+        //
+        // aisle.setActualRepeats(actualRepeats);
+        // aisleOffset += actualRepeats;
+        // }
 
         // global minimum checks
         for (Object2IntMap.Entry entry : globalCount.object2IntEntrySet()) {
@@ -450,9 +459,11 @@ public PatternState getPatternState() {
         return state;
     }
 
-    @Override
-    public boolean legacyBuilderError() {
-        return !hasStartOffset;
+    /**
+     * DO NOT MUTATE THIS
+     */
+    public AisleStrategy getAisleStrategy() {
+        return aisleStrategy;
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
index 6c896f6e038..c39edf799b6 100644
--- a/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
@@ -2,7 +2,6 @@
 
 import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.TraceabilityPredicate;
-import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
 import com.google.common.base.Joiner;
@@ -52,6 +51,7 @@ public class FactoryBlockPattern {
      */
     private OriginOffset offset;
     private char centerChar;
+    private AisleStrategy aisleStrategy;
 
     private final List aisles = new ArrayList<>();
 
@@ -63,18 +63,18 @@ public class FactoryBlockPattern {
     /**
      * In the form of [ aisleDir, stringDir, charDir ]
      */
-    private final RelativeDirection[] structureDir = new RelativeDirection[3];
+    private final RelativeDirection[] directions = new RelativeDirection[3];
 
     /**
      * @see FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection)
      */
     private FactoryBlockPattern(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) {
-        structureDir[0] = aisleDir;
-        structureDir[1] = stringDir;
-        structureDir[2] = charDir;
+        directions[0] = aisleDir;
+        directions[1] = stringDir;
+        directions[2] = charDir;
         int flags = 0;
         for (int i = 0; i < 3; i++) {
-            switch (structureDir[i]) {
+            switch (directions[i]) {
                 case UP:
                 case DOWN:
                     flags |= 0x1;
@@ -95,74 +95,36 @@ private FactoryBlockPattern(RelativeDirection aisleDir, RelativeDirection string
 
     /**
      * Adds a repeatable aisle to this pattern.
-     * 
-     * @param aisle The aisle to add
-     * @see FactoryBlockPattern#setRepeatable(int, int)
+     *
+     * @param minRepeats The min repeats, inclusive
+     * @param maxRepeats The max repeats, inclusive
+     * @param aisle      The aisle to add
      */
-    public FactoryBlockPattern aisleRepeatable(int minRepeat, int maxRepeat, @NotNull String... aisle) {
-        if (ArrayUtils.isEmpty(aisle) || StringUtils.isEmpty(aisle[0]))
-            throw new IllegalArgumentException("Empty pattern for aisle");
-
-        // set the dimensions if the user hasn't already
-        if (dimensions[2] == -1) {
-            dimensions[2] = aisle[0].length();
-        }
-        if (dimensions[1] == -1) {
-            dimensions[1] = aisle.length;
-        }
-
-        if (aisle.length != dimensions[1]) {
-            throw new IllegalArgumentException("Expected aisle with height of " + dimensions[1] +
-                    ", but was given one with a height of " + aisle.length + ")");
-        } else {
-            for (String s : aisle) {
-                if (s.length() != dimensions[2]) {
-                    throw new IllegalArgumentException(
-                            "Not all rows in the given aisle are the correct width (expected " + dimensions[2] +
-                                    ", found one with " + s.length() + ")");
-                }
-
-                for (char c : s.toCharArray()) {
-                    if (!this.symbolMap.containsKey(c)) {
-                        this.symbolMap.put(c, null);
-                    }
+    public FactoryBlockPattern aisleRepeatable(int minRepeats, int maxRepeats, @NotNull String... aisle) {
+        validateAisle(aisle);
+        for (String s : aisle) {
+            for (char c : s.toCharArray()) {
+                if (!this.symbolMap.containsKey(c)) {
+                    this.symbolMap.put(c, null);
                 }
             }
-
-            aisles.add(new PatternAisle(minRepeat, maxRepeat, aisle));
-            if (minRepeat > maxRepeat)
-                throw new IllegalArgumentException("Lower bound of repeat counting must smaller than upper bound!");
-            return this;
         }
-    }
 
-    /**
-     * Adds a single aisle to this pattern. (so multiple calls to this will increase the aisleDir by 1)
-     */
-    public FactoryBlockPattern aisle(String... aisle) {
-        return aisleRepeatable(1, 1, aisle);
-    }
-
-    /**
-     * Set last aisle repeatable
-     * 
-     * @param minRepeat Minimum amount of repeats, inclusive
-     * @param maxRepeat Maximum amount of repeats, inclusive
-     */
-    public FactoryBlockPattern setRepeatable(int minRepeat, int maxRepeat) {
-        if (minRepeat > maxRepeat)
+        if (minRepeats > maxRepeats)
             throw new IllegalArgumentException("Lower bound of repeat counting must smaller than upper bound!");
-        aisles.get(aisles.size() - 1).setRepeats(minRepeat, maxRepeat);
+
+        PatternAisle aile = new PatternAisle(aisle);
+        aile.minRepeats = minRepeats;
+        aile.maxRepeats = maxRepeats;
+        aisles.add(aile);
         return this;
     }
 
     /**
-     * Set last aisle repeatable
-     * 
-     * @param repeatCount The amount to repeat
+     * Adds a single aisle to this pattern. (so multiple calls to this will increase the aisleDir by 1)
      */
-    public FactoryBlockPattern setRepeatable(int repeatCount) {
-        return setRepeatable(repeatCount, repeatCount);
+    public FactoryBlockPattern aisle(String... aisle) {
+        return aisleRepeatable(1, 1, aisle);
     }
 
     /**
@@ -209,15 +171,19 @@ public FactoryBlockPattern where(char symbol, TraceabilityPredicate blockMatcher
         return this;
     }
 
+    public FactoryBlockPattern aisleStrategy(AisleStrategy strategy) {
+        this.aisleStrategy = strategy;
+        return this;
+    }
+
     public BlockPattern build() {
         checkMissingPredicates();
         this.dimensions[0] = aisles.size();
-        // the reason this exists is before this rewrite, MultiblockControllerBase would
-        // pass the opposite front facing into the pattern check, but now this is fixed.
-        //
-        if (offset == null) GTLog.logger.warn(
-                "You didn't use .startOffset() on the builder! Start offset will now be auto detected, which may product unintended results!");
-        return new BlockPattern(aisles.toArray(new PatternAisle[0]), dimensions, structureDir, offset, symbolMap,
+        if (aisleStrategy == null) aisleStrategy = new BasicAisleStrategy();
+
+        aisleStrategy.finish(dimensions, directions, aisles);
+        return new BlockPattern(aisles.toArray(new PatternAisle[0]), aisleStrategy, dimensions, directions, offset,
+                symbolMap,
                 centerChar);
     }
 
@@ -234,4 +200,32 @@ private void checkMissingPredicates() {
             throw new IllegalStateException("Predicates for character(s) " + COMMA_JOIN.join(list) + " are missing");
         }
     }
+
+    private String[] validateAisle(String[] aisle) {
+        if (ArrayUtils.isEmpty(aisle) || StringUtils.isEmpty(aisle[0]))
+            throw new IllegalArgumentException("Empty pattern for aisle");
+
+        // set the dimensions if the user hasn't already
+        if (dimensions[2] == -1) {
+            dimensions[2] = aisle[0].length();
+        }
+        if (dimensions[1] == -1) {
+            dimensions[1] = aisle.length;
+        }
+
+        if (aisle.length != dimensions[1]) {
+            throw new IllegalArgumentException("Expected aisle with height of " + dimensions[1] +
+                    ", but was given one with a height of " + aisle.length + ")");
+        } else {
+            for (String s : aisle) {
+                if (s.length() != dimensions[2]) {
+                    throw new IllegalArgumentException(
+                            "Not all rows in the given aisle are the correct width (expected " + dimensions[2] +
+                                    ", found one with " + s.length() + ")");
+                }
+            }
+
+            return aisle;
+        }
+    }
 }
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index dc59f51344b..b87c6a2d227 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -79,15 +79,6 @@ default void clearCache() {
      */
     Long2ObjectMap getCache();
 
-    /**
-     * If anything from legacy need to be updated.
-     * 
-     * @return True if yes, which will throw an error in the multiblock.
-     */
-    default boolean legacyBuilderError() {
-        return false;
-    }
-
     OriginOffset getOffset();
 
     default void moveOffset(RelativeDirection dir, int amount) {
diff --git a/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java b/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
index bff4158588a..7e8cf9bd0e7 100644
--- a/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
+++ b/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
@@ -25,6 +25,10 @@ public PatternAisle(int repeats, String[] pattern) {
         this(repeats, repeats, pattern);
     }
 
+    public PatternAisle(String[] pattern) {
+        this(1, 1, pattern);
+    }
+
     public void setRepeats(int minRepeats, int maxRepeats) {
         this.minRepeats = minRepeats;
         this.maxRepeats = maxRepeats;
diff --git a/src/main/java/gregtech/api/util/GTUtility.java b/src/main/java/gregtech/api/util/GTUtility.java
index 497de86808c..15befed150b 100644
--- a/src/main/java/gregtech/api/util/GTUtility.java
+++ b/src/main/java/gregtech/api/util/GTUtility.java
@@ -670,7 +670,7 @@ public static ItemStack toItem(World world, BlockPos pos) {
         // first check if the block is a GT machine
         TileEntity tileEntity = world.getTileEntity(pos);
         if (tileEntity instanceof IGregTechTileEntity) {
-            stack =  ((IGregTechTileEntity) tileEntity).getMetaTileEntity().getStackForm();
+            stack = ((IGregTechTileEntity) tileEntity).getMetaTileEntity().getStackForm();
         }
 
         if (stack.isEmpty()) {
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index b27f93dc12d..a5d03800a82 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -179,7 +179,8 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
             if (!multiblock.isStructureFormed("MAIN")) {
-                multiblock.getBuildableShapes("MAIN", getMap(player.getHeldItem(hand))).get(0).getMap(multiblock, new BlockPos(0, 128, 0), new HashMap<>());
+                multiblock.getBuildableShapes("MAIN", getMap(player.getHeldItem(hand))).get(0).getMap(multiblock,
+                        new BlockPos(0, 128, 0), new HashMap<>());
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
index 82aa3973fb8..5f5c77c5a1d 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
@@ -81,7 +81,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     protected BlockPattern createStructurePattern() {
         FactoryBlockPattern pattern = FactoryBlockPattern.start(RIGHT, UP, FRONT)
                 .aisle("FIF", "RTR", "GAS", " Y ")
-                .aisle("FIF", "RTR", "GAD", " Y ").setRepeatable(3, 16)
+                .aisleRepeatable(3, 16, "FIF", "RTR", "GAD", " Y ")
                 .aisle("FOF", "RTR", "GAD", " Y ")
                 .where('S', selfPredicate())
                 .where('F', states(getCasingState())
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
index e45bc37c969..056e43c26f0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
@@ -119,7 +119,7 @@ public void invalidateStructure(String name) {
     protected @NotNull BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start(UP, FRONT, RIGHT)
                 .aisle("YYY", "YYY", "YSY")
-                .aisle("XXX", "X#X", "XXX").setRepeatable(1, 11)
+                .aisleRepeatable(1, 11, "XXX", "X#X", "XXX")
                 .aisle("XXX", "XXX", "XXX")
                 .where('S', selfPredicate())
                 .where('Y', states(getCasingState())
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
index 91985ceec83..9505f8a2bd3 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
@@ -7,6 +7,7 @@
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.pattern.pattern.BasicAisleStrategy;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.RecipeMaps;
@@ -23,10 +24,13 @@
 
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.resources.I18n;
+import net.minecraft.init.Blocks;
 import net.minecraft.item.ItemStack;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.util.SoundEvent;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.TextComponentString;
 import net.minecraft.world.World;
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
@@ -55,6 +59,21 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     protected @NotNull BlockPattern createStructurePattern() {
         TraceabilityPredicate casing = states(getCasingState()).setMinGlobalLimited(10);
         TraceabilityPredicate abilities = autoAbilities();
+
+        if (true) {
+            return FactoryBlockPattern.start()
+                    .aisle("S")
+                    .aisle("G")
+                    .aisle("B")
+                    .aisle("W")
+                    .where('S', selfPredicate())
+                    .where('G', states(Blocks.CONCRETE.getStateFromMeta(5)))
+                    .where('B', states(Blocks.CONCRETE.getStateFromMeta(11)))
+                    .where('W', states(Blocks.CONCRETE.getDefaultState()))
+                    .aisleStrategy(new BasicAisleStrategy().multiAisle(1, 3, 1, 3))
+                    .build();
+        }
+
         return FactoryBlockPattern.start()
                 .aisle("XXX", "XSX", "XXX")
                 .aisle("XCX", "CPC", "XCX")
@@ -140,6 +159,14 @@ public void addInformation(ItemStack stack, @Nullable World player, List
         tooltip.add(TooltipHelper.RAINBOW_SLOW + I18n.format("gregtech.machine.perfect_oc"));
     }
 
+    @Override
+    public void addDisplayText(List textList) {
+        super.addDisplayText(textList);
+        textList.add(new TextComponentString("Multi aisle repeats: " +
+                ((BasicAisleStrategy) ((BlockPattern) structures.get("MAIN")).getAisleStrategy())
+                        .getMultiAisleRepeats(1)));
+    }
+
     @SideOnly(Side.CLIENT)
     @NotNull
     @Override
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index 7ec03dacc08..fd26fafbe7a 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -234,7 +234,7 @@ protected BlockPattern createStructurePattern() {
         return FactoryBlockPattern.start(UP, FRONT, RIGHT)
                 .aisle("XXXXX", "XXXXX", "XXXXX", "XXXXX", "XXSXX")
                 .aisle("XXXXX", "XCCCX", "XCCCX", "XCCCX", "XXXXX")
-                .aisle("GGGGG", "GBBBG", "GBBBG", "GBBBG", "GGGGG").setRepeatable(1, MAX_BATTERY_LAYERS)
+                .aisleRepeatable(1, MAX_BATTERY_LAYERS, "GGGGG", "GBBBG", "GBBBG", "GBBBG", "GGGGG")
                 .aisle("GGGGG", "GGGGG", "GGGGG", "GGGGG", "GGGGG")
                 .where('S', selfPredicate())
                 .where('C', states(getCasingState()))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index 60a5610d3f6..a6aa5318cbd 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -407,7 +407,7 @@ public Set getAllCovers() {
         }
         return FactoryBlockPattern.start(LEFT, BACK, UP)
                 .aisle(start.toString())
-                .aisle(slice.toString()).setRepeatable(3, MAX_WIDTH)
+                .aisleRepeatable(3, MAX_WIDTH, slice.toString())
                 .aisle(end.toString())
                 .where('S', selfPredicate())
                 .where('A', states(MetaBlocks.METAL_CASING.getState(BlockMetalCasing.MetalCasingType.STEEL_SOLID))

From befb7cf384671083859d6a26b6a7f7c9a0b48330 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 22 Sep 2024 17:59:35 -0700
Subject: [PATCH 44/64] but it works

---
 .../api/pattern/pattern/BasicAisleStrategy.java    | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
index 822649b281f..e7f52974ba4 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
@@ -22,7 +22,7 @@ public boolean check(boolean flip) {
         for (int[] multiAisle : multiAisles) {
             int result = checkMultiAisle(multiAisle, offset, flip);
             if (result == -1) return false;
-            offset += result * (multiAisle[3] - multiAisle[2]);
+            offset += result;
         }
         return true;
     }
@@ -33,21 +33,25 @@ public int getMultiAisleRepeats(int index) {
 
     protected int checkMultiAisle(int[] multi, int offset, boolean flip) {
         int aisleOffset = 0;
+        int temp = 0;
         // go from 0 to max repeat
         for (int i = 1; i <= multi[1]; i++) {
             // check all aisles in the multi aisle
             for (int j = multi[2]; j < multi[3]; j++) {
-                int result = checkRepeatAisle(j, offset + aisleOffset, flip);
+                int result = checkRepeatAisle(j, offset + temp, flip);
                 // same logic as normal aisle check
                 if (result == -1) {
                     if (i <= multi[0]) return -1;
-                    return multi[4] = i - 1;
+                    multi[4] = i - 1;
+                    return aisleOffset;
                 }
-                aisleOffset += result;
+                temp += result;
             }
+            aisleOffset = temp;
         }
 
-        return multi[4] = multi[1];
+        multi[4] = multi[1];
+        return aisleOffset;
     }
 
     protected int checkRepeatAisle(int index, int offset, boolean flip) {

From cc5211b90230f22809bd8bfecf533e39501d5c9d Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sat, 28 Sep 2024 12:50:45 -0700
Subject: [PATCH 45/64] generify default pattern generation, and multi aisles

---
 .../multiblock/MultiblockControllerBase.java  | 14 +--
 .../api/pattern/MultiblockShapeInfo.java      | 26 ++++++
 .../pattern/pattern/BasicAisleStrategy.java   |  5 +-
 .../api/pattern/pattern/BlockPattern.java     | 93 ++++---------------
 .../pattern/pattern/ExpandablePattern.java    |  5 +-
 .../api/pattern/pattern/IBlockPattern.java    | 14 ++-
 .../api/pattern/pattern/PatternAisle.java     |  4 +
 .../handler/MultiblockPreviewRenderer.java    |  2 +-
 .../behaviors/MultiblockBuilderBehavior.java  |  6 +-
 .../MetaTileEntityLargeChemicalReactor.java   |  1 +
 .../MultiblockInfoRecipeWrapper.java          |  2 +-
 11 files changed, 74 insertions(+), 98 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 8ca7386985d..3bf82ed9468 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -779,17 +779,11 @@ public List getMatchingShapes() {
     }
 
     /**
-     * The new(and better) way of getting shapes for in world, jei, and autobuild. Default impl just converts
-     * {@link MultiblockControllerBase#getMatchingShapes()}, if not empty, to this. If getMatchingShapes is empty, uses
-     * a default generated structure pattern, it's not very good which is why you should override this.
-     *
-     * @param keyMap A map for autobuild, or null if it is an in world or jei preview. Note that for in world and jei
-     *               previews you can return a singleton list(only the first element will be used anyway).
+     * Get the default preview shape for JEI and in world previews.
      */
     // todo add use for the keyMap with the multiblock builder
     // todo maybe add name arg for building substructures
-    public List getBuildableShapes(String substructureName,
-                                                        @Nullable Object2IntMap keyMap) {
+    public List getPreviewShapes(String substructureName) {
         List infos = getMatchingShapes();
 
         // if there is no overriden getMatchingShapes() just return the default one
@@ -797,8 +791,10 @@ public List getBuildableShapes(String substructureName,
             // for jei and stuff
             if (getSubstructure(substructureName) == null) createStructurePatterns();
 
-            MultiblockShapeInfo info = getSubstructure(substructureName).getDefaultShape(false);
+            MultiblockShapeInfo info = MultiblockShapeInfo.fromShape(getSubstructure(substructureName));
+
             if (info == null) return Collections.emptyList();
+
             return Collections.singletonList(info);
         }
 
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index 5b3ec9c6cc6..10d5a2cd4fa 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -3,11 +3,14 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.pattern.pattern.PatternAisle;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
+import it.unimi.dsi.fastutil.chars.Char2ObjectMaps;
+
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.init.Blocks;
@@ -298,6 +301,29 @@ public static Builder builder() {
         return builder(RelativeDirection.FRONT, RelativeDirection.UP, RelativeDirection.RIGHT);
     }
 
+    public static MultiblockShapeInfo fromShape(RelativeDirection[] directions, char[][][] shape, Char2ObjectMap predicates) {
+        if (shape == null) return null;
+        Char2ObjectMap candidates = new Char2ObjectOpenHashMap<>();
+        for (Char2ObjectMap.Entry entry : predicates.char2ObjectEntrySet()) {
+            if (entry.getValue().candidates == null) {
+                candidates.put(entry.getCharKey(), BlockInfo.EMPTY);
+            } else {
+                candidates.put(entry.getCharKey(), entry.getValue().candidates.get()[0]);
+            }
+        }
+
+        return new MultiblockShapeInfo(Arrays.stream(shape).map(PatternAisle::new).toArray(PatternAisle[]::new), candidates,
+                Char2ObjectMaps.emptyMap(), directions);
+    }
+
+    public static MultiblockShapeInfo fromShape(IBlockPattern pattern) {
+        Char2ObjectMap predicates = new Char2ObjectOpenHashMap<>();
+        RelativeDirection[] directions = new RelativeDirection[3];
+        char[][][] shape = pattern.getDefaultShape(predicates, directions);
+        if (shape == null) return null;
+        return fromShape(directions, shape, predicates);
+    }
+
     public static class Builder {
 
         private List shape = new ArrayList<>();
diff --git a/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
index e7f52974ba4..dc69a6a883c 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
@@ -94,16 +94,13 @@ protected void finish(int[] dimensions, RelativeDirection[] directions, List a[2]));
-        GTLog.logger.error("lawrence");
-        for (int[] arr : multiAisles) {
-            GTLog.logger.error(Arrays.toString(arr));
-        }
     }
 
     protected void multiAisleError() {
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index eba19073d1c..fa752b47d68 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -1,10 +1,8 @@
 package gregtech.api.pattern.pattern;
 
-import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
-import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
@@ -12,7 +10,6 @@
 import gregtech.api.util.RelativeDirection;
 
 import net.minecraft.block.state.IBlockState;
-import net.minecraft.init.Blocks;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
@@ -21,8 +18,6 @@
 import it.unimi.dsi.fastutil.chars.Char2IntMap;
 import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
-import it.unimi.dsi.fastutil.chars.Char2ObjectMaps;
-import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2CharMap;
@@ -192,40 +187,6 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
         aisleStrategy.start(controllerPos, frontFacing, upwardsFacing);
         if (!aisleStrategy.check(isFlipped)) return false;
 
-        // for (int aisleI = 0; aisleI < aisles.length; aisleI++) {
-        // PatternAisle aisle = aisles[aisleI];
-        //
-        // // check everything below min repeats to ensure its valid
-        // // don't check aisle.minRepeats itself since its checked below
-        // for (int repeats = 1; repeats < aisle.minRepeats; repeats++) {
-        // boolean aisleResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI,
-        // aisleOffset + repeats, isFlipped);
-        // if (!aisleResult) return false;
-        // }
-        //
-        // // if this doesn't get set in the inner loop, then it means all repeats passed
-        // int actualRepeats = aisle.maxRepeats;
-        //
-        // for (int repeats = aisle.minRepeats; repeats <= aisle.maxRepeats; repeats++) {
-        // boolean aisleResult = checkAisle(controllerPos, frontFacing, upwardsFacing, aisleI,
-        // aisleOffset + repeats, isFlipped);
-        //
-        // // greedy search, tries to make the current aisle repeat as much as possible
-        // if (!aisleResult) {
-        // // if the min repetition is invalid then the whole pattern is invalid
-        // if (repeats == aisle.minRepeats) {
-        // return false;
-        // }
-        // // otherwise this is the max repeats
-        // actualRepeats = repeats - 1;
-        // break;
-        // }
-        // }
-        //
-        // aisle.setActualRepeats(actualRepeats);
-        // aisleOffset += actualRepeats;
-        // }
-
         // global minimum checks
         for (Object2IntMap.Entry entry : globalCount.object2IntEntrySet()) {
             if (entry.getIntValue() < entry.getKey().minGlobalCount) {
@@ -314,13 +275,13 @@ public int getRepetitionCount(int aisleI) {
     }
 
     @Override
-    public MultiblockShapeInfo getDefaultShape(boolean skipMTEs) {
+    public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map, RelativeDirection[] directions) {
         // for each symbol, which simple predicate is being used
         // this advances whenever a minimum has been satisfied(if any), or a maximum has been reached(if any)
         // preview counts are treated as exactly that many
         Char2IntMap predicateIndex = new Char2IntOpenHashMap();
         // candidates to be passed into MultiblockShapeInfo
-        Char2ObjectMap candidates = new Char2ObjectOpenHashMap<>();
+//        Char2ObjectMap candidates = new Char2ObjectOpenHashMap<>();
         // cache for candidates
         Object2CharMap infos = new Object2CharOpenHashMap<>();
         Object2IntMap globalCache = new Object2IntOpenHashMap<>();
@@ -332,6 +293,15 @@ public MultiblockShapeInfo getDefaultShape(boolean skipMTEs) {
         char currentChar = 1;
         int aisleOffset = 0;
 
+        for (TraceabilityPredicate predicate : predicates.values()) {
+            for (TraceabilityPredicate.SimplePredicate simple : predicate.common) {
+                if (infos.put(simple, currentChar) == 0) {
+                    map.put(currentChar, simple);
+                    currentChar++;
+                }
+            }
+        }
+
         // first pass fills in all the minimum counts
         for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
             for (int repeats = 1; repeats <= aisles[aisleI].minRepeats; repeats++) {
@@ -347,32 +317,11 @@ public MultiblockShapeInfo getDefaultShape(boolean skipMTEs) {
 
                         if (simple.candidates == null) continue;
 
-                        // cache all the BlockInfo, so that we only need to get it once per simple predicate
-                        if (!infos.containsKey(simple)) {
-                            BlockInfo[] blockInfos = simple.candidates.get();
-                            int pointer = 0;
-                            if (blockInfos.length != 1 && skipMTEs) {
-                                // move the pointer to the last pos where there is no MTE
-                                try {
-                                    while ((blockInfos[pointer].getTileEntity() instanceof MetaTileEntityHolder))
-                                        pointer++;
-                                } catch (ArrayIndexOutOfBoundsException e) {
-                                    // every candidate is a MTE, just do first one
-                                    pointer = 0;
-                                }
-                            }
-                            infos.put(simple, currentChar);
-                            candidates.put(currentChar, blockInfos[pointer]);
-                            currentChar++;
-                        }
-
                         char info = infos.getChar(simple);
                         int layerCount = layerCache.put(simple, layerCache.getInt(simple) + 1) + 1;
                         int globalCount = globalCache.put(simple, globalCache.getInt(simple) + 1) + 1;
 
-                        // replace all air with 0 instead of whatever char
-                        pattern.get(aisleOffset)[stringI][charI] = candidates.get(info).getBlockState() ==
-                                Blocks.AIR.getDefaultState() ? 0 : info;
+                        pattern.get(aisleOffset)[stringI][charI] = info;
 
                         TraceabilityPredicate.SimplePredicate next = simple;
 
@@ -421,9 +370,11 @@ public MultiblockShapeInfo getDefaultShape(boolean skipMTEs) {
                                 (next.maxGlobalCount != -1 && globalCount == next.maxGlobalCount)) {
                             // if the current predicate is used, move until the next free one
                             int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
-                            if (newIndex >= predicate.common.size())
+                            if (newIndex >= predicate.common.size()) {
                                 GTLog.logger.warn("Failed to generate default structure pattern.",
                                         new Throwable());
+                                newIndex = 0;
+                            }
                             next = predicate.common.get(newIndex);
                             globalCount = globalCache.getInt(next);
                             layerCount = layerCache.getInt(next);
@@ -431,27 +382,19 @@ public MultiblockShapeInfo getDefaultShape(boolean skipMTEs) {
 
                         if (next.candidates == null) continue;
 
-                        if (!infos.containsKey(next)) {
-                            BlockInfo info = next.candidates.get()[0];
-                            infos.put(next, currentChar);
-                            candidates.put(currentChar, info);
-                            currentChar++;
-                        }
-
                         char info = infos.getChar(next);
                         layerCache.put(next, layerCount + 1);
                         globalCache.put(next, globalCount + 1);
 
-                        pattern.get(aisleOffset)[stringI][charI] = candidates.get(info).getBlockState() ==
-                                Blocks.AIR.getDefaultState() ? 0 : info;
+                        pattern.get(aisleOffset)[stringI][charI] = info;
                     }
                 }
                 aisleOffset++;
             }
         }
 
-        return new MultiblockShapeInfo(pattern.stream().map(a -> new PatternAisle(1, a)).toArray(PatternAisle[]::new),
-                candidates, Char2ObjectMaps.emptyMap(), directions);
+        System.arraycopy(this.directions, 0, directions, 0, 3);
+        return pattern.toArray(new char[0][][]);
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index e1528a1ab7c..09067ff53c7 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -10,6 +10,8 @@
 import gregtech.api.util.RelativeDirection;
 import gregtech.api.util.function.QuadFunction;
 
+import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
+
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
@@ -21,6 +23,7 @@
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.function.BiFunction;
 
@@ -173,7 +176,7 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
     }
 
     @Override
-    public MultiblockShapeInfo getDefaultShape(boolean skipMTEs) {
+    public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map, RelativeDirection[] directions) {
         // todo undo
         return null;
     }
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index b87c6a2d227..8e4c0901999 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -3,9 +3,14 @@
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.OriginOffset;
+import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.RelativeDirection;
 
+import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
+
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
@@ -51,11 +56,12 @@ boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
      * Gets the default shape, if the multiblock does not specify one. Return null to represent the default shape does
      * not exist.
      * 
-     * @param skipMTEs If enabled, does not place MTEs at positions unless all candidates are MTEs, in which case
-     *                 selects the first candidate.
+     * @param map Pass in an empty map to receive a populated map with the chars in the return mapping to a simpel predicate.
+     * @param directions Pass in an array of length 3 to get the directions for the preview.
      */
-    @Nullable
-    MultiblockShapeInfo getDefaultShape(boolean skipMTEs);
+    // peak nullability, basically the return value can be null but if it is not then no arrays can be null
+    char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map, RelativeDirection[] directions);
+
 
     /**
      * Gets the internal pattern state, you should use the one returned from
diff --git a/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java b/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
index 7e8cf9bd0e7..635cd942102 100644
--- a/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
+++ b/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java
@@ -29,6 +29,10 @@ public PatternAisle(String[] pattern) {
         this(1, 1, pattern);
     }
 
+    public PatternAisle(char[][] pattern) {
+        this(1, pattern);
+    }
+
     public void setRepeats(int minRepeats, int maxRepeats) {
         this.minRepeats = minRepeats;
         this.maxRepeats = maxRepeats;
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index 9cbbb62086a..6e1fb5df29a 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -91,7 +91,7 @@ public static void renderMultiBlockPreview(MultiblockControllerBase controller,
         mbpEndTime = System.currentTimeMillis() + durTimeMillis;
         opList = GLAllocation.generateDisplayLists(1); // allocate op list
         GlStateManager.glNewList(opList, GL11.GL_COMPILE);
-        List shapes = controller.getBuildableShapes("MAIN", null);
+        List shapes = controller.getPreviewShapes("MAIN");
         if (!shapes.isEmpty()) {
             renderControllerInList(controller, shapes.get(0), layer);
             shapes.get(0).sendDotMessage(player);
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index a5d03800a82..c0ddd3e4b80 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -165,6 +165,7 @@ public ModularPanel buildUI(HandGuiData guiData, PanelSyncManager guiSyncManager
     }
 
     @Override
+    // todo full substructure debug and autobuild support
     public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPos pos, EnumFacing side, float hitX,
                                            float hitY, float hitZ, EnumHand hand) {
         // Initial checks
@@ -179,13 +180,12 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
             if (!multiblock.isStructureFormed("MAIN")) {
-                multiblock.getBuildableShapes("MAIN", getMap(player.getHeldItem(hand))).get(0).getMap(multiblock,
-                        new BlockPos(0, 128, 0), new HashMap<>());
+//                multiblock.auto("MAIN", getMap(player.getHeldItem(hand))).get(0).getMap(multiblock,
+//                        new BlockPos(0, 128, 0), new HashMap<>());
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
         } else {
-            // todo full substructure debug and autobuild support
             // If not sneaking, try to show structure debug info (if any) in chat.
             if (!multiblock.isStructureFormed("MAIN")) {
                 PatternError error = multiblock.getSubstructure("MAIN").getPatternState().getError();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
index 9505f8a2bd3..b8f72e59d17 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeChemicalReactor.java
@@ -60,6 +60,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
         TraceabilityPredicate casing = states(getCasingState()).setMinGlobalLimited(10);
         TraceabilityPredicate abilities = autoAbilities();
 
+        // todo remove
         if (true) {
             return FactoryBlockPattern.start()
                     .aisle("S")
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 96c4acff970..d1b46fdb402 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -123,7 +123,7 @@ public MBPattern(final WorldSceneRenderer sceneRenderer, final List p
     public MultiblockInfoRecipeWrapper(@NotNull MultiblockControllerBase controller) {
         this.controller = controller;
         Set drops = new ObjectOpenCustomHashSet<>(ItemStackHashStrategy.comparingAllButCount());
-        this.patterns = controller.getBuildableShapes("MAIN", null).stream()
+        this.patterns = controller.getPreviewShapes("MAIN").stream()
                 .map(it -> initializePattern(it, drops))
                 .toArray(MBPattern[]::new);
         allItemStackInputs.addAll(drops);

From d60c59bfc033b62d6d06a431f72f05934b141bd5 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 13 Oct 2024 15:42:21 -0700
Subject: [PATCH 46/64] the worst code this has even seen

---
 .../multiblock/MultiblockControllerBase.java  |  50 +++++--
 .../gregtech/api/pattern/PatternError.java    |   5 +
 .../api/pattern/TraceabilityPredicate.java    | 140 ++++++++----------
 .../api/pattern/pattern/BlockPattern.java     |  39 +++--
 .../pattern/pattern/ExpandablePattern.java    |  15 +-
 .../api/pattern/pattern/IBlockPattern.java    |  16 +-
 .../behaviors/MultiblockBuilderBehavior.java  |  14 +-
 .../MetaTileEntityPrimitiveBlastFurnace.java  |   3 +-
 .../electric/MetaTileEntityCleanroom.java     |  13 +-
 .../MetaTileEntityPowerSubstation.java        |   2 +-
 .../MetaTileEntityCharcoalPileIgniter.java    |   6 +-
 .../MultiblockInfoRecipeWrapper.java          |   7 +-
 12 files changed, 182 insertions(+), 128 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 3bf82ed9468..acdf79dbacf 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -11,6 +11,7 @@
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.MultiblockShapeInfo;
+import gregtech.api.pattern.PatternError;
 import gregtech.api.pattern.PatternStringError;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.IBlockPattern;
@@ -28,6 +29,10 @@
 import gregtech.client.renderer.texture.cube.SimpleOrientedCubeRenderer;
 import gregtech.common.blocks.MetaBlocks;
 
+import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
+
+import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
+
 import net.minecraft.block.Block;
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.renderer.texture.TextureAtlasSprite;
@@ -76,6 +81,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
@@ -216,14 +222,13 @@ public TextureAtlasSprite getFrontDefaultTexture() {
         return getFrontOverlay().getParticleSprite();
     }
 
-    public static TraceabilityPredicate tilePredicate(@NotNull BiFunction predicate,
+    public static TraceabilityPredicate tilePredicate(@NotNull BiPredicate predicate,
                                                       @Nullable Supplier candidates) {
-        return new TraceabilityPredicate((worldState, patternState) -> {
+        return new TraceabilityPredicate(worldState -> {
             TileEntity tileEntity = worldState.getTileEntity();
-            if (!(tileEntity instanceof IGregTechTileEntity))
-                return false;
+            if (!(tileEntity instanceof IGregTechTileEntity)) return PatternError.PLACEHOLDER;
             MetaTileEntity metaTileEntity = ((IGregTechTileEntity) tileEntity).getMetaTileEntity();
-            return predicate.apply(worldState, metaTileEntity);
+            return predicate.test(worldState, metaTileEntity) ? null : PatternError.PLACEHOLDER;
         }, candidates);
     }
 
@@ -258,7 +263,8 @@ public static TraceabilityPredicate abilities(MultiblockAbility... allowedAbi
 
     public static TraceabilityPredicate states(IBlockState... allowedStates) {
         return new TraceabilityPredicate(
-                (worldState, patternState) -> ArrayUtils.contains(allowedStates, worldState.getBlockState()),
+                worldState -> ArrayUtils.contains(allowedStates, worldState.getBlockState()) ? null :
+                        PatternError.PLACEHOLDER,
                 getCandidates(allowedStates));
     }
 
@@ -268,18 +274,18 @@ public static TraceabilityPredicate states(IBlockState... allowedStates) {
     public static TraceabilityPredicate frames(Material... frameMaterials) {
         return states(Arrays.stream(frameMaterials).map(m -> MetaBlocks.FRAMES.get(m).getBlock(m))
                 .toArray(IBlockState[]::new))
-                        .or(new TraceabilityPredicate((worldState, patternState) -> {
+                        .or(new TraceabilityPredicate(worldState -> {
                             TileEntity tileEntity = worldState.getTileEntity();
-                            if (!(tileEntity instanceof IPipeTilepipeTile)) {
-                                return false;
+                            if (!(tileEntity instanceof IPipeTile pipeTile)) {
+                                return PatternError.PLACEHOLDER;
                             }
-                            return ArrayUtils.contains(frameMaterials, pipeTile.getFrameMaterial());
+                            return ArrayUtils.contains(frameMaterials, pipeTile.getFrameMaterial()) ? null : PatternError.PLACEHOLDER;
                         }));
     }
 
     public static TraceabilityPredicate blocks(Block... block) {
         return new TraceabilityPredicate(
-                (worldState, patternState) -> ArrayUtils.contains(block, worldState.getBlockState().getBlock()),
+                worldState -> ArrayUtils.contains(block, worldState.getBlockState().getBlock()) ? null : PatternError.PLACEHOLDER,
                 getCandidates(Arrays.stream(block).map(Block::getDefaultState).toArray(IBlockState[]::new)));
     }
 
@@ -583,6 +589,10 @@ public IBlockPattern getSubstructure(String name) {
         return structures.get(name);
     }
 
+    public IBlockPattern getSubstructure(String name, String defult) {
+        return structures.get(structures.containsKey(name) ? name : defult);
+    }
+
     @Override
     public void onRemoval() {
         super.onRemoval();
@@ -801,6 +811,24 @@ public List getPreviewShapes(String substructureName) {
         return infos;
     }
 
+    /**
+     * Autobuilds the multiblock, using the {@code substructure} string to select the substructure, or the main structure if invalid.
+     * Then delegates to {@link IBlockPattern#autoBuild(EntityPlayer, Map)}
+     */
+    public void autoBuild(EntityPlayer player, Map map) {
+        // todo lang
+        RelativeDirection[] directions = new RelativeDirection[3];
+        Char2ObjectMap predicateMap = new Char2ObjectOpenHashMap<>();
+        IBlockPattern structure = getSubstructure(map.getOrDefault("substructure", "MAIN"), "MAIN");
+        char[][][] pattern = structure.getDefaultShape(predicateMap, directions);
+        // call another method so that subclasses can override this to handle the map without copying everything
+        autoBuildInternal(player, pattern, predicateMap, directions);
+    }
+
+    protected void autoBuildInternal(EntityPlayer player, char[][][] pattern, Char2ObjectMap predicateMap, RelativeDirection[] directions) {
+
+    }
+
     @SideOnly(Side.CLIENT)
     public String[] getDescription() {
         String key = String.format("gregtech.multiblock.%s.description", metaTileEntityId.getPath());
diff --git a/src/main/java/gregtech/api/pattern/PatternError.java b/src/main/java/gregtech/api/pattern/PatternError.java
index 091d53ee139..1cb96f80c88 100644
--- a/src/main/java/gregtech/api/pattern/PatternError.java
+++ b/src/main/java/gregtech/api/pattern/PatternError.java
@@ -8,10 +8,15 @@
 
 import org.jetbrains.annotations.Nullable;
 
+import java.util.Collections;
 import java.util.List;
 
 public class PatternError {
 
+    /**
+     * Return this for your pattern errors if you want them to be a default error with the pos of the BlockWorldState and candidates of the simple predicate's error.
+     */
+    public static final PatternError PLACEHOLDER = new PatternError(BlockPos.ORIGIN, Collections.emptyList());
     protected BlockPos pos;
     protected List> candidates;
 
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index c1eaae76b42..868a7caddd3 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -4,7 +4,6 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.pattern.PatternState;
 import gregtech.api.util.BlockInfo;
 
 import net.minecraft.block.state.IBlockState;
@@ -17,32 +16,32 @@
 import net.minecraftforge.fml.relauncher.SideOnly;
 
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.*;
-import java.util.function.BiPredicate;
-import java.util.function.Predicate;
+import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 public class TraceabilityPredicate {
 
     // Allow any block.
-    public static final TraceabilityPredicate ANY = new TraceabilityPredicate((worldState, patternState) -> true);
+    public static final TraceabilityPredicate ANY = new TraceabilityPredicate(worldState -> null);
     // Allow the air block.
     public static final TraceabilityPredicate AIR = new TraceabilityPredicate(
-            (worldState, patternState) -> worldState.getBlockState().getBlock().isAir(worldState.getBlockState(),
-                    worldState.getWorld(), worldState.getPos()));
+            worldState -> worldState.getBlockState().getBlock().isAir(worldState.getBlockState(),
+                    worldState.getWorld(), worldState.getPos()) ? null : new PatternError(worldState.getPos(), Collections.emptyList()));
     // Allow all heating coils, and require them to have the same type.
     public static Supplier HEATING_COILS = () -> new TraceabilityPredicate(
-            (worldState, patternState) -> GregTechAPI.HEATING_COILS.containsKey(worldState.getBlockState()),
+            worldState -> GregTechAPI.HEATING_COILS.containsKey(worldState.getBlockState()) ? null : PatternError.PLACEHOLDER,
             () -> GregTechAPI.HEATING_COILS.entrySet().stream()
                     // sort to make autogenerated jei previews not pick random coils each game load
                     .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
                     .map(entry -> new BlockInfo(entry.getKey(), null))
-                    .toArray(BlockInfo[]::new))
+                    .toArray(BlockInfo[]::new), null)
                             .addTooltips("gregtech.multiblock.pattern.error.coils");
 
-    public final List common = new ArrayList<>();
+    public final List simple = new ArrayList<>();
     protected boolean isCenter;
     protected boolean hasAir = false;
     protected boolean isSingle = true;
@@ -50,29 +49,23 @@ public class TraceabilityPredicate {
     public TraceabilityPredicate() {}
 
     public TraceabilityPredicate(TraceabilityPredicate predicate) {
-        common.addAll(predicate.common);
+        simple.addAll(predicate.simple);
         isCenter = predicate.isCenter;
         hasAir = predicate.hasAir;
         isSingle = predicate.isSingle;
     }
 
-    @Deprecated
-    public TraceabilityPredicate(Predicate predicate, Supplier candidates) {
-        common.add(new SimplePredicate(predicate, candidates));
+    public TraceabilityPredicate(Function predicate,
+                                 Supplier candidates, Function, BlockInfo> buildFunction) {
+        simple.add(new SimplePredicate(predicate, candidates, buildFunction));
     }
 
-    @Deprecated
-    public TraceabilityPredicate(Predicate predicate) {
-        this(predicate, null);
+    public TraceabilityPredicate(Function predicate, Supplier candidates) {
+        this(predicate, candidates, null);
     }
 
-    public TraceabilityPredicate(BiPredicate predicate,
-                                 Supplier candidates) {
-        common.add(new SimplePredicate(predicate, candidates));
-    }
-
-    public TraceabilityPredicate(BiPredicate predicate) {
-        this(predicate, null);
+    public TraceabilityPredicate(Function predicate) {
+        this(predicate, null, null);
     }
 
     /**
@@ -90,7 +83,7 @@ public boolean isCenter() {
 
     public TraceabilityPredicate sort() {
         // reverse so that all min layer and global counts are at the front
-        common.sort(Collections
+        simple.sort(Collections
                 .reverseOrder(Comparator.comparingInt(a -> ((a.minLayerCount + 1) * 100 + a.minGlobalCount))));
         return this;
     }
@@ -103,7 +96,7 @@ public TraceabilityPredicate sort() {
     public TraceabilityPredicate addTooltips(String... tips) {
         if (FMLCommonHandler.instance().getSide() == Side.CLIENT && tips.length > 0) {
             List tooltips = Arrays.stream(tips).collect(Collectors.toList());
-            common.forEach(predicate -> {
+            simple.forEach(predicate -> {
                 if (predicate.candidates == null) return;
                 if (predicate.toolTips == null) {
                     predicate.toolTips = new ArrayList<>();
@@ -121,8 +114,8 @@ public TraceabilityPredicate addTooltips(String... tips) {
      */
     public List> getCandidates() {
         List> candidates = new ArrayList<>();
-        for (TraceabilityPredicate.SimplePredicate common : common) {
-            candidates.add(common.getCandidates());
+        for (TraceabilityPredicate.SimplePredicate pred : simple) {
+            candidates.add(pred.getCandidates());
         }
         return candidates;
     }
@@ -141,7 +134,7 @@ public TraceabilityPredicate addTooltip(String langKey, Object... data) {
      * Set the minimum number of candidate blocks.
      */
     public TraceabilityPredicate setMinGlobalLimited(int min) {
-        common.forEach(p -> p.minGlobalCount = min);
+        simple.forEach(p -> p.minGlobalCount = min);
         return this;
     }
 
@@ -153,7 +146,7 @@ public TraceabilityPredicate setMinGlobalLimited(int min, int previewCount) {
      * Set the maximum number of candidate blocks.
      */
     public TraceabilityPredicate setMaxGlobalLimited(int max) {
-        common.forEach(p -> p.maxGlobalCount = max);
+        simple.forEach(p -> p.maxGlobalCount = max);
         return this;
     }
 
@@ -165,7 +158,7 @@ public TraceabilityPredicate setMaxGlobalLimited(int max, int previewCount) {
      * Set the minimum number of candidate blocks for each aisle layer.
      */
     public TraceabilityPredicate setMinLayerLimited(int min) {
-        common.forEach(p -> p.minLayerCount = min);
+        simple.forEach(p -> p.minLayerCount = min);
         return this;
     }
 
@@ -177,7 +170,7 @@ public TraceabilityPredicate setMinLayerLimited(int min, int previewCount) {
      * Set the maximum number of candidate blocks for each aisle layer.
      */
     public TraceabilityPredicate setMaxLayerLimited(int max) {
-        common.forEach(p -> p.maxLayerCount = max);
+        simple.forEach(p -> p.maxLayerCount = max);
         return this;
     }
 
@@ -198,15 +191,21 @@ public TraceabilityPredicate setExactLimit(int limit) {
      * Set the number of it appears in JEI pages. It only affects JEI preview. (The specific number)
      */
     public TraceabilityPredicate setPreviewCount(int count) {
-        common.forEach(p -> p.previewCount = count);
+        simple.forEach(p -> p.previewCount = count);
         return this;
     }
 
-    public boolean test(BlockWorldState worldState, PatternState patternState,
+    public PatternError test(BlockWorldState worldState,
                         Object2IntMap globalCache,
                         Object2IntMap layerCache) {
-        return common.stream()
-                .anyMatch(predicate -> predicate.testLimited(worldState, patternState, globalCache, layerCache));
+        PatternError lastError = null;
+        for (SimplePredicate pred : simple) {
+            PatternError error = pred.testLimited(worldState, globalCache, layerCache);
+            if (error == null) return null;
+
+            lastError = error;
+        }
+        return lastError == PatternError.PLACEHOLDER ? new PatternError(worldState.getPos(), getCandidates()) : lastError;
     }
 
     public TraceabilityPredicate or(TraceabilityPredicate other) {
@@ -218,17 +217,16 @@ public TraceabilityPredicate or(TraceabilityPredicate other) {
                 newPredicate.isSingle = this.isSingle && other.isSingle;
             }
             newPredicate.hasAir = newPredicate.hasAir || this == AIR || other == AIR;
-            newPredicate.common.addAll(other.common);
+            newPredicate.simple.addAll(other.simple);
             return newPredicate;
         }
         return this;
     }
 
     public static class SimplePredicate {
-
         public final Supplier candidates;
-
-        public final BiPredicate predicate;
+        public final Function predicate;
+        public final Function, BlockInfo> buildFunction;
 
         @SideOnly(Side.CLIENT)
         private List toolTips;
@@ -240,17 +238,11 @@ public static class SimplePredicate {
 
         public int previewCount = -1;
 
-        @Deprecated
-        public SimplePredicate(Predicate predicate, Supplier candidates) {
-            // legacy compat
-            this.predicate = (state, info) -> predicate.test(state);
-            this.candidates = candidates;
-        }
-
-        public SimplePredicate(BiPredicate predicate,
-                               Supplier candidates) {
+        public SimplePredicate(Function predicate,
+                               Supplier candidates, @Nullable Function, BlockInfo> buildFunction) {
             this.predicate = predicate;
             this.candidates = candidates;
+            this.buildFunction = buildFunction;
         }
 
         @SideOnly(Side.CLIENT)
@@ -288,42 +280,38 @@ public List getToolTips(TraceabilityPredicate predicates) {
             return result;
         }
 
-        public boolean testRaw(BlockWorldState worldState, PatternState patternState) {
-            return predicate.test(worldState, patternState);
+        public PatternError testRaw(BlockWorldState worldState) {
+            return predicate.apply(worldState);
         }
 
-        public boolean testLimited(BlockWorldState worldState, PatternState patternState,
+        public PatternError testLimited(BlockWorldState worldState,
                                    Object2IntMap globalCache,
                                    Object2IntMap layerCache) {
-            return testGlobal(worldState, patternState, globalCache) && testLayer(worldState, patternState, layerCache);
+            PatternError error = testGlobal(worldState, globalCache);
+            if (error != null) return error;
+            return testLayer(worldState, layerCache);
         }
 
-        public boolean testGlobal(BlockWorldState worldState, PatternState patternState,
-                                  Object2IntMap cache) {
-            if (minGlobalCount == -1 && maxGlobalCount == -1 || cache == null)
-                return predicate.test(worldState, patternState);
-
-            boolean base = predicate.test(worldState, patternState);
-            int count = cache.getInt(this);
-            count += base ? 1 : 0;
-            cache.put(this, count);
-            if (maxGlobalCount == -1 || count <= maxGlobalCount) return base;
-            patternState.setError(new SinglePredicateError(this, 0));
-            return false;
+        public PatternError testGlobal(BlockWorldState worldState, Object2IntMap cache) {
+            PatternError result = predicate.apply(worldState);
+
+            if ((minGlobalCount == -1 && maxGlobalCount == -1) || cache == null || result != null) return result;
+
+            int count = cache.put(this, cache.getInt(this) + 1) + 1;
+            if (maxGlobalCount == -1 || count <= maxGlobalCount) return null;
+
+            return new SinglePredicateError(this, 0);
         }
 
-        public boolean testLayer(BlockWorldState worldState, PatternState patternState,
-                                 Object2IntMap cache) {
-            if (minLayerCount == -1 && maxLayerCount == -1 || cache == null)
-                return predicate.test(worldState, patternState);
-
-            boolean base = predicate.test(worldState, patternState);
-            int count = cache.getInt(this);
-            count += base ? 1 : 0;
-            cache.put(this, count);
-            if (maxLayerCount == -1 || count <= maxLayerCount) return base;
-            patternState.setError(new SinglePredicateError(this, 2));
-            return false;
+        public PatternError testLayer(BlockWorldState worldState, Object2IntMap cache) {
+            PatternError result = predicate.apply(worldState);
+
+            if ((minLayerCount == -1 && maxLayerCount == -1) || cache == null || result != null) return result;
+
+            int count = cache.put(this, cache.getInt(this) + 1) + 1;
+            if (maxLayerCount == -1 || count <= maxLayerCount) return null;
+
+            return new SinglePredicateError(this, 2);
         }
 
         public List getCandidates() {
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index fa752b47d68..3c3262a081d 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -4,12 +4,14 @@
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.OriginOffset;
+import gregtech.api.pattern.PatternError;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
 import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
@@ -27,8 +29,7 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 
 public class BlockPattern implements IBlockPattern {
 
@@ -249,8 +250,11 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
 
                 // GTLog.logger.info("Checked pos at " + charPos + " with flip " + flip);
 
-                boolean result = predicate.test(worldState, state, globalCount, layerCount);
-                if (!result) return false;
+                PatternError result = predicate.test(worldState, globalCount, layerCount);
+                if (result != null) {
+                    state.setError(result);
+                    return false;
+                }
 
                 charPos.offset(absoluteChar);
             }
@@ -274,14 +278,15 @@ public int getRepetitionCount(int aisleI) {
         return aisles[aisleI].actualRepeats;
     }
 
+    // todo add support for all aisle strategies
     @Override
-    public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map, RelativeDirection[] directions) {
+    public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map, @Nullable RelativeDirection[] directions) {
         // for each symbol, which simple predicate is being used
         // this advances whenever a minimum has been satisfied(if any), or a maximum has been reached(if any)
         // preview counts are treated as exactly that many
         Char2IntMap predicateIndex = new Char2IntOpenHashMap();
         // candidates to be passed into MultiblockShapeInfo
-//        Char2ObjectMap candidates = new Char2ObjectOpenHashMap<>();
+        // Char2ObjectMap candidates = new Char2ObjectOpenHashMap<>();
         // cache for candidates
         Object2CharMap infos = new Object2CharOpenHashMap<>();
         Object2IntMap globalCache = new Object2IntOpenHashMap<>();
@@ -294,7 +299,7 @@ public int getRepetitionCount(int aisleI) {
         int aisleOffset = 0;
 
         for (TraceabilityPredicate predicate : predicates.values()) {
-            for (TraceabilityPredicate.SimplePredicate simple : predicate.common) {
+            for (TraceabilityPredicate.SimplePredicate simple : predicate.simple) {
                 if (infos.put(simple, currentChar) == 0) {
                     map.put(currentChar, simple);
                     currentChar++;
@@ -312,8 +317,8 @@ public int getRepetitionCount(int aisleI) {
                         char c = aisles[aisleI].charAt(stringI, charI);
                         TraceabilityPredicate predicate = predicates.get(c);
                         // we used up all the simple predicates, just let the second pass fill them in
-                        if (predicateIndex.get(c) >= predicate.common.size()) continue;
-                        TraceabilityPredicate.SimplePredicate simple = predicate.common.get(predicateIndex.get(c));
+                        if (predicateIndex.get(c) >= predicate.simple.size()) continue;
+                        TraceabilityPredicate.SimplePredicate simple = predicate.simple.get(predicateIndex.get(c));
 
                         if (simple.candidates == null) continue;
 
@@ -333,8 +338,8 @@ public int getRepetitionCount(int aisleI) {
                                 (next.minGlobalCount == -1 || globalCount == next.minGlobalCount)) {
                             // if the current predicate is used, move until the next free one
                             int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
-                            if (newIndex >= predicate.common.size()) break;
-                            next = predicate.common.get(newIndex);
+                            if (newIndex >= predicate.simple.size()) break;
+                            next = predicate.simple.get(newIndex);
                             globalCount = globalCache.getInt(next);
                             layerCount = layerCache.getInt(next);
                         }
@@ -358,7 +363,7 @@ public int getRepetitionCount(int aisleI) {
 
                         char c = aisles[aisleI].charAt(stringI, charI);
                         TraceabilityPredicate predicate = predicates.get(c);
-                        TraceabilityPredicate.SimplePredicate next = predicate.common.get(predicateIndex.get(c));
+                        TraceabilityPredicate.SimplePredicate next = predicate.simple.get(predicateIndex.get(c));
 
                         int layerCount = layerCache.getInt(next);
                         int globalCount = globalCache.getInt(next);
@@ -370,12 +375,12 @@ public int getRepetitionCount(int aisleI) {
                                 (next.maxGlobalCount != -1 && globalCount == next.maxGlobalCount)) {
                             // if the current predicate is used, move until the next free one
                             int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
-                            if (newIndex >= predicate.common.size()) {
+                            if (newIndex >= predicate.simple.size()) {
                                 GTLog.logger.warn("Failed to generate default structure pattern.",
                                         new Throwable());
                                 newIndex = 0;
                             }
-                            next = predicate.common.get(newIndex);
+                            next = predicate.simple.get(newIndex);
                             globalCount = globalCache.getInt(next);
                             layerCount = layerCache.getInt(next);
                         }
@@ -393,10 +398,14 @@ public int getRepetitionCount(int aisleI) {
             }
         }
 
-        System.arraycopy(this.directions, 0, directions, 0, 3);
+        if (directions != null) System.arraycopy(this.directions, 0, directions, 0, 3);
         return pattern.toArray(new char[0][][]);
     }
 
+    @Override
+    public void autoBuild(EntityPlayer player, Map map) {
+    }
+
     @Override
     public PatternState getPatternState() {
         return state;
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index 09067ff53c7..c71a2eedc46 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -5,6 +5,7 @@
 import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.OriginOffset;
+import gregtech.api.pattern.PatternError;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.RelativeDirection;
@@ -13,6 +14,7 @@
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 
 import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
@@ -25,6 +27,7 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.Map;
 import java.util.function.BiFunction;
 
 public class ExpandablePattern implements IBlockPattern {
@@ -162,8 +165,11 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
                         !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
             }
 
-            boolean result = predicate.test(worldState, state, globalCount, null);
-            if (!result) return false;
+            PatternError result = predicate.test(worldState, globalCount, null);
+            if (result != null) {
+                state.setError(result);
+                return false;
+            }
         }
 
         for (Object2IntMap.Entry entry : globalCount.object2IntEntrySet()) {
@@ -177,10 +183,13 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
 
     @Override
     public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map, RelativeDirection[] directions) {
-        // todo undo
+        // todo maybe add this and autobuild
         return null;
     }
 
+    @Override
+    public void autoBuild(EntityPlayer player, Map map) {}
+
     @Override
     public PatternState getPatternState() {
         return state;
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index 8e4c0901999..5be7239d972 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -11,6 +11,7 @@
 
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 
+import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
@@ -19,6 +20,8 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.Map;
+
 public interface IBlockPattern {
 
     /**
@@ -57,10 +60,17 @@ boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
      * not exist.
      * 
      * @param map Pass in an empty map to receive a populated map with the chars in the return mapping to a simpel predicate.
-     * @param directions Pass in an array of length 3 to get the directions for the preview.
+     * @param directions Pass in an array of length 3 to get the directions for the preview, or null if you don't want it.
      */
     // peak nullability, basically the return value can be null but if it is not then no arrays can be null
-    char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map, RelativeDirection[] directions);
+    char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map, @Nullable RelativeDirection[] directions);
+
+    /**
+     * Autobuilds a pattern, using the player inventory(and maybe AE soontm) items if in survival.
+     * @param player The player initiating this autobuild.
+     * @param map The map from the multiblock builder.
+     */
+    void autoBuild(EntityPlayer player, Map map);
 
 
     /**
@@ -71,7 +81,7 @@ boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
     PatternState getPatternState();
 
     /**
-     * Clears the cache for checkPatternFastAt(...) in case something in the pattern is changed.
+     * Clears the cache for checkPatternFastAt(...) in case something in the pattern is changed. Default impl just getCache and then clears it.
      */
     default void clearCache() {
         getCache().clear();
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index c0ddd3e4b80..0ce0ff692ea 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -11,6 +11,8 @@
 import gregtech.api.pattern.PatternError;
 import gregtech.api.util.GTUtility;
 
+import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
+
 import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.item.Item;
@@ -41,8 +43,10 @@
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -60,11 +64,11 @@ public ActionResult onItemRightClick(World world, EntityPlayer player
         return ActionResult.newResult(EnumActionResult.SUCCESS, heldItem);
     }
 
-    public static Object2IntMap getMap(ItemStack target) {
-        if (!target.hasTagCompound()) return new Object2IntOpenHashMap<>();
+    public static Map getMap(ItemStack target) {
+        if (!target.hasTagCompound()) return Collections.emptyMap();
 
         NBTTagCompound tag = target.getTagCompound().getCompoundTag("MultiblockBuilder");
-        Object2IntMap result = new Object2IntOpenHashMap<>();
+        Map result = new HashMap<>();
 
         for (String str : tag.getKeySet()) {
             NBTTagCompound entry = tag.getCompoundTag(str);
@@ -72,7 +76,7 @@ public static Object2IntMap getMap(ItemStack target) {
             String val = entry.getString("Value");
             if (key.isEmpty() || val.isEmpty()) continue;
 
-            result.put(key, Integer.parseInt(val));
+            result.put(key, val);
         }
 
         return result;
@@ -146,12 +150,10 @@ public ModularPanel buildUI(HandGuiData guiData, PanelSyncManager guiSyncManager
                                 .size(8 * 18, 18)
                                 .child(new TextFieldWidget()
                                         .left(0).width(4 * 18)
-                                        .setValidator(str -> str.replaceAll("\\W", ""))
                                         .value(keyValues[s])
                                         .background(GTGuiTextures.DISPLAY))
                                 .child(new TextFieldWidget()
                                         .left(4 * 18).width(4 * 18)
-                                        .setValidator(str -> str.replaceAll("\\D", ""))
                                         .value(valueValues[s])
                                         .background(GTGuiTextures.DISPLAY))));
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
index e1f5091f866..746c58c93d0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
@@ -11,6 +11,7 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.RecipeMapPrimitiveMultiblockController;
+import gregtech.api.pattern.PatternError;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
@@ -48,7 +49,7 @@
 public class MetaTileEntityPrimitiveBlastFurnace extends RecipeMapPrimitiveMultiblockController {
 
     private static final TraceabilityPredicate SNOW_PREDICATE = new TraceabilityPredicate(
-            bws -> GTUtility.isBlockSnow(bws.getBlockState()));
+            bws -> GTUtility.isBlockSnow(bws.getBlockState()) ? null : PatternError.PLACEHOLDER);
 
     public MetaTileEntityPrimitiveBlastFurnace(ResourceLocation metaTileEntityId) {
         super(metaTileEntityId, RecipeMaps.PRIMITIVE_BLAST_FURNACE_RECIPES);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index 09994f86b94..e24f045eee4 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -25,6 +25,7 @@
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
 import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.MultiblockShapeInfo;
+import gregtech.api.pattern.PatternError;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.FactoryExpandablePattern;
 import gregtech.api.pattern.pattern.IBlockPattern;
@@ -274,7 +275,7 @@ protected IBlockPattern createStructurePattern() {
     @NotNull
     protected TraceabilityPredicate filterPredicate() {
         return new TraceabilityPredicate(
-                (worldState, patternState) -> GregTechAPI.CLEANROOM_FILTERS.containsKey(worldState.getBlockState()),
+                worldState -> GregTechAPI.CLEANROOM_FILTERS.containsKey(worldState.getBlockState()) ? null : PatternError.PLACEHOLDER,
                 () -> GregTechAPI.CLEANROOM_FILTERS.entrySet().stream()
                         .filter(entry -> entry.getValue().getCleanroomType() != null)
                         .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
@@ -303,22 +304,22 @@ protected IBlockState getGlassState() {
     @NotNull
     protected static TraceabilityPredicate doorPredicate() {
         return new TraceabilityPredicate(
-                (worldState, patternState) -> worldState.getBlockState().getBlock() instanceof BlockDoor);
+                worldState -> worldState.getBlockState().getBlock() instanceof BlockDoor ? null : PatternError.PLACEHOLDER);
     }
 
     @NotNull
     protected TraceabilityPredicate innerPredicate() {
-        return new TraceabilityPredicate((worldState, patternState) -> {
+        return new TraceabilityPredicate(worldState -> {
             // all non-MetaTileEntities are allowed inside by default
             TileEntity tileEntity = worldState.getTileEntity();
-            if (!(tileEntity instanceof IGregTechTileEntity)) return true;
+            if (!(tileEntity instanceof IGregTechTileEntity)) return null;
 
             MetaTileEntity metaTileEntity = ((IGregTechTileEntity) tileEntity).getMetaTileEntity();
 
             // always ban other cleanrooms, can cause problems otherwise
-            if (metaTileEntity instanceof ICleanroomProvider) return false;
+            if (metaTileEntity instanceof ICleanroomProvider) return null;
 
-            return !isMachineBanned(metaTileEntity);
+            return isMachineBanned(metaTileEntity) ? PatternError.PLACEHOLDER : null;
         });
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index fd26fafbe7a..afddc30c3c2 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -289,7 +289,7 @@ protected IBlockState getGlassState() {
     }
 
     protected static final Supplier BATTERY_PREDICATE = () -> new TraceabilityPredicate(
-            (worldState, patternState) -> GregTechAPI.PSS_BATTERIES.containsKey(worldState.getBlockState()),
+            worldState -> GregTechAPI.PSS_BATTERIES.containsKey(worldState.getBlockState()) ? null : PatternError.PLACEHOLDER,
             () -> GregTechAPI.PSS_BATTERIES.entrySet().stream()
                     .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
                     .map(entry -> new BlockInfo(entry.getKey(), null))
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index 1723d967579..19b75c6cb52 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -12,6 +12,7 @@
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.MultiblockShapeInfo;
+import gregtech.api.pattern.PatternError;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.FactoryExpandablePattern;
 import gregtech.api.pattern.pattern.IBlockPattern;
@@ -189,9 +190,10 @@ protected IBlockPattern createStructurePattern() {
     @NotNull
     private TraceabilityPredicate logPredicate() {
         return new TraceabilityPredicate(
-                (worldState, patternState) -> worldState.getBlockState().getBlock().isWood(worldState.getWorld(),
+                worldState -> worldState.getBlockState().getBlock().isWood(worldState.getWorld(),
                         worldState.getPos()) ||
-                        worldState.getBlockState().equals(MetaBlocks.BRITTLE_CHARCOAL.getDefaultState()));
+                        worldState.getBlockState().equals(MetaBlocks.BRITTLE_CHARCOAL.getDefaultState()) ? null :
+                PatternError.PLACEHOLDER);
     }
 
     private void setActive(boolean active) {
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index d1b46fdb402..a83e92f37e8 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -351,13 +351,12 @@ public void drawInfo(@NotNull Minecraft minecraft, int recipeWidth, int recipeHe
                     .get(rayTraceResult.getBlockPos());
             if (predicates != null) {
                 BlockWorldState worldState = new BlockWorldState();
-                PatternState patternState = new PatternState();
 
                 worldState.setWorld(renderer.world);
                 worldState.setPos(rayTraceResult.getBlockPos());
 
-                for (TraceabilityPredicate.SimplePredicate common : predicates.common) {
-                    if (common.testRaw(worldState, patternState)) {
+                for (TraceabilityPredicate.SimplePredicate common : predicates.simple) {
+                    if (common.testRaw(worldState) == null) {
                         predicateTips = common.getToolTips(predicates);
                         break;
                     }
@@ -419,7 +418,7 @@ public boolean handleClick(@NotNull Minecraft minecraft, int mouseX, int mouseY,
                 this.selected = selected;
                 TraceabilityPredicate predicate = patterns[currentRendererPage].predicateMap.get(this.selected);
                 if (predicate != null) {
-                    predicates.addAll(predicate.common);
+                    predicates.addAll(predicate.simple);
                     predicates.removeIf(p -> p.candidates == null);
                     this.father = predicate;
                     setItemStackGroup();

From 8ab848f527eb542f5dd781d51eac6dcc719bb403 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 13 Oct 2024 15:53:56 -0700
Subject: [PATCH 47/64] spotlessApply spotlessApply spotlessApply spotlessApply
 spotlessApply spotlessApply

---
 .../multiblock/MultiblockControllerBase.java  | 25 ++++++++--------
 .../api/pattern/MultiblockShapeInfo.java      |  9 +++---
 .../gregtech/api/pattern/PatternError.java    |  3 +-
 .../api/pattern/TraceabilityPredicate.java    | 29 ++++++++++++-------
 .../api/pattern/pattern/BlockPattern.java     |  6 ++--
 .../pattern/pattern/ExpandablePattern.java    |  7 ++---
 .../api/pattern/pattern/IBlockPattern.java    | 22 +++++++-------
 .../behaviors/MultiblockBuilderBehavior.java  |  8 ++---
 .../electric/MetaTileEntityCleanroom.java     |  6 ++--
 .../MetaTileEntityPowerSubstation.java        |  3 +-
 .../MetaTileEntityCharcoalPileIgniter.java    |  2 +-
 .../MultiblockInfoRecipeWrapper.java          |  1 -
 12 files changed, 63 insertions(+), 58 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index acdf79dbacf..c3ae364b6b1 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -29,10 +29,6 @@
 import gregtech.client.renderer.texture.cube.SimpleOrientedCubeRenderer;
 import gregtech.common.blocks.MetaBlocks;
 
-import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
-
-import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
-
 import net.minecraft.block.Block;
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.renderer.texture.TextureAtlasSprite;
@@ -58,8 +54,9 @@
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
 import codechicken.lib.vec.Rotation;
+import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
+import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 import org.apache.commons.lang3.ArrayUtils;
@@ -80,7 +77,6 @@
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -276,16 +272,18 @@ public static TraceabilityPredicate frames(Material... frameMaterials) {
                 .toArray(IBlockState[]::new))
                         .or(new TraceabilityPredicate(worldState -> {
                             TileEntity tileEntity = worldState.getTileEntity();
-                            if (!(tileEntity instanceof IPipeTile pipeTile)) {
+                            if (!(tileEntity instanceof IPipeTilepipeTile)) {
                                 return PatternError.PLACEHOLDER;
                             }
-                            return ArrayUtils.contains(frameMaterials, pipeTile.getFrameMaterial()) ? null : PatternError.PLACEHOLDER;
+                            return ArrayUtils.contains(frameMaterials, pipeTile.getFrameMaterial()) ? null :
+                                    PatternError.PLACEHOLDER;
                         }));
     }
 
     public static TraceabilityPredicate blocks(Block... block) {
         return new TraceabilityPredicate(
-                worldState -> ArrayUtils.contains(block, worldState.getBlockState().getBlock()) ? null : PatternError.PLACEHOLDER,
+                worldState -> ArrayUtils.contains(block, worldState.getBlockState().getBlock()) ? null :
+                        PatternError.PLACEHOLDER,
                 getCandidates(Arrays.stream(block).map(Block::getDefaultState).toArray(IBlockState[]::new)));
     }
 
@@ -812,7 +810,8 @@ public List getPreviewShapes(String substructureName) {
     }
 
     /**
-     * Autobuilds the multiblock, using the {@code substructure} string to select the substructure, or the main structure if invalid.
+     * Autobuilds the multiblock, using the {@code substructure} string to select the substructure, or the main
+     * structure if invalid.
      * Then delegates to {@link IBlockPattern#autoBuild(EntityPlayer, Map)}
      */
     public void autoBuild(EntityPlayer player, Map map) {
@@ -825,9 +824,9 @@ public void autoBuild(EntityPlayer player, Map map) {
         autoBuildInternal(player, pattern, predicateMap, directions);
     }
 
-    protected void autoBuildInternal(EntityPlayer player, char[][][] pattern, Char2ObjectMap predicateMap, RelativeDirection[] directions) {
-
-    }
+    protected void autoBuildInternal(EntityPlayer player, char[][][] pattern,
+                                     Char2ObjectMap predicateMap,
+                                     RelativeDirection[] directions) {}
 
     @SideOnly(Side.CLIENT)
     public String[] getDescription() {
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index 10d5a2cd4fa..a0bfffadf29 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -9,8 +9,6 @@
 import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
-import it.unimi.dsi.fastutil.chars.Char2ObjectMaps;
-
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.init.Blocks;
@@ -26,6 +24,7 @@
 import it.unimi.dsi.fastutil.chars.Char2IntMap;
 import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
+import it.unimi.dsi.fastutil.chars.Char2ObjectMaps;
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 
@@ -301,7 +300,8 @@ public static Builder builder() {
         return builder(RelativeDirection.FRONT, RelativeDirection.UP, RelativeDirection.RIGHT);
     }
 
-    public static MultiblockShapeInfo fromShape(RelativeDirection[] directions, char[][][] shape, Char2ObjectMap predicates) {
+    public static MultiblockShapeInfo fromShape(RelativeDirection[] directions, char[][][] shape,
+                                                Char2ObjectMap predicates) {
         if (shape == null) return null;
         Char2ObjectMap candidates = new Char2ObjectOpenHashMap<>();
         for (Char2ObjectMap.Entry entry : predicates.char2ObjectEntrySet()) {
@@ -312,7 +312,8 @@ public static MultiblockShapeInfo fromShape(RelativeDirection[] directions, char
             }
         }
 
-        return new MultiblockShapeInfo(Arrays.stream(shape).map(PatternAisle::new).toArray(PatternAisle[]::new), candidates,
+        return new MultiblockShapeInfo(Arrays.stream(shape).map(PatternAisle::new).toArray(PatternAisle[]::new),
+                candidates,
                 Char2ObjectMaps.emptyMap(), directions);
     }
 
diff --git a/src/main/java/gregtech/api/pattern/PatternError.java b/src/main/java/gregtech/api/pattern/PatternError.java
index 1cb96f80c88..b5ee500ac4c 100644
--- a/src/main/java/gregtech/api/pattern/PatternError.java
+++ b/src/main/java/gregtech/api/pattern/PatternError.java
@@ -14,7 +14,8 @@
 public class PatternError {
 
     /**
-     * Return this for your pattern errors if you want them to be a default error with the pos of the BlockWorldState and candidates of the simple predicate's error.
+     * Return this for your pattern errors if you want them to be a default error with the pos of the BlockWorldState
+     * and candidates of the simple predicate's error.
      */
     public static final PatternError PLACEHOLDER = new PatternError(BlockPos.ORIGIN, Collections.emptyList());
     protected BlockPos pos;
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index 868a7caddd3..29cc4e2d99a 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -30,16 +30,19 @@ public class TraceabilityPredicate {
     // Allow the air block.
     public static final TraceabilityPredicate AIR = new TraceabilityPredicate(
             worldState -> worldState.getBlockState().getBlock().isAir(worldState.getBlockState(),
-                    worldState.getWorld(), worldState.getPos()) ? null : new PatternError(worldState.getPos(), Collections.emptyList()));
+                    worldState.getWorld(), worldState.getPos()) ? null :
+                            new PatternError(worldState.getPos(), Collections.emptyList()));
     // Allow all heating coils, and require them to have the same type.
     public static Supplier HEATING_COILS = () -> new TraceabilityPredicate(
-            worldState -> GregTechAPI.HEATING_COILS.containsKey(worldState.getBlockState()) ? null : PatternError.PLACEHOLDER,
+            worldState -> GregTechAPI.HEATING_COILS.containsKey(worldState.getBlockState()) ? null :
+                    PatternError.PLACEHOLDER,
             () -> GregTechAPI.HEATING_COILS.entrySet().stream()
                     // sort to make autogenerated jei previews not pick random coils each game load
                     .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
                     .map(entry -> new BlockInfo(entry.getKey(), null))
-                    .toArray(BlockInfo[]::new), null)
-                            .addTooltips("gregtech.multiblock.pattern.error.coils");
+                    .toArray(BlockInfo[]::new),
+            null)
+                    .addTooltips("gregtech.multiblock.pattern.error.coils");
 
     public final List simple = new ArrayList<>();
     protected boolean isCenter;
@@ -56,7 +59,8 @@ public TraceabilityPredicate(TraceabilityPredicate predicate) {
     }
 
     public TraceabilityPredicate(Function predicate,
-                                 Supplier candidates, Function, BlockInfo> buildFunction) {
+                                 Supplier candidates,
+                                 Function, BlockInfo> buildFunction) {
         simple.add(new SimplePredicate(predicate, candidates, buildFunction));
     }
 
@@ -196,8 +200,8 @@ public TraceabilityPredicate setPreviewCount(int count) {
     }
 
     public PatternError test(BlockWorldState worldState,
-                        Object2IntMap globalCache,
-                        Object2IntMap layerCache) {
+                             Object2IntMap globalCache,
+                             Object2IntMap layerCache) {
         PatternError lastError = null;
         for (SimplePredicate pred : simple) {
             PatternError error = pred.testLimited(worldState, globalCache, layerCache);
@@ -205,7 +209,8 @@ public PatternError test(BlockWorldState worldState,
 
             lastError = error;
         }
-        return lastError == PatternError.PLACEHOLDER ? new PatternError(worldState.getPos(), getCandidates()) : lastError;
+        return lastError == PatternError.PLACEHOLDER ? new PatternError(worldState.getPos(), getCandidates()) :
+                lastError;
     }
 
     public TraceabilityPredicate or(TraceabilityPredicate other) {
@@ -224,6 +229,7 @@ public TraceabilityPredicate or(TraceabilityPredicate other) {
     }
 
     public static class SimplePredicate {
+
         public final Supplier candidates;
         public final Function predicate;
         public final Function, BlockInfo> buildFunction;
@@ -239,7 +245,8 @@ public static class SimplePredicate {
         public int previewCount = -1;
 
         public SimplePredicate(Function predicate,
-                               Supplier candidates, @Nullable Function, BlockInfo> buildFunction) {
+                               Supplier candidates,
+                               @Nullable Function, BlockInfo> buildFunction) {
             this.predicate = predicate;
             this.candidates = candidates;
             this.buildFunction = buildFunction;
@@ -285,8 +292,8 @@ public PatternError testRaw(BlockWorldState worldState) {
         }
 
         public PatternError testLimited(BlockWorldState worldState,
-                                   Object2IntMap globalCache,
-                                   Object2IntMap layerCache) {
+                                        Object2IntMap globalCache,
+                                        Object2IntMap layerCache) {
             PatternError error = testGlobal(worldState, globalCache);
             if (error != null) return error;
             return testLayer(worldState, layerCache);
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 3c3262a081d..fc98337ec10 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -280,7 +280,8 @@ public int getRepetitionCount(int aisleI) {
 
     // todo add support for all aisle strategies
     @Override
-    public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map, @Nullable RelativeDirection[] directions) {
+    public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map,
+                                                                     @Nullable RelativeDirection[] directions) {
         // for each symbol, which simple predicate is being used
         // this advances whenever a minimum has been satisfied(if any), or a maximum has been reached(if any)
         // preview counts are treated as exactly that many
@@ -403,8 +404,7 @@ public int getRepetitionCount(int aisleI) {
     }
 
     @Override
-    public void autoBuild(EntityPlayer player, Map map) {
-    }
+    public void autoBuild(EntityPlayer player, Map map) {}
 
     @Override
     public PatternState getPatternState() {
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index c71a2eedc46..a180a112745 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -3,7 +3,6 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
-import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.PatternError;
 import gregtech.api.pattern.TraceabilityPredicate;
@@ -11,8 +10,6 @@
 import gregtech.api.util.RelativeDirection;
 import gregtech.api.util.function.QuadFunction;
 
-import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
-
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.tileentity.TileEntity;
@@ -20,6 +17,7 @@
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
 
+import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
@@ -182,7 +180,8 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
     }
 
     @Override
-    public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map, RelativeDirection[] directions) {
+    public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map,
+                                                                     RelativeDirection[] directions) {
         // todo maybe add this and autobuild
         return null;
     }
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index 5be7239d972..df089ff7560 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -1,21 +1,17 @@
 package gregtech.api.pattern.pattern;
 
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.RelativeDirection;
 
-import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
-
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
 
+import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -59,20 +55,23 @@ boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
      * Gets the default shape, if the multiblock does not specify one. Return null to represent the default shape does
      * not exist.
      * 
-     * @param map Pass in an empty map to receive a populated map with the chars in the return mapping to a simpel predicate.
-     * @param directions Pass in an array of length 3 to get the directions for the preview, or null if you don't want it.
+     * @param map        Pass in an empty map to receive a populated map with the chars in the return mapping to a
+     *                   simpel predicate.
+     * @param directions Pass in an array of length 3 to get the directions for the preview, or null if you don't want
+     *                   it.
      */
     // peak nullability, basically the return value can be null but if it is not then no arrays can be null
-    char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map, @Nullable RelativeDirection[] directions);
+    char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map,
+                                                              @Nullable RelativeDirection[] directions);
 
     /**
      * Autobuilds a pattern, using the player inventory(and maybe AE soontm) items if in survival.
+     * 
      * @param player The player initiating this autobuild.
-     * @param map The map from the multiblock builder.
+     * @param map    The map from the multiblock builder.
      */
     void autoBuild(EntityPlayer player, Map map);
 
-
     /**
      * Gets the internal pattern state, you should use the one returned from
      * {@link IBlockPattern#checkPatternFastAt(World, BlockPos, EnumFacing, EnumFacing, boolean)} always
@@ -81,7 +80,8 @@ boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
     PatternState getPatternState();
 
     /**
-     * Clears the cache for checkPatternFastAt(...) in case something in the pattern is changed. Default impl just getCache and then clears it.
+     * Clears the cache for checkPatternFastAt(...) in case something in the pattern is changed. Default impl just
+     * getCache and then clears it.
      */
     default void clearCache() {
         getCache().clear();
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index 0ce0ff692ea..1ebf2fbb015 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -11,8 +11,6 @@
 import gregtech.api.pattern.PatternError;
 import gregtech.api.util.GTUtility;
 
-import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
-
 import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.item.Item;
@@ -39,8 +37,6 @@
 import com.cleanroommc.modularui.widgets.layout.Row;
 import com.cleanroommc.modularui.widgets.textfield.TextFieldWidget;
 import com.github.bsideup.jabel.Desugar;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.Collections;
@@ -182,8 +178,8 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
             if (!multiblock.isStructureFormed("MAIN")) {
-//                multiblock.auto("MAIN", getMap(player.getHeldItem(hand))).get(0).getMap(multiblock,
-//                        new BlockPos(0, 128, 0), new HashMap<>());
+                // multiblock.auto("MAIN", getMap(player.getHeldItem(hand))).get(0).getMap(multiblock,
+                // new BlockPos(0, 128, 0), new HashMap<>());
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index e24f045eee4..3025cb96f91 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -275,7 +275,8 @@ protected IBlockPattern createStructurePattern() {
     @NotNull
     protected TraceabilityPredicate filterPredicate() {
         return new TraceabilityPredicate(
-                worldState -> GregTechAPI.CLEANROOM_FILTERS.containsKey(worldState.getBlockState()) ? null : PatternError.PLACEHOLDER,
+                worldState -> GregTechAPI.CLEANROOM_FILTERS.containsKey(worldState.getBlockState()) ? null :
+                        PatternError.PLACEHOLDER,
                 () -> GregTechAPI.CLEANROOM_FILTERS.entrySet().stream()
                         .filter(entry -> entry.getValue().getCleanroomType() != null)
                         .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
@@ -304,7 +305,8 @@ protected IBlockState getGlassState() {
     @NotNull
     protected static TraceabilityPredicate doorPredicate() {
         return new TraceabilityPredicate(
-                worldState -> worldState.getBlockState().getBlock() instanceof BlockDoor ? null : PatternError.PLACEHOLDER);
+                worldState -> worldState.getBlockState().getBlock() instanceof BlockDoor ? null :
+                        PatternError.PLACEHOLDER);
     }
 
     @NotNull
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index afddc30c3c2..72444c9d198 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -289,7 +289,8 @@ protected IBlockState getGlassState() {
     }
 
     protected static final Supplier BATTERY_PREDICATE = () -> new TraceabilityPredicate(
-            worldState -> GregTechAPI.PSS_BATTERIES.containsKey(worldState.getBlockState()) ? null : PatternError.PLACEHOLDER,
+            worldState -> GregTechAPI.PSS_BATTERIES.containsKey(worldState.getBlockState()) ? null :
+                    PatternError.PLACEHOLDER,
             () -> GregTechAPI.PSS_BATTERIES.entrySet().stream()
                     .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
                     .map(entry -> new BlockInfo(entry.getKey(), null))
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index 19b75c6cb52..fa232bc1573 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -193,7 +193,7 @@ private TraceabilityPredicate logPredicate() {
                 worldState -> worldState.getBlockState().getBlock().isWood(worldState.getWorld(),
                         worldState.getPos()) ||
                         worldState.getBlockState().equals(MetaBlocks.BRITTLE_CHARCOAL.getDefaultState()) ? null :
-                PatternError.PLACEHOLDER);
+                                PatternError.PLACEHOLDER);
     }
 
     private void setActive(boolean active) {
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index a83e92f37e8..348a848e7b0 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -8,7 +8,6 @@
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
-import gregtech.api.pattern.pattern.PatternState;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTUtility;
 import gregtech.api.util.ItemStackHashStrategy;

From 1df1c2d7df34fdccc2488410d338895acde8c621 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Sun, 22 Dec 2024 01:35:54 -0800
Subject: [PATCH 48/64] the calm before the refactor

---
 .../multiblock/IMultiblockPart.java           |   2 +-
 .../multiblock/MultiblockControllerBase.java  |  63 ++++----
 .../gregtech/api/pattern/GreggyBlockPos.java  |  28 ++--
 .../api/pattern/MultiblockShapeInfo.java      |  64 +-------
 .../gregtech/api/pattern/OriginOffset.java    |   5 -
 .../api/pattern/pattern/AisleStrategy.java    |  15 ++
 .../pattern/pattern/BasicAisleStrategy.java   |  25 ++-
 .../api/pattern/pattern/BlockPattern.java     | 151 ++++++++----------
 .../pattern/pattern/ExpandablePattern.java    |   7 +-
 .../pattern/pattern/FactoryBlockPattern.java  |  20 +--
 .../pattern/FactoryExpandablePattern.java     |  29 +---
 .../api/pattern/pattern/IBlockPattern.java    |  15 +-
 .../MetaTileEntityPowerSubstation.java        |  15 --
 .../MultiblockInfoRecipeWrapper.java          |   1 +
 14 files changed, 169 insertions(+), 271 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
index 830aa377535..fbec530f467 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
@@ -8,7 +8,7 @@ public interface IMultiblockPart {
     boolean isAttachedToMultiBlock();
 
     /**
-     * Use {@link IMultiblockPart#addToMultiBlock(MultiblockControllerBase, String)} insead!
+     * Use {@link IMultiblockPart#addToMultiBlock(MultiblockControllerBase, String)} instead!
      */
     @Deprecated
     default void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index c3ae364b6b1..2e6f74bf0a1 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -75,7 +75,6 @@
 import java.util.NavigableSet;
 import java.util.Objects;
 import java.util.TreeSet;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
 import java.util.function.BiPredicate;
 import java.util.function.Function;
@@ -93,8 +92,6 @@ public abstract class MultiblockControllerBase extends MetaTileEntity implements
 
     private final Map, List> multiblockAbilities = new HashMap<>();
 
-    // treeset here to get logn time for contains, and for automatically sorting itself
-    // prioritize the manually specified sorter first, defaulting to the hashcode for tiebreakers
     private final NavigableSet multiblockParts = new TreeSet<>(partComparator);
 
     protected EnumFacing upwardsFacing = EnumFacing.UP;
@@ -409,8 +406,7 @@ public void checkStructurePattern(String name) {
         GTLog.logger.info(
                 "structure check for " + getClass().getSimpleName() + " took " + (System.nanoTime() - time) + " nanos");
 
-        if (result.getState().isValid()) { // structure check succeeds
-            // if structure isn't formed or cache fails
+        if (result.getState().isValid()) {
             if (result.isFormed()) {
                 // fast rebuild parts
                 if (result.getState() == PatternState.EnumCheckState.VALID_UNCACHED) {
@@ -419,7 +415,6 @@ public void checkStructurePattern(String name) {
                     List> addedParts = new ArrayList<>();
 
                     forEachMultiblockPart(name, part -> {
-                        // this part is already added, so igore it
                         if (multiblockParts.contains(part)) return true;
 
                         // todo move below into separate check
@@ -447,20 +442,20 @@ public void checkStructurePattern(String name) {
                 return;
             }
 
-            AtomicBoolean valid = new AtomicBoolean(true);
+            boolean[] valid = new boolean[1];
+            valid[0] = true;
 
             forEachMultiblockPart(name, part -> {
                 if (part.isAttachedToMultiBlock() && !part.canPartShare(this, name)) {
-                    valid.set(false);
+                    valid[0] = false;
                     return false;
                 }
                 return true;
             });
 
             // since the structure isn't formed, don't invalidate, instead just don't form it
-            if (!valid.get()) return;
+            if (!valid[0]) return;
 
-            // normal rebuild parts
             forEachMultiblockPart(name, part -> {
                 // parts *should* not have this controller added
                 multiblockParts.add(part);
@@ -477,8 +472,8 @@ public void checkStructurePattern(String name) {
             }
 
             formStructure(name);
-        } else { // structure check fails
-            if (result.isFormed()) { // invalidate if not already
+        } else {
+            if (result.isFormed()) {
                 invalidateStructure(name);
             }
         }
@@ -640,15 +635,13 @@ public NBTTagCompound writeToNBT(NBTTagCompound data) {
     public void writeInitialSyncData(PacketBuffer buf) {
         super.writeInitialSyncData(buf);
         buf.writeByte(upwardsFacing.getIndex());
-        // todo see if necessary to sync this
-        // buf.writeLong(GTUtility.boolArrToLong(s));
+        // todo see if necessary to sync structure formed
     }
 
     @Override
     public void receiveInitialSyncData(PacketBuffer buf) {
         super.receiveInitialSyncData(buf);
         this.upwardsFacing = EnumFacing.VALUES[buf.readByte()];
-        // GTUtility.longToBoolArr(buf.readLong(), structuresFormed);
     }
 
     @Override
@@ -658,7 +651,7 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
             this.upwardsFacing = EnumFacing.VALUES[buf.readByte()];
             scheduleRenderUpdate();
         } else if (dataId == STRUCTURE_FORMED) {
-            // it forces me so uh yay
+            // todo rewrite this entire thing :skull:
             String name = buf.readString(65536);
             if ("null".equals(name)) {
                 for (IBlockPattern pattern : structures.values()) {
@@ -794,15 +787,12 @@ public List getMatchingShapes() {
     public List getPreviewShapes(String substructureName) {
         List infos = getMatchingShapes();
 
-        // if there is no overriden getMatchingShapes() just return the default one
         if (infos.isEmpty()) {
             // for jei and stuff
             if (getSubstructure(substructureName) == null) createStructurePatterns();
-
             MultiblockShapeInfo info = MultiblockShapeInfo.fromShape(getSubstructure(substructureName));
 
             if (info == null) return Collections.emptyList();
-
             return Collections.singletonList(info);
         }
 
@@ -812,21 +802,40 @@ public List getPreviewShapes(String substructureName) {
     /**
      * Autobuilds the multiblock, using the {@code substructure} string to select the substructure, or the main
      * structure if invalid.
-     * Then delegates to {@link IBlockPattern#autoBuild(EntityPlayer, Map)}
      */
     public void autoBuild(EntityPlayer player, Map map) {
         // todo lang
         RelativeDirection[] directions = new RelativeDirection[3];
         Char2ObjectMap predicateMap = new Char2ObjectOpenHashMap<>();
-        IBlockPattern structure = getSubstructure(map.getOrDefault("substructure", "MAIN"), "MAIN");
-        char[][][] pattern = structure.getDefaultShape(predicateMap, directions);
-        // call another method so that subclasses can override this to handle the map without copying everything
-        autoBuildInternal(player, pattern, predicateMap, directions);
+        IBlockPattern structure = getSubstructure(map.getOrDefault("substructure", "MAIN"));
+        char[][][] pattern = structure.getDefaultShape(predicateMap, map, directions);
+
+        Char2ObjectMap buildCandidates = new Char2ObjectOpenHashMap<>();
+        for (Char2ObjectMap.Entry entry : predicateMap.char2ObjectEntrySet()) {
+            if (entry.getValue().buildFunction != null) {
+                buildCandidates.put(entry.getCharKey(), entry.getValue().buildFunction.apply(map));
+            }
+        }
     }
 
-    protected void autoBuildInternal(EntityPlayer player, char[][][] pattern,
-                                     Char2ObjectMap predicateMap,
-                                     RelativeDirection[] directions) {}
+    /**
+     * @return True iff the item has been successfully removed from the player's inventory(or AE system, satchels, etc)
+     *         or the player is in creative mode.
+     *         Currently only removes from the player's main inventory. The count of the passed in stack does not
+     *         matter, only 1 is removed from the player.
+     */
+    protected static boolean hasAndRemoveItem(EntityPlayer player, ItemStack stack) {
+        if (player.isCreative()) return true;
+
+        for (ItemStack ztack : player.inventory.mainInventory) {
+            if (!ztack.isEmpty() && ztack.isItemEqual(stack)) {
+                ztack.setCount(ztack.getCount() - 1);
+                return true;
+            }
+        }
+
+        return false;
+    }
 
     @SideOnly(Side.CLIENT)
     public String[] getDescription() {
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index 9494e6f25c4..c6274f67c58 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -13,8 +13,7 @@
 /**
  * A possibly saner(and mutable) alternative to BlockPos, where getters and setters use indices and axis instead of
  * separate names to avoid stupid code. All methods that return GreggyBlockPos return {@code this} whenever possible,
- * except for .copy() which returns
- * a new instance.
+ * finding the one method that returns a new instance will be left as an exercise to the reader.
  */
 public class GreggyBlockPos {
 
@@ -115,7 +114,6 @@ public GreggyBlockPos z(int val) {
     public GreggyBlockPos setAxisRelative(EnumFacing.Axis a1, EnumFacing.Axis a2, int p1, int p2, int p3) {
         set(a1, p1);
         set(a2, p2);
-        // the 3 ordinals add up to 3, so to find the third axis just subtract the other 2 ordinals from 3
         pos[3 - a1.ordinal() - a2.ordinal()] = p3;
         return this;
     }
@@ -131,7 +129,7 @@ public GreggyBlockPos offset(EnumFacing facing, int amount) {
     }
 
     /**
-     * Sets this pos's position to be the same as the other one
+     * Sets this pos'sposition to be the same as the other one
      * 
      * @param other The other pos to get position from
      */
@@ -141,7 +139,7 @@ public GreggyBlockPos from(GreggyBlockPos other) {
     }
 
     /**
-     * BlockPos verion of {@link GreggyBlockPos#from(GreggyBlockPos)}
+     * BlockPos verison of {@link GreggyBlockPos#from(GreggyBlockPos)}
      */
     public GreggyBlockPos from(BlockPos other) {
         pos[0] = other.getX();
@@ -314,19 +312,17 @@ public boolean equals(Object other) {
     }
 
     /**
-     * Validates the facings argument to be of length 3 and use each pair of facings exactly once.
+     * Validates the enum array that each pair of enum ordinals happen exactly once(it is assumed the max ordinal is 5).
      */
-    public static void validateFacingsArray(EnumFacing... facings) {
+    public static > void validateFacingsArray(T[] facings) {
         if (facings.length != 3) throw new IllegalArgumentException("Facings must be array of length 3!");
 
-        // validate facings, int division so opposite facings mark the same element
-        boolean[] dirs = new boolean[3];
+        int x = 0;
         for (int i = 0; i < 3; i++) {
-            dirs[facings[i].ordinal() / 2] = true;
+            x |= 1 << (facings[i].ordinal() / 2);
         }
 
-        if (!(dirs[0] && dirs[1] && dirs[2]))
-            throw new IllegalArgumentException("The 3 facings must use each axis exactly once!");
+        if (x != 7) throw new IllegalArgumentException("The 3 facings must use each axis exactly once!");
     }
 
     /**
@@ -340,8 +336,6 @@ public static GreggyBlockPos startPos(GreggyBlockPos first, GreggyBlockPos secon
         for (int i = 0; i < 3; i++) {
             int a = first.get(facings[i].getAxis());
             int b = second.get(facings[i].getAxis());
-
-            // multiplying by -1 reverses the direction of min(...)
             int mult = facings[i].getAxisDirection().getOffset();
 
             start.set(facings[i].getAxis(), Math.min(a * mult, b * mult) * mult);
@@ -376,13 +370,10 @@ public static Iterable allInBox(GreggyBlockPos first, GreggyBloc
         for (int i = 0; i < 3; i++) {
             int a = first.get(facings[i].getAxis());
             int b = second.get(facings[i].getAxis());
-
-            // multiplying by -1 reverses the direction of min(...)
             int mult = facings[i].getAxisDirection().getOffset();
 
             start.set(facings[i].getAxis(), Math.min(a * mult, b * mult) * mult);
 
-            // length of the bounding box's axis
             length[i] = Math.abs(a - b);
         }
 
@@ -423,8 +414,7 @@ protected GreggyBlockPos computeNext() {
     }
 
     /**
-     * BlockPos version of {@link GreggyBlockPos#get(EnumFacing.Axis)}, for if the operation is small enough
-     * allocating more BlockPos is acceptable.
+     * BlockPos version of {@link GreggyBlockPos#get(EnumFacing.Axis)}.
      */
     public static int getAxis(BlockPos pos, EnumFacing.Axis axis) {
         return switch (axis) {
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index a0bfffadf29..7d1e6561577 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -12,13 +12,11 @@
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.init.Blocks;
-import net.minecraft.item.ItemStack;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.text.TextComponentString;
 import net.minecraft.util.text.TextComponentTranslation;
-import net.minecraft.world.World;
 
 import com.github.bsideup.jabel.Desugar;
 import it.unimi.dsi.fastutil.chars.Char2IntMap;
@@ -26,7 +24,6 @@
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectMaps;
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -78,45 +75,6 @@ public MultiblockShapeInfo(PatternAisle[] aisles, Char2ObjectMap symb
         symbols.defaultReturnValue(BlockInfo.EMPTY);
     }
 
-    /**
-     * Builds the given multiblock. The world is gotten from the player's world variable.
-     * 
-     * @param src     The multiblock in world to build from.
-     * @param player  The player autobuilding and whos inventory will be used.
-     * @param partial If true, if the player does not have enough materials, the multiblock will be built as much as
-     *                possible until they run out of a block to place.
-     *                If false, the autobuild will only succeed if the player can build the entire multiblock at once.
-     * @return Whether the autobuild was successful. If partial is false, returns true only if the entire multiblock was
-     *         build. If partial is true, returns false only if no blocks were placed.
-     */
-    public boolean autoBuild(MultiblockControllerBase src, EntityPlayer player, boolean partial) {
-        World world = player.world;
-        GreggyBlockPos pos = whereController(src.getClass());
-
-        EnumFacing frontFacing = src.getFrontFacing();
-        EnumFacing upFacing = src.getUpwardsFacing();
-
-        EnumFacing absoluteAisle = directions[0].getRelativeFacing(frontFacing, upFacing, false);
-        EnumFacing absoluteString = directions[1].getRelativeFacing(frontFacing, upFacing, false);
-        EnumFacing absoluteChar = directions[2].getRelativeFacing(frontFacing, upFacing, false);
-
-        GreggyBlockPos start = new GreggyBlockPos(src.getPos())
-                .offset(absoluteAisle, pos.x())
-                .offset(absoluteString, pos.y())
-                .offset(absoluteChar, pos.y());
-
-        Object2IntMap materials;
-        if (!partial) {
-            Char2IntMap count = getChars();
-            for (Char2IntMap.Entry entry : count.char2IntEntrySet()) {
-                BlockInfo info = symbols.get(entry.getCharKey());
-
-            }
-        }
-
-        return false;
-    }
-
     /**
      * Gets a map of where blocks should be placed, note that the controller is expected to be front facing SOUTH(and up
      * facing
@@ -320,7 +278,7 @@ public static MultiblockShapeInfo fromShape(RelativeDirection[] directions, char
     public static MultiblockShapeInfo fromShape(IBlockPattern pattern) {
         Char2ObjectMap predicates = new Char2ObjectOpenHashMap<>();
         RelativeDirection[] directions = new RelativeDirection[3];
-        char[][][] shape = pattern.getDefaultShape(predicates, directions);
+        char[][][] shape = pattern.getDefaultShape(predicates, null, directions);
         if (shape == null) return null;
         return fromShape(directions, shape, predicates);
     }
@@ -336,24 +294,7 @@ public Builder(RelativeDirection aisleDir, RelativeDirection stringDir, Relative
             directions[0] = aisleDir;
             directions[1] = stringDir;
             directions[2] = charDir;
-            int flags = 0;
-            for (int i = 0; i < 3; i++) {
-                switch (directions[i]) {
-                    case UP:
-                    case DOWN:
-                        flags |= 0x1;
-                        break;
-                    case LEFT:
-                    case RIGHT:
-                        flags |= 0x2;
-                        break;
-                    case FRONT:
-                    case BACK:
-                        flags |= 0x4;
-                        break;
-                }
-            }
-            if (flags != 0x7) throw new IllegalArgumentException("Must have 3 different axes!");
+            GreggyBlockPos.validateFacingsArray(directions);
         }
 
         public Builder aisle(String... data) {
@@ -415,6 +356,7 @@ public Builder dot(char symbol, int dot, String lang) {
                 return this;
             }
 
+            // todo make lang a Function, String> to account for builder map
             dotMap.put(symbol, new Dot(dot, lang));
             return this;
         }
diff --git a/src/main/java/gregtech/api/pattern/OriginOffset.java b/src/main/java/gregtech/api/pattern/OriginOffset.java
index 4684580cedb..3aa6c2435fe 100644
--- a/src/main/java/gregtech/api/pattern/OriginOffset.java
+++ b/src/main/java/gregtech/api/pattern/OriginOffset.java
@@ -9,11 +9,6 @@
  */
 public class OriginOffset {
 
-    /**
-     * length 3 because the opposite directions cancel out, it stores amount with the first direction in each pair(an
-     * even
-     * ordinal).
-     */
     protected final int[] offset = new int[3];
 
     public OriginOffset move(RelativeDirection dir, int amount) {
diff --git a/src/main/java/gregtech/api/pattern/pattern/AisleStrategy.java b/src/main/java/gregtech/api/pattern/pattern/AisleStrategy.java
index e46832b6ebf..142bf306d24 100644
--- a/src/main/java/gregtech/api/pattern/pattern/AisleStrategy.java
+++ b/src/main/java/gregtech/api/pattern/pattern/AisleStrategy.java
@@ -4,9 +4,14 @@
 import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
+import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.util.EnumFacing;
 
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
 import java.util.List;
+import java.util.Map;
 
 /**
  * A strategy to how aisles should be checked in patterns.
@@ -28,6 +33,15 @@ public abstract class AisleStrategy {
      */
     public abstract boolean check(boolean flip);
 
+    /**
+     * Gets the order in which aisles should be displayed, or built in case of autobuild.
+     * 
+     * @param map The map, the same one that is passed through
+     *            {@link gregtech.api.metatileentity.multiblock.MultiblockControllerBase#autoBuild(EntityPlayer, Map)}
+     * @return Array where the i-th element specifies that at offset i there would be aisle a_i
+     */
+    public abstract int @NotNull [] getDefaultAisles(@Nullable Map map);
+
     /**
      * Called at the start of a structure check.
      */
@@ -46,6 +60,7 @@ protected void finish(int[] dimensions, RelativeDirection[] directions, List map) {
+        IntList list = new IntArrayList();
+        for (int[] multi : multiAisles) {
+            for (int i = 0; i < multi[0]; i++) {
+                for (int j = multi[2]; j < multi[3]; j++) {
+                    for (int k = 0; k < aisles.get(j).minRepeats; k++) list.add(j);
+                }
+            }
+        }
+        return list.toIntArray();
+    }
+
     @Override
     protected void finish(int[] dimensions, RelativeDirection[] directions, List aisles) {
         super.finish(dimensions, directions, aisles);
 
-        // maybe just set the reference? but then the field cant be final so idk
+        // maybe just set the reference? but then the field cant be final
+        // todo figure out some way to retrieve all repeats from multi aisles, currently only last repeats
         this.aisles.addAll(aisles);
 
         BitSet covered = new BitSet(aisles.size());
@@ -105,7 +120,7 @@ protected void finish(int[] dimensions, RelativeDirection[] directions, List map,
+                                                                     Map keyMap,
                                                                      @Nullable RelativeDirection[] directions) {
         // for each symbol, which simple predicate is being used
         // this advances whenever a minimum has been satisfied(if any), or a maximum has been reached(if any)
@@ -297,7 +296,6 @@ public int getRepetitionCount(int aisleI) {
 
         // 0 is reserved for air
         char currentChar = 1;
-        int aisleOffset = 0;
 
         for (TraceabilityPredicate predicate : predicates.values()) {
             for (TraceabilityPredicate.SimplePredicate simple : predicate.simple) {
@@ -308,94 +306,88 @@ public int getRepetitionCount(int aisleI) {
             }
         }
 
+        int[] order = aisleStrategy.getDefaultAisles(keyMap);
+
         // first pass fills in all the minimum counts
-        for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
-            for (int repeats = 1; repeats <= aisles[aisleI].minRepeats; repeats++) {
-                pattern.add(new char[dimensions[1]][dimensions[2]]);
-                layerCache.clear();
-                for (int stringI = 0; stringI < dimensions[1]; stringI++) {
-                    for (int charI = 0; charI < dimensions[2]; charI++) {
-                        char c = aisles[aisleI].charAt(stringI, charI);
-                        TraceabilityPredicate predicate = predicates.get(c);
-                        // we used up all the simple predicates, just let the second pass fill them in
-                        if (predicateIndex.get(c) >= predicate.simple.size()) continue;
-                        TraceabilityPredicate.SimplePredicate simple = predicate.simple.get(predicateIndex.get(c));
-
-                        if (simple.candidates == null) continue;
-
-                        char info = infos.getChar(simple);
-                        int layerCount = layerCache.put(simple, layerCache.getInt(simple) + 1) + 1;
-                        int globalCount = globalCache.put(simple, globalCache.getInt(simple) + 1) + 1;
-
-                        pattern.get(aisleOffset)[stringI][charI] = info;
-
-                        TraceabilityPredicate.SimplePredicate next = simple;
-
-                        // don't need inequalities since everything is incremented once at a time
-                        // only put the minimum amount of parts possible
-                        // missing parts will be filled in the second pass
-                        while ((next.previewCount == -1 || globalCount == next.previewCount) &&
-                                (next.minLayerCount == -1 || layerCount == next.minLayerCount) &&
-                                (next.minGlobalCount == -1 || globalCount == next.minGlobalCount)) {
-                            // if the current predicate is used, move until the next free one
-                            int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
-                            if (newIndex >= predicate.simple.size()) break;
-                            next = predicate.simple.get(newIndex);
-                            globalCount = globalCache.getInt(next);
-                            layerCount = layerCache.getInt(next);
-                        }
+        for (int aisleOffset = 0; aisleOffset < order.length; aisleOffset++) {
+            pattern.add(new char[dimensions[1]][dimensions[2]]);
+            layerCache.clear();
+            for (int stringI = 0; stringI < dimensions[1]; stringI++) {
+                for (int charI = 0; charI < dimensions[2]; charI++) {
+                    char c = aisles[order[aisleOffset]].charAt(stringI, charI);
+                    TraceabilityPredicate predicate = predicates.get(c);
+                    // we used up all the simple predicates, just let the second pass fill them in
+                    if (predicateIndex.get(c) >= predicate.simple.size()) continue;
+                    TraceabilityPredicate.SimplePredicate simple = predicate.simple.get(predicateIndex.get(c));
+
+                    if (simple.candidates == null) continue;
+
+                    char info = infos.getChar(simple);
+                    int layerCount = layerCache.put(simple, layerCache.getInt(simple) + 1) + 1;
+                    int globalCount = globalCache.put(simple, globalCache.getInt(simple) + 1) + 1;
+
+                    pattern.get(aisleOffset)[stringI][charI] = info;
+
+                    TraceabilityPredicate.SimplePredicate next = simple;
+
+                    // don't need inequalities since everything is incremented once at a time
+                    // only put the minimum amount of parts possible
+                    // missing parts will be filled in the second pass
+                    while ((next.previewCount == -1 || globalCount == next.previewCount) &&
+                            (next.minLayerCount == -1 || layerCount == next.minLayerCount) &&
+                            (next.minGlobalCount == -1 || globalCount == next.minGlobalCount)) {
+                        // if the current predicate is used, move until the next free one
+                        int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
+                        if (newIndex >= predicate.simple.size()) break;
+                        next = predicate.simple.get(newIndex);
+                        globalCount = globalCache.getInt(next);
+                        layerCount = layerCache.getInt(next);
                     }
                 }
-                aisleOffset++;
             }
         }
 
         predicateIndex.clear();
-        aisleOffset = 0;
 
         // second pass fills everything else
-        for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
-            for (int repeats = 1; repeats <= aisles[aisleI].minRepeats; repeats++) {
-                layerCache.clear();
-                for (int stringI = 0; stringI < dimensions[1]; stringI++) {
-                    for (int charI = 0; charI < dimensions[2]; charI++) {
-                        // skip if populated by first pass
-                        if (pattern.get(aisleOffset)[stringI][charI] != 0) continue;
-
-                        char c = aisles[aisleI].charAt(stringI, charI);
-                        TraceabilityPredicate predicate = predicates.get(c);
-                        TraceabilityPredicate.SimplePredicate next = predicate.simple.get(predicateIndex.get(c));
-
-                        int layerCount = layerCache.getInt(next);
-                        int globalCount = globalCache.getInt(next);
-
-                        // don't need inequalities since everything is incremented once at a time
-                        // do this first because the first pass could have left some predicates already used
-                        while ((next.previewCount != -1 && globalCount == next.previewCount) ||
-                                (next.maxLayerCount != -1 && layerCount == next.maxLayerCount) ||
-                                (next.maxGlobalCount != -1 && globalCount == next.maxGlobalCount)) {
-                            // if the current predicate is used, move until the next free one
-                            int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
-                            if (newIndex >= predicate.simple.size()) {
-                                GTLog.logger.warn("Failed to generate default structure pattern.",
-                                        new Throwable());
-                                newIndex = 0;
-                            }
-                            next = predicate.simple.get(newIndex);
-                            globalCount = globalCache.getInt(next);
-                            layerCount = layerCache.getInt(next);
+        for (int aisleOffset = 0; aisleOffset < order.length; aisleOffset++) {
+            layerCache.clear();
+            for (int stringI = 0; stringI < dimensions[1]; stringI++) {
+                for (int charI = 0; charI < dimensions[2]; charI++) {
+                    // skip if populated by first pass
+                    if (pattern.get(aisleOffset)[stringI][charI] != 0) continue;
+
+                    char c = aisles[order[aisleOffset]].charAt(stringI, charI);
+                    TraceabilityPredicate predicate = predicates.get(c);
+                    TraceabilityPredicate.SimplePredicate next = predicate.simple.get(predicateIndex.get(c));
+
+                    int layerCount = layerCache.getInt(next);
+                    int globalCount = globalCache.getInt(next);
+
+                    // do this first because the first pass could have left some predicates already used
+                    while ((next.previewCount != -1 && globalCount == next.previewCount) ||
+                            (next.maxLayerCount != -1 && layerCount == next.maxLayerCount) ||
+                            (next.maxGlobalCount != -1 && globalCount == next.maxGlobalCount)) {
+                        // if the current predicate is used, move until the next free one
+                        int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
+                        if (newIndex >= predicate.simple.size()) {
+                            GTLog.logger.warn("Failed to generate default structure pattern.",
+                                    new Throwable());
+                            newIndex = 0;
                         }
+                        next = predicate.simple.get(newIndex);
+                        globalCount = globalCache.getInt(next);
+                        layerCount = layerCache.getInt(next);
+                    }
 
-                        if (next.candidates == null) continue;
+                    if (next.candidates == null) continue;
 
-                        char info = infos.getChar(next);
-                        layerCache.put(next, layerCount + 1);
-                        globalCache.put(next, globalCount + 1);
+                    char info = infos.getChar(next);
+                    layerCache.put(next, layerCount + 1);
+                    globalCache.put(next, globalCount + 1);
 
-                        pattern.get(aisleOffset)[stringI][charI] = info;
-                    }
+                    pattern.get(aisleOffset)[stringI][charI] = info;
                 }
-                aisleOffset++;
             }
         }
 
@@ -403,16 +395,13 @@ public int getRepetitionCount(int aisleI) {
         return pattern.toArray(new char[0][][]);
     }
 
-    @Override
-    public void autoBuild(EntityPlayer player, Map map) {}
-
     @Override
     public PatternState getPatternState() {
         return state;
     }
 
     /**
-     * DO NOT MUTATE THIS
+     * Probably shouldn't mutate this.
      */
     public AisleStrategy getAisleStrategy() {
         return aisleStrategy;
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index a180a112745..6dcb2a8f027 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -11,7 +11,6 @@
 import gregtech.api.util.function.QuadFunction;
 
 import net.minecraft.block.state.IBlockState;
-import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
@@ -181,14 +180,12 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
 
     @Override
     public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map,
+                                                                     Map keyMap,
                                                                      RelativeDirection[] directions) {
-        // todo maybe add this and autobuild
+        // todo maybe add this
         return null;
     }
 
-    @Override
-    public void autoBuild(EntityPlayer player, Map map) {}
-
     @Override
     public PatternState getPatternState() {
         return state;
diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
index c39edf799b6..a9486d831fa 100644
--- a/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
@@ -1,5 +1,6 @@
 package gregtech.api.pattern.pattern;
 
+import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.OriginOffset;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.RelativeDirection;
@@ -72,24 +73,7 @@ private FactoryBlockPattern(RelativeDirection aisleDir, RelativeDirection string
         directions[0] = aisleDir;
         directions[1] = stringDir;
         directions[2] = charDir;
-        int flags = 0;
-        for (int i = 0; i < 3; i++) {
-            switch (directions[i]) {
-                case UP:
-                case DOWN:
-                    flags |= 0x1;
-                    break;
-                case LEFT:
-                case RIGHT:
-                    flags |= 0x2;
-                    break;
-                case FRONT:
-                case BACK:
-                    flags |= 0x4;
-                    break;
-            }
-        }
-        if (flags != 0x7) throw new IllegalArgumentException("Must have 3 different axes!");
+        GreggyBlockPos.validateFacingsArray(directions);
         this.symbolMap.put(' ', TraceabilityPredicate.ANY);
     }
 
diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
index 1d5bd90fbba..5844e35a965 100644
--- a/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java
@@ -14,31 +14,14 @@ public class FactoryExpandablePattern {
 
     protected QuadFunction boundsFunction;
     protected BiFunction predicateFunction;
-    protected final RelativeDirection[] structureDir = new RelativeDirection[3];
+    protected final RelativeDirection[] directions = new RelativeDirection[3];
 
     private FactoryExpandablePattern(RelativeDirection aisleDir, RelativeDirection stringDir,
                                      RelativeDirection charDir) {
-        structureDir[0] = aisleDir;
-        structureDir[1] = stringDir;
-        structureDir[2] = charDir;
-        int flags = 0;
-        for (int i = 0; i < 3; i++) {
-            switch (structureDir[i]) {
-                case UP:
-                case DOWN:
-                    flags |= 0x1;
-                    break;
-                case LEFT:
-                case RIGHT:
-                    flags |= 0x2;
-                    break;
-                case FRONT:
-                case BACK:
-                    flags |= 0x4;
-                    break;
-            }
-        }
-        if (flags != 0x7) throw new IllegalArgumentException("Must have 3 different axes!");
+        directions[0] = aisleDir;
+        directions[1] = stringDir;
+        directions[2] = charDir;
+        GreggyBlockPos.validateFacingsArray(directions);
     }
 
     /**
@@ -90,6 +73,6 @@ public ExpandablePattern build() {
         if (predicateFunction == null)
             throw new IllegalStateException("Predicate function is null! Use .predicateFunction(...) on the builder!");
 
-        return new ExpandablePattern(boundsFunction, predicateFunction, structureDir);
+        return new ExpandablePattern(boundsFunction, predicateFunction, directions);
     }
 }
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index df089ff7560..b8fc9362d21 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -6,7 +6,6 @@
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.RelativeDirection;
 
-import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
@@ -56,22 +55,16 @@ boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
      * not exist.
      * 
      * @param map        Pass in an empty map to receive a populated map with the chars in the return mapping to a
-     *                   simpel predicate.
+     *                   simple predicate.
+     * @param keyMap     The map from multiblock builder for autobuild, or null if in other things like previews.
      * @param directions Pass in an array of length 3 to get the directions for the preview, or null if you don't want
      *                   it.
      */
-    // peak nullability, basically the return value can be null but if it is not then no arrays can be null
+    // peak nullability
     char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map,
+                                                              @Nullable Map keyMap,
                                                               @Nullable RelativeDirection[] directions);
 
-    /**
-     * Autobuilds a pattern, using the player inventory(and maybe AE soontm) items if in survival.
-     * 
-     * @param player The player initiating this autobuild.
-     * @param map    The map from the multiblock builder.
-     */
-    void autoBuild(EntityPlayer player, Map map);
-
     /**
      * Gets the internal pattern state, you should use the one returned from
      * {@link IBlockPattern#checkPatternFastAt(World, BlockPos, EnumFacing, EnumFacing, boolean)} always
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index 72444c9d198..eefde52c9fa 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -753,19 +753,4 @@ public long getPassiveDrainPerTick() {
                     .longValue();
         }
     }
-
-    private static class BatteryMatchWrapper {
-
-        private final IBatteryData partType;
-        private int amount;
-
-        public BatteryMatchWrapper(IBatteryData partType) {
-            this.partType = partType;
-        }
-
-        public BatteryMatchWrapper increment() {
-            amount++;
-            return this;
-        }
-    }
 }
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 348a848e7b0..ffb7dcca08d 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -565,6 +565,7 @@ private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @Not
                     .put(BlockPos.fromLong(pos), blockInfo.getPredicate()));
         }
 
+        // todo fix this sort
         List sortedParts = gatherStructureBlocks(worldSceneRenderer.world, blockMap, parts).stream()
                 .sorted((one, two) -> {
                     if (one.isController) return -1;

From fbc55acefd0cb3fef8ccf564ea51b6354b491fd5 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Mon, 23 Dec 2024 04:36:42 -0800
Subject: [PATCH 49/64] "it compiles at least"

---
 .../multiblock/MultiblockControllerBase.java  | 169 ++++++++++++++----
 .../gregtech/api/pattern/GreggyBlockPos.java  |  10 +-
 .../api/pattern/MultiblockShapeInfo.java      |  31 +---
 .../api/pattern/pattern/BlockPattern.java     | 134 +++-----------
 .../pattern/pattern/ExpandablePattern.java    |  14 +-
 .../api/pattern/pattern/IBlockPattern.java    |  18 +-
 .../java/gregtech/api/util/BlockInfo.java     |   9 -
 .../gregtech/api/util/GregFakePlayer.java     |   3 +-
 .../handler/MultiblockPreviewRenderer.java    |  55 +++---
 .../client/utils/TrackedDummyWorld.java       |  14 +-
 .../behaviors/MultiblockBuilderBehavior.java  |   3 +-
 .../MultiblockInfoRecipeWrapper.java          |  34 ++--
 12 files changed, 245 insertions(+), 249 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 2e6f74bf0a1..0661931cc73 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -54,9 +54,10 @@
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
 import codechicken.lib.vec.Rotation;
-import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
-import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
+import com.google.common.collect.AbstractIterator;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 import org.apache.commons.lang3.ArrayUtils;
@@ -70,6 +71,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableSet;
@@ -651,7 +653,7 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
             this.upwardsFacing = EnumFacing.VALUES[buf.readByte()];
             scheduleRenderUpdate();
         } else if (dataId == STRUCTURE_FORMED) {
-            // todo rewrite this entire thing :skull:
+            // todo rewrite this entire thing :skull:(including the server side code)
             String name = buf.readString(65536);
             if ("null".equals(name)) {
                 for (IBlockPattern pattern : structures.values()) {
@@ -780,23 +782,26 @@ public List getMatchingShapes() {
     }
 
     /**
-     * Get the default preview shape for JEI and in world previews.
+     * Called in either JEI or in-world previews to specify what maps they should be autobuilt with.
+     * Default impl returns a singleton iterator with {@code null}.
+     * 
+     * @return An iterator, you can return the same map but mutated. Iterator can be empty but why would you.
      */
-    // todo add use for the keyMap with the multiblock builder
-    // todo maybe add name arg for building substructures
-    public List getPreviewShapes(String substructureName) {
-        List infos = getMatchingShapes();
-
-        if (infos.isEmpty()) {
-            // for jei and stuff
-            if (getSubstructure(substructureName) == null) createStructurePatterns();
-            MultiblockShapeInfo info = MultiblockShapeInfo.fromShape(getSubstructure(substructureName));
-
-            if (info == null) return Collections.emptyList();
-            return Collections.singletonList(info);
-        }
+    @NotNull
+    public Iterator> getPreviewBuilds() {
+        return new AbstractIterator<>() {
 
-        return infos;
+            private boolean used;
+
+            @Override
+            protected Map computeNext() {
+                if (!used) {
+                    used = true;
+                    return null;
+                }
+                return endOfData();
+            }
+        };
     }
 
     /**
@@ -804,37 +809,133 @@ public List getPreviewShapes(String substructureName) {
      * structure if invalid.
      */
     public void autoBuild(EntityPlayer player, Map map) {
-        // todo lang
-        RelativeDirection[] directions = new RelativeDirection[3];
-        Char2ObjectMap predicateMap = new Char2ObjectOpenHashMap<>();
-        IBlockPattern structure = getSubstructure(map.getOrDefault("substructure", "MAIN"));
-        char[][][] pattern = structure.getDefaultShape(predicateMap, map, directions);
-
-        Char2ObjectMap buildCandidates = new Char2ObjectOpenHashMap<>();
-        for (Char2ObjectMap.Entry entry : predicateMap.char2ObjectEntrySet()) {
-            if (entry.getValue().buildFunction != null) {
-                buildCandidates.put(entry.getCharKey(), entry.getValue().buildFunction.apply(map));
+        if (getWorld().isRemote) throw new IllegalArgumentException("client side call wuh");
+
+        Long2ObjectMap predicates = getSubstructure("MAIN").getDefaultShape(this, map);
+
+        // for each symbol, which simple predicate is being used
+        // this advances whenever a minimum has been satisfied(if any), or a maximum has been reached(if any)
+        // preview counts are treated as exactly that many
+        Object2IntMap simpleIndex = new Object2IntOpenHashMap<>();
+        Object2IntMap globalCache = new Object2IntOpenHashMap<>();
+        Map cache = new HashMap<>();
+
+        BiPredicate place = (l, info) -> {
+            BlockPos pos = BlockPos.fromLong(l);
+
+            // don't stop build if its air, or maybe do?
+            if (!getWorld().isAirBlock(pos)) return true;
+
+            IBlockState state = info.getBlockState();
+
+            if (info.getTileEntity() instanceof MetaTileEntityHolder holder) {
+                ItemStack removed = hasAndRemoveItem(player, holder.getMetaTileEntity().getStackForm());
+                if (holder.getMetaTileEntity() != this && !removed.isEmpty()) {
+                    getWorld().setBlockState(pos, state);
+
+                    MetaTileEntityHolder newHolder = new MetaTileEntityHolder();
+                    newHolder.setMetaTileEntity(holder.getMetaTileEntity());
+                    newHolder.getMetaTileEntity().onPlacement();
+                    if (removed.hasTagCompound())
+                        newHolder.getMetaTileEntity().initFromItemStackData(removed.getTagCompound());
+
+                    // todo add relative facing fix to make hatches face air
+                    // // get the relative direction from the part facing, then use that to get the real enum facing
+                    // EnumFacing newFacing = FACING_MAP.get(holder.getMetaTileEntity().getFrontFacing())
+                    // .getRelativeFacing(frontFacing, upwardsFacing, false);
+                    // newHolder.getMetaTileEntity().setFrontFacing(newFacing);
+                    //
+                    // if (holder.getMetaTileEntity() instanceof MultiblockControllerBase holderBase) {
+                    // MultiblockControllerBase mteBase = (MultiblockControllerBase) newHolder.getMetaTileEntity();
+                    //
+                    // EnumFacing newUpFacing = FACING_MAP.get(holderBase.getUpwardsFacing())
+                    // .getRelativeFacing(frontFacing, upwardsFacing, false);
+                    // mteBase.setUpwardsFacing(newUpFacing);
+                    // }
+                } else return false;
+            } else {
+                if (!hasAndRemoveItem(player, GTUtility.toItem(info.getBlockState())).isEmpty())
+                    getWorld().setBlockState(pos, state);
+                else return false;
+            }
+
+            return true;
+        };
+
+        for (Long2ObjectMap.Entry entry : predicates.long2ObjectEntrySet()) {
+            // todo add autobuild key params here, also remove layer stuff from rest of the code elsewehre
+            TraceabilityPredicate pred = entry.getValue();
+            if (simpleIndex.getInt(pred) >= pred.simple.size()) continue;
+
+            TraceabilityPredicate.SimplePredicate simple = pred.simple.get(simpleIndex.getInt(pred));
+            int count = globalCache.getInt(simple);
+
+            while ((simple.previewCount == -1 || count == simple.previewCount) &&
+                    (simple.minGlobalCount == -1 || count == simple.minGlobalCount)) {
+                // if the current predicate is used, move until the next free one
+                int newIndex = simpleIndex.put(pred, simpleIndex.getInt(pred) + 1) + 1;
+                if (newIndex >= pred.simple.size()) break;
+                simple = pred.simple.get(newIndex);
+                count = globalCache.getInt(simple);
+            }
+            globalCache.put(simple, globalCache.getInt(simple) + 1);
+
+            if (simple.candidates == null) continue;
+
+            TraceabilityPredicate.SimplePredicate finalSimple = simple;
+            cache.computeIfAbsent(simple, k -> finalSimple.candidates.get()[0]);
+
+            if (!place.test(entry.getLongKey(), cache.get(simple))) return;
+        }
+
+        for (Long2ObjectMap.Entry entry : predicates.long2ObjectEntrySet()) {
+            TraceabilityPredicate pred = entry.getValue();
+            if (simpleIndex.getInt(pred) >= pred.simple.size()) continue;
+
+            TraceabilityPredicate.SimplePredicate simple = pred.simple.get(simpleIndex.getInt(pred));
+            int count = globalCache.getInt(simple);
+
+            while ((simple.previewCount != -1 && count == simple.previewCount) ||
+                    (simple.maxGlobalCount != -1 && count == simple.maxGlobalCount)) {
+                // if the current predicate is used, move until the next free one
+                int newIndex = simpleIndex.put(pred, simpleIndex.getInt(pred) + 1) + 1;
+                if (newIndex >= pred.simple.size()) {
+                    GTLog.logger.warn("Failed to generate default structure pattern.",
+                            new Throwable());
+                    return;
+                }
+                simple = pred.simple.get(newIndex);
+                count = globalCache.getInt(simple);
             }
+            globalCache.put(simple, globalCache.getInt(simple) + 1);
+
+            if (simple.candidates == null) continue;
+
+            TraceabilityPredicate.SimplePredicate finalSimple = simple;
+            cache.computeIfAbsent(simple, k -> finalSimple.candidates.get()[0]);
+
+            if (!place.test(entry.getLongKey(), cache.get(simple))) return;
         }
     }
 
     /**
-     * @return True iff the item has been successfully removed from the player's inventory(or AE system, satchels, etc)
-     *         or the player is in creative mode.
+     * @return The item stack that is removed from the player's inventory(or AE system, satchels, etc).
+     *         If the player is in creative mode, return a copy of the input stack.
      *         Currently only removes from the player's main inventory. The count of the passed in stack does not
      *         matter, only 1 is removed from the player.
      */
-    protected static boolean hasAndRemoveItem(EntityPlayer player, ItemStack stack) {
-        if (player.isCreative()) return true;
+    protected static ItemStack hasAndRemoveItem(EntityPlayer player, ItemStack stack) {
+        if (stack.isEmpty()) return ItemStack.EMPTY;
+        if (player.isCreative()) return stack.copy();
 
         for (ItemStack ztack : player.inventory.mainInventory) {
             if (!ztack.isEmpty() && ztack.isItemEqual(stack)) {
                 ztack.setCount(ztack.getCount() - 1);
-                return true;
+                return ztack.copy();
             }
         }
 
-        return false;
+        return ItemStack.EMPTY;
     }
 
     @SideOnly(Side.CLIENT)
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index c6274f67c58..dd5dc468db9 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -17,7 +17,6 @@
  */
 public class GreggyBlockPos {
 
-    protected final int[] pos;
     public static final int NUM_X_BITS = 1 + MathHelper.log2(MathHelper.smallestEncompassingPowerOfTwo(30000000));
     public static final int NUM_Z_BITS = NUM_X_BITS, NUM_Y_BITS = 64 - 2 * NUM_X_BITS;
     public static final int Y_SHIFT = NUM_Z_BITS;
@@ -25,6 +24,8 @@ public class GreggyBlockPos {
     public static final long Z_MASK = (1L << NUM_Z_BITS) - 1;
     public static final long Y_MASK = (1L << (NUM_Z_BITS + NUM_Y_BITS)) - 1;
 
+    protected final int[] pos;
+
     public GreggyBlockPos() {
         this(0, 0, 0);
     }
@@ -311,6 +312,13 @@ public boolean equals(Object other) {
         return false;
     }
 
+    /**
+     * Static version of {@link GreggyBlockPos#toLong()}
+     */
+    public static long toLong(int x, int y, int z) {
+        return (long) x << X_SHIFT | ((long) y << Y_SHIFT) & Y_MASK | (z & Z_MASK);
+    }
+
     /**
      * Validates the enum array that each pair of enum ordinals happen exactly once(it is assumed the max ordinal is 5).
      */
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index 7d1e6561577..fe3b1f4e797 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -3,7 +3,6 @@
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.pattern.pattern.PatternAisle;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTLog;
@@ -22,7 +21,6 @@
 import it.unimi.dsi.fastutil.chars.Char2IntMap;
 import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
-import it.unimi.dsi.fastutil.chars.Char2ObjectMaps;
 import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap;
 
 import java.util.ArrayList;
@@ -176,7 +174,7 @@ public BlockPos getMap(MultiblockControllerBase src, BlockPos start, Map predicates) {
-        if (shape == null) return null;
-        Char2ObjectMap candidates = new Char2ObjectOpenHashMap<>();
-        for (Char2ObjectMap.Entry entry : predicates.char2ObjectEntrySet()) {
-            if (entry.getValue().candidates == null) {
-                candidates.put(entry.getCharKey(), BlockInfo.EMPTY);
-            } else {
-                candidates.put(entry.getCharKey(), entry.getValue().candidates.get()[0]);
-            }
-        }
-
-        return new MultiblockShapeInfo(Arrays.stream(shape).map(PatternAisle::new).toArray(PatternAisle[]::new),
-                candidates,
-                Char2ObjectMaps.emptyMap(), directions);
-    }
-
-    public static MultiblockShapeInfo fromShape(IBlockPattern pattern) {
-        Char2ObjectMap predicates = new Char2ObjectOpenHashMap<>();
-        RelativeDirection[] directions = new RelativeDirection[3];
-        char[][][] shape = pattern.getDefaultShape(predicates, null, directions);
-        if (shape == null) return null;
-        return fromShape(directions, shape, predicates);
-    }
-
     public static class Builder {
 
         private List shape = new ArrayList<>();
@@ -344,7 +317,7 @@ public Builder where(char symbol, Supplier partSupplier, EnumFacing frontSide
 
         /**
          * Adds a dot block to represent the char.
-         * 
+         *
          * @param symbol The symbol in the pattern to put.
          * @param dot    The amount of dots on the block, 0-15
          * @param lang   The lang to show for the block, pass in the lang key and it will format.
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 4f88b1f1f68..f709ff12a39 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -1,6 +1,7 @@
 package gregtech.api.pattern.pattern;
 
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
+import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.OriginOffset;
@@ -21,6 +22,8 @@
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectRBTreeMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMap;
 import it.unimi.dsi.fastutil.objects.Object2CharMap;
 import it.unimi.dsi.fastutil.objects.Object2CharOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
@@ -174,7 +177,6 @@ public Long2ObjectMap getCache() {
     @Override
     public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
                                   EnumFacing upwardsFacing, boolean isFlipped) {
-        // todo still need to rework this to have a temporary global cache
         this.globalCount.clear();
         this.layerCount.clear();
         cache.clear();
@@ -278,121 +280,35 @@ public int getRepetitionCount(int aisleI) {
     }
 
     @Override
-    public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map,
-                                                                     Map keyMap,
-                                                                     @Nullable RelativeDirection[] directions) {
-        // for each symbol, which simple predicate is being used
-        // this advances whenever a minimum has been satisfied(if any), or a maximum has been reached(if any)
-        // preview counts are treated as exactly that many
-        Char2IntMap predicateIndex = new Char2IntOpenHashMap();
-        // candidates to be passed into MultiblockShapeInfo
-        // Char2ObjectMap candidates = new Char2ObjectOpenHashMap<>();
-        // cache for candidates
-        Object2CharMap infos = new Object2CharOpenHashMap<>();
-        Object2IntMap globalCache = new Object2IntOpenHashMap<>();
-        Object2IntMap layerCache = new Object2IntOpenHashMap<>();
-
-        List pattern = new ArrayList<>(dimensions[0]);
-
-        // 0 is reserved for air
-        char currentChar = 1;
-
-        for (TraceabilityPredicate predicate : predicates.values()) {
-            for (TraceabilityPredicate.SimplePredicate simple : predicate.simple) {
-                if (infos.put(simple, currentChar) == 0) {
-                    map.put(currentChar, simple);
-                    currentChar++;
-                }
-            }
-        }
+    public Long2ObjectSortedMap getDefaultShape(MultiblockControllerBase src,
+                                                                       Map keyMap) {
+        Long2ObjectSortedMap map = new Long2ObjectRBTreeMap<>();
+        EnumFacing absoluteAisle = directions[0].getRelativeFacing(src.getFrontFacing(), src.getUpwardsFacing());
+        EnumFacing absoluteString = directions[1].getRelativeFacing(src.getFrontFacing(), src.getUpwardsFacing());
+        EnumFacing absoluteChar = directions[2].getRelativeFacing(src.getFrontFacing(), src.getUpwardsFacing());
 
-        int[] order = aisleStrategy.getDefaultAisles(keyMap);
+        GreggyBlockPos pos = new GreggyBlockPos(src.getPos());
+        GreggyBlockPos start = startPos(pos, src.getFrontFacing(), src.getUpwardsFacing(), false);
+        GreggyBlockPos serial = new GreggyBlockPos().from(start);
 
-        // first pass fills in all the minimum counts
-        for (int aisleOffset = 0; aisleOffset < order.length; aisleOffset++) {
-            pattern.add(new char[dimensions[1]][dimensions[2]]);
-            layerCache.clear();
-            for (int stringI = 0; stringI < dimensions[1]; stringI++) {
-                for (int charI = 0; charI < dimensions[2]; charI++) {
-                    char c = aisles[order[aisleOffset]].charAt(stringI, charI);
-                    TraceabilityPredicate predicate = predicates.get(c);
-                    // we used up all the simple predicates, just let the second pass fill them in
-                    if (predicateIndex.get(c) >= predicate.simple.size()) continue;
-                    TraceabilityPredicate.SimplePredicate simple = predicate.simple.get(predicateIndex.get(c));
-
-                    if (simple.candidates == null) continue;
-
-                    char info = infos.getChar(simple);
-                    int layerCount = layerCache.put(simple, layerCache.getInt(simple) + 1) + 1;
-                    int globalCount = globalCache.put(simple, globalCache.getInt(simple) + 1) + 1;
-
-                    pattern.get(aisleOffset)[stringI][charI] = info;
-
-                    TraceabilityPredicate.SimplePredicate next = simple;
-
-                    // don't need inequalities since everything is incremented once at a time
-                    // only put the minimum amount of parts possible
-                    // missing parts will be filled in the second pass
-                    while ((next.previewCount == -1 || globalCount == next.previewCount) &&
-                            (next.minLayerCount == -1 || layerCount == next.minLayerCount) &&
-                            (next.minGlobalCount == -1 || globalCount == next.minGlobalCount)) {
-                        // if the current predicate is used, move until the next free one
-                        int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
-                        if (newIndex >= predicate.simple.size()) break;
-                        next = predicate.simple.get(newIndex);
-                        globalCount = globalCache.getInt(next);
-                        layerCount = layerCache.getInt(next);
-                    }
+        int[] order = aisleStrategy.getDefaultAisles(keyMap);
+        for (int i = 0; i < order.length; i++) {
+            for (int j = 0; j < dimensions[1]; j++) {
+                for (int k = 0; k < dimensions[2]; k++) {
+                    TraceabilityPredicate pred = predicates.get(aisles[order[i]].charAt(j, k));
+                    if (pred != TraceabilityPredicate.ANY && pred != TraceabilityPredicate.AIR)
+                        map.put(serial.toLong(), predicates.get(aisles[order[i]].charAt(j, k)));
+                    serial.offset(absoluteChar);
                 }
+                serial.offset(absoluteString);
+                serial.offset(absoluteChar.getOpposite(), dimensions[2]);
             }
-        }
 
-        predicateIndex.clear();
-
-        // second pass fills everything else
-        for (int aisleOffset = 0; aisleOffset < order.length; aisleOffset++) {
-            layerCache.clear();
-            for (int stringI = 0; stringI < dimensions[1]; stringI++) {
-                for (int charI = 0; charI < dimensions[2]; charI++) {
-                    // skip if populated by first pass
-                    if (pattern.get(aisleOffset)[stringI][charI] != 0) continue;
-
-                    char c = aisles[order[aisleOffset]].charAt(stringI, charI);
-                    TraceabilityPredicate predicate = predicates.get(c);
-                    TraceabilityPredicate.SimplePredicate next = predicate.simple.get(predicateIndex.get(c));
-
-                    int layerCount = layerCache.getInt(next);
-                    int globalCount = globalCache.getInt(next);
-
-                    // do this first because the first pass could have left some predicates already used
-                    while ((next.previewCount != -1 && globalCount == next.previewCount) ||
-                            (next.maxLayerCount != -1 && layerCount == next.maxLayerCount) ||
-                            (next.maxGlobalCount != -1 && globalCount == next.maxGlobalCount)) {
-                        // if the current predicate is used, move until the next free one
-                        int newIndex = predicateIndex.put(c, predicateIndex.get(c) + 1) + 1;
-                        if (newIndex >= predicate.simple.size()) {
-                            GTLog.logger.warn("Failed to generate default structure pattern.",
-                                    new Throwable());
-                            newIndex = 0;
-                        }
-                        next = predicate.simple.get(newIndex);
-                        globalCount = globalCache.getInt(next);
-                        layerCount = layerCache.getInt(next);
-                    }
-
-                    if (next.candidates == null) continue;
-
-                    char info = infos.getChar(next);
-                    layerCache.put(next, layerCount + 1);
-                    globalCache.put(next, globalCount + 1);
-
-                    pattern.get(aisleOffset)[stringI][charI] = info;
-                }
-            }
+            serial.from(start);
+            serial.offset(absoluteAisle, i + 1);
         }
 
-        if (directions != null) System.arraycopy(this.directions, 0, directions, 0, 3);
-        return pattern.toArray(new char[0][][]);
+        return map;
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index 6dcb2a8f027..25569c3a411 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -1,6 +1,7 @@
 package gregtech.api.pattern.pattern;
 
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
+import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.GreggyBlockPos;
 import gregtech.api.pattern.OriginOffset;
@@ -16,13 +17,13 @@
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
 
-import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMaps;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 import java.util.Map;
 import java.util.function.BiFunction;
@@ -179,11 +180,10 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
     }
 
     @Override
-    public char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map,
-                                                                     Map keyMap,
-                                                                     RelativeDirection[] directions) {
-        // todo maybe add this
-        return null;
+    public Long2ObjectSortedMap getDefaultShape(MultiblockControllerBase src,
+                                                                       Map keyMap) {
+        // todo add this
+        return Long2ObjectSortedMaps.emptyMap();
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index b8fc9362d21..843b53b63b0 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -10,8 +10,8 @@
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
 
-import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMap;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -53,17 +53,13 @@ boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
     /**
      * Gets the default shape, if the multiblock does not specify one. Return null to represent the default shape does
      * not exist.
-     * 
-     * @param map        Pass in an empty map to receive a populated map with the chars in the return mapping to a
-     *                   simple predicate.
-     * @param keyMap     The map from multiblock builder for autobuild, or null if in other things like previews.
-     * @param directions Pass in an array of length 3 to get the directions for the preview, or null if you don't want
-     *                   it.
+     *
+     * @param keyMap The map from multiblock builder for autobuild.
+     * @return The long key is using {@link gregtech.api.pattern.GreggyBlockPos#toLong(int, int, int)} with x, y, z
+     *         respectively being. The map is sorted using the natural ordering(thus with x, y, z order).
      */
-    // peak nullability
-    char @Nullable [] @NotNull [] @NotNull [] getDefaultShape(Char2ObjectMap map,
-                                                              @Nullable Map keyMap,
-                                                              @Nullable RelativeDirection[] directions);
+    Long2ObjectSortedMap getDefaultShape(MultiblockControllerBase src,
+                                                                @Nullable Map keyMap);
 
     /**
      * Gets the internal pattern state, you should use the one returned from
diff --git a/src/main/java/gregtech/api/util/BlockInfo.java b/src/main/java/gregtech/api/util/BlockInfo.java
index 725c10f0cd6..b576413cf31 100644
--- a/src/main/java/gregtech/api/util/BlockInfo.java
+++ b/src/main/java/gregtech/api/util/BlockInfo.java
@@ -6,8 +6,6 @@
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.init.Blocks;
 import net.minecraft.tileentity.TileEntity;
-import net.minecraft.util.math.BlockPos;
-import net.minecraft.world.World;
 
 import com.google.common.base.Preconditions;
 
@@ -57,11 +55,4 @@ public TileEntity getTileEntity() {
     public TraceabilityPredicate getPredicate() {
         return predicate;
     }
-
-    public void apply(World world, BlockPos pos) {
-        world.setBlockState(pos, blockState);
-        if (tileEntity != null) {
-            world.setTileEntity(pos, tileEntity);
-        }
-    }
 }
diff --git a/src/main/java/gregtech/api/util/GregFakePlayer.java b/src/main/java/gregtech/api/util/GregFakePlayer.java
index 672f8be98dc..b9629aa7d8a 100644
--- a/src/main/java/gregtech/api/util/GregFakePlayer.java
+++ b/src/main/java/gregtech/api/util/GregFakePlayer.java
@@ -46,7 +46,8 @@ public boolean isSpectator() {
 
     @Override
     public boolean isCreative() {
-        return false;
+        // is this cooked? surely someone wouldnt
+        return true;
     }
 
     @Override
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index 6e1fb5df29a..a97a1741871 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -4,9 +4,11 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.GreggyBlockPos;
-import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.util.BlockInfo;
+import gregtech.api.util.GregFakePlayer;
+import gregtech.client.renderer.scene.ImmediateWorldSceneRenderer;
 import gregtech.client.utils.TrackedDummyWorld;
+import gregtech.common.ConfigHolder;
 
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.Minecraft;
@@ -36,9 +38,11 @@
 import org.lwjgl.opengl.GL11;
 
 import java.util.HashMap;
-import java.util.List;
+import java.util.Iterator;
 import java.util.Map;
 
+import static gregtech.integration.jei.multiblock.MultiblockInfoRecipeWrapper.SOURCE;
+
 @SideOnly(Side.CLIENT)
 public class MultiblockPreviewRenderer {
 
@@ -78,23 +82,24 @@ public static void renderWorldLastEvent(RenderWorldLastEvent event) {
         }
     }
 
-    public static void renderMultiBlockPreview(MultiblockControllerBase controller, EntityPlayer player,
+    public static void renderMultiBlockPreview(MultiblockControllerBase src, EntityPlayer player,
                                                long durTimeMillis) {
-        if (!controller.getPos().equals(mbpPos)) {
+        if (!src.getPos().equals(mbpPos)) {
             layer = 0;
         } else {
             if (mbpEndTime - System.currentTimeMillis() < 200) return;
             layer++;
         }
         resetMultiblockRender();
-        mbpPos = controller.getPos();
+        mbpPos = src.getPos();
         mbpEndTime = System.currentTimeMillis() + durTimeMillis;
         opList = GLAllocation.generateDisplayLists(1); // allocate op list
         GlStateManager.glNewList(opList, GL11.GL_COMPILE);
-        List shapes = controller.getPreviewShapes("MAIN");
-        if (!shapes.isEmpty()) {
-            renderControllerInList(controller, shapes.get(0), layer);
-            shapes.get(0).sendDotMessage(player);
+        Iterator> iter = src.getPreviewBuilds();
+        if (iter.hasNext()) {
+            renderControllerInList(src, iter.next(), layer);
+            // todo add dots again
+            // shapes.get(0).sendDotMessage(player);
         }
         GlStateManager.glEndList();
     }
@@ -108,19 +113,27 @@ public static void resetMultiblockRender() {
         }
     }
 
-    public static void renderControllerInList(MultiblockControllerBase controllerBase, MultiblockShapeInfo shapeInfo,
+    public static void renderControllerInList(MultiblockControllerBase src, Map keyMap,
                                               int layer) {
+        TrackedDummyWorld world = new TrackedDummyWorld();
+        ImmediateWorldSceneRenderer worldSceneRenderer = new ImmediateWorldSceneRenderer(world);
+        worldSceneRenderer.setClearColor(ConfigHolder.client.multiblockPreviewColor);
+
         Map blockMap = new HashMap<>();
-        BlockPos controllerPos = shapeInfo.getMap(controllerBase, new BlockPos(0, 128, 0), blockMap);
-        MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap
-                .get(controllerPos).getTileEntity()).getMetaTileEntity();
 
-        EnumFacing facing = controllerBase.getFrontFacing();
-        EnumFacing upwardsFacing = controllerBase.getUpwardsFacing();
+        // absolutely dog way of doing this, just setting the center at 128 so that both patterns going down and up can
+        // work
+        MetaTileEntityHolder holder = new MetaTileEntityHolder();
+        world.setBlockState(SOURCE, src.getBlock().getDefaultState());
+        holder.setMetaTileEntity(src);
+        holder.getMetaTileEntity().onPlacement();
 
-        TrackedDummyWorld world = new TrackedDummyWorld();
-        world.addBlocks(blockMap);
-        int finalMaxY = layer % (shapeInfo.getUpCount(facing, upwardsFacing) + 1);
+        world.setTileEntity(SOURCE, holder);
+
+        ((MultiblockControllerBase) holder.getMetaTileEntity()).autoBuild(new GregFakePlayer(world), keyMap);
+
+        // todo fix
+        int finalMaxY = layer % 5;
         world.setRenderFilter(pos -> pos.getY() - 127 == finalMaxY || finalMaxY == 0);
 
         Minecraft mc = Minecraft.getMinecraft();
@@ -128,18 +141,18 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas
         Tessellator tes = Tessellator.getInstance();
         BufferBuilder buff = tes.getBuffer();
 
-        if (controller != null) controller.checkStructurePattern();
+        ((MultiblockControllerBase) holder.getMetaTileEntity()).checkStructurePattern();
 
         BlockRenderLayer oldLayer = MinecraftForgeClient.getRenderLayer();
         TargetBlockAccess targetBA = new TargetBlockAccess(world, BlockPos.ORIGIN);
 
         GreggyBlockPos greg = new GreggyBlockPos();
-        GreggyBlockPos offset = new GreggyBlockPos(controllerPos);
+        GreggyBlockPos offset = new GreggyBlockPos(SOURCE);
         GreggyBlockPos temp = new GreggyBlockPos();
 
         for (BlockPos pos : blockMap.keySet()) {
             targetBA.setPos(pos);
-            greg.from(controllerBase.getPos()).add(temp.from(pos)).subtract(offset);
+            greg.from(src.getPos()).add(temp.from(pos)).subtract(offset);
 
             GlStateManager.pushMatrix();
             GlStateManager.translate(greg.x(), greg.y(), greg.z());
diff --git a/src/main/java/gregtech/client/utils/TrackedDummyWorld.java b/src/main/java/gregtech/client/utils/TrackedDummyWorld.java
index 939233d0b1e..2141316fa3d 100644
--- a/src/main/java/gregtech/client/utils/TrackedDummyWorld.java
+++ b/src/main/java/gregtech/client/utils/TrackedDummyWorld.java
@@ -1,6 +1,5 @@
 package gregtech.client.utils;
 
-import gregtech.api.util.BlockInfo;
 import gregtech.api.util.world.DummyWorld;
 
 import net.minecraft.block.state.IBlockState;
@@ -14,7 +13,6 @@
 import org.jetbrains.annotations.NotNull;
 
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
 import java.util.function.Predicate;
 
@@ -49,17 +47,6 @@ public TrackedDummyWorld(World world) {
         proxyWorld = world;
     }
 
-    public void addBlocks(Map renderedBlocks) {
-        renderedBlocks.forEach(this::addBlock);
-    }
-
-    public void addBlock(BlockPos pos, BlockInfo blockInfo) {
-        if (blockInfo.getBlockState().getBlock() == Blocks.AIR)
-            return;
-        this.renderedBlocks.add(pos);
-        blockInfo.apply(this, pos);
-    }
-
     @Override
     public TileEntity getTileEntity(@NotNull BlockPos pos) {
         if (renderFilter != null && !renderFilter.test(pos))
@@ -83,6 +70,7 @@ public boolean setBlockState(@NotNull BlockPos pos, @NotNull IBlockState newStat
         maxPos.setX(Math.max(maxPos.getX(), pos.getX()));
         maxPos.setY(Math.max(maxPos.getY(), pos.getY()));
         maxPos.setZ(Math.max(maxPos.getZ(), pos.getZ()));
+        renderedBlocks.add(pos);
         return super.setBlockState(pos, newState, flags);
     }
 
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index 1ebf2fbb015..84199e04efc 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -178,8 +178,7 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
             if (!multiblock.isStructureFormed("MAIN")) {
-                // multiblock.auto("MAIN", getMap(player.getHeldItem(hand))).get(0).getMap(multiblock,
-                // new BlockPos(0, 128, 0), new HashMap<>());
+                multiblock.autoBuild(player, getMap(player.getHeldItem(hand)));
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index ffb7dcca08d..f45e06546f4 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -6,10 +6,10 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.BlockWorldState;
-import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GTUtility;
+import gregtech.api.util.GregFakePlayer;
 import gregtech.api.util.ItemStackHashStrategy;
 import gregtech.client.renderer.scene.ImmediateWorldSceneRenderer;
 import gregtech.client.renderer.scene.WorldSceneRenderer;
@@ -56,6 +56,7 @@
 import mezz.jei.api.recipe.IRecipeWrapper;
 import mezz.jei.gui.recipes.RecipeLayout;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.lwjgl.input.Mouse;
 import org.lwjgl.opengl.GL11;
 
@@ -67,6 +68,7 @@
 
 public class MultiblockInfoRecipeWrapper implements IRecipeWrapper {
 
+    public static final BlockPos SOURCE = new BlockPos(0, 128, 0);
     private static final int MAX_PARTS = 18;
     private static final int PARTS_HEIGHT = 36;
     private static final int SLOT_SIZE = 18;
@@ -122,9 +124,11 @@ public MBPattern(final WorldSceneRenderer sceneRenderer, final List p
     public MultiblockInfoRecipeWrapper(@NotNull MultiblockControllerBase controller) {
         this.controller = controller;
         Set drops = new ObjectOpenCustomHashSet<>(ItemStackHashStrategy.comparingAllButCount());
-        this.patterns = controller.getPreviewShapes("MAIN").stream()
-                .map(it -> initializePattern(it, drops))
-                .toArray(MBPattern[]::new);
+        List temp = new ArrayList<>();
+        for (Iterator> iter = controller.getPreviewBuilds(); iter.hasNext();) {
+            temp.add(initializePattern(controller, iter.next(), drops));
+        }
+        this.patterns = temp.toArray(new MBPattern[0]);
         allItemStackInputs.addAll(drops);
         this.nextLayerButton = new GuiButton(0, 176 - (ICON_SIZE + RIGHT_PADDING), 70, ICON_SIZE, ICON_SIZE, "");
         this.buttonPreviousPattern = new GuiButton(0, 176 - ((2 * ICON_SIZE) + RIGHT_PADDING + 1), 90, ICON_SIZE,
@@ -524,18 +528,24 @@ private static Collection gatherStructureBlocks(World world, @NotNull
 
     @NotNull
     // todo substructure support
-    private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @NotNull Set parts) {
+    private MBPattern initializePattern(@NotNull MultiblockControllerBase src, @Nullable Map keyMap,
+                                        @NotNull Set parts) {
+        TrackedDummyWorld world = new TrackedDummyWorld();
+        ImmediateWorldSceneRenderer worldSceneRenderer = new ImmediateWorldSceneRenderer(world);
+        worldSceneRenderer.setClearColor(ConfigHolder.client.multiblockPreviewColor);
+
         Map blockMap = new HashMap<>();
+
         // absolutely dog way of doing this, just setting the center at 128 so that both patterns going down and up can
         // work
-        BlockPos controllerPos = shapeInfo.getMap(this.controller, new BlockPos(0, 128, 0), blockMap);
-        MultiblockControllerBase controller = (MultiblockControllerBase) ((MetaTileEntityHolder) blockMap
-                .get(controllerPos).getTileEntity()).getMetaTileEntity();
+        MetaTileEntityHolder holder = new MetaTileEntityHolder();
+        world.setBlockState(SOURCE, src.getBlock().getDefaultState());
+        holder.setMetaTileEntity(src);
+        holder.getMetaTileEntity().onPlacement();
 
-        TrackedDummyWorld world = new TrackedDummyWorld();
-        ImmediateWorldSceneRenderer worldSceneRenderer = new ImmediateWorldSceneRenderer(world);
-        worldSceneRenderer.setClearColor(ConfigHolder.client.multiblockPreviewColor);
-        world.addBlocks(blockMap);
+        world.setTileEntity(SOURCE, holder);
+
+        ((MultiblockControllerBase) holder.getMetaTileEntity()).autoBuild(new GregFakePlayer(world), keyMap);
 
         Vector3f size = world.getSize();
         Vector3f minPos = world.getMinPos();

From 36863b7d8942bee1c565679be30d77dfe45cb465 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Tue, 24 Dec 2024 02:10:33 -0800
Subject: [PATCH 50/64] =?UTF-8?q?"it=20works=E2=80=9D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../multiblock/MultiblockControllerBase.java  |  66 +++---
 .../api/pattern/TraceabilityPredicate.java    |   8 +-
 .../pattern/pattern/BasicAisleStrategy.java   |  18 +-
 .../api/pattern/pattern/BlockPattern.java     |   8 +-
 .../pattern/pattern/ExpandablePattern.java    |   4 +-
 .../pattern/pattern/FactoryBlockPattern.java  |   2 +-
 .../api/pattern/pattern/IBlockPattern.java    |   3 +-
 .../java/gregtech/api/util/BlockInfo.java     |  12 --
 .../java/gregtech/api/util/GTUtility.java     |  15 +-
 .../handler/MultiblockPreviewRenderer.java    |  20 +-
 .../client/utils/TrackedDummyWorld.java       |   7 +-
 .../network/packets/PacketRecoverMTE.java     |   2 +
 .../MultiblockInfoRecipeWrapper.java          | 201 ++++++++++--------
 13 files changed, 195 insertions(+), 171 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 0661931cc73..0cc72d94fac 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -584,10 +584,6 @@ public IBlockPattern getSubstructure(String name) {
         return structures.get(name);
     }
 
-    public IBlockPattern getSubstructure(String name, String defult) {
-        return structures.get(structures.containsKey(name) ? name : defult);
-    }
-
     @Override
     public void onRemoval() {
         super.onRemoval();
@@ -797,7 +793,7 @@ public Iterator> getPreviewBuilds() {
             protected Map computeNext() {
                 if (!used) {
                     used = true;
-                    return null;
+                    return Collections.emptyMap();
                 }
                 return endOfData();
             }
@@ -812,6 +808,18 @@ public void autoBuild(EntityPlayer player, Map map) {
         if (getWorld().isRemote) throw new IllegalArgumentException("client side call wuh");
 
         Long2ObjectMap predicates = getSubstructure("MAIN").getDefaultShape(this, map);
+        if (predicates == null) return;
+
+        autoBuild(player, map, predicates);
+    }
+
+    /**
+     * Autobuild the multiblock, this is like {@link MultiblockControllerBase#autoBuild(EntityPlayer, Map)} but if
+     * you have the predicate map for other uses. This does mutate the map passed in.
+     */
+    public void autoBuild(EntityPlayer player, Map map,
+                          Long2ObjectMap predicates) {
+        if (getWorld().isRemote) throw new IllegalArgumentException("client side call wuh");
 
         // for each symbol, which simple predicate is being used
         // this advances whenever a minimum has been satisfied(if any), or a maximum has been reached(if any)
@@ -823,22 +831,21 @@ public void autoBuild(EntityPlayer player, Map map) {
         BiPredicate place = (l, info) -> {
             BlockPos pos = BlockPos.fromLong(l);
 
-            // don't stop build if its air, or maybe do?
+            // don't stop build if its air
             if (!getWorld().isAirBlock(pos)) return true;
 
-            IBlockState state = info.getBlockState();
-
             if (info.getTileEntity() instanceof MetaTileEntityHolder holder) {
                 ItemStack removed = hasAndRemoveItem(player, holder.getMetaTileEntity().getStackForm());
-                if (holder.getMetaTileEntity() != this && !removed.isEmpty()) {
-                    getWorld().setBlockState(pos, state);
-
+                if (!removed.isEmpty()) {
                     MetaTileEntityHolder newHolder = new MetaTileEntityHolder();
                     newHolder.setMetaTileEntity(holder.getMetaTileEntity());
                     newHolder.getMetaTileEntity().onPlacement();
                     if (removed.hasTagCompound())
                         newHolder.getMetaTileEntity().initFromItemStackData(removed.getTagCompound());
 
+                    getWorld().setBlockState(pos, holder.getMetaTileEntity().getBlock().getDefaultState());
+                    getWorld().setTileEntity(pos, newHolder);
+
                     // todo add relative facing fix to make hatches face air
                     // // get the relative direction from the part facing, then use that to get the real enum facing
                     // EnumFacing newFacing = FACING_MAP.get(holder.getMetaTileEntity().getFrontFacing())
@@ -855,29 +862,37 @@ public void autoBuild(EntityPlayer player, Map map) {
                 } else return false;
             } else {
                 if (!hasAndRemoveItem(player, GTUtility.toItem(info.getBlockState())).isEmpty())
-                    getWorld().setBlockState(pos, state);
+                    getWorld().setBlockState(pos, info.getBlockState());
                 else return false;
             }
 
             return true;
         };
 
-        for (Long2ObjectMap.Entry entry : predicates.long2ObjectEntrySet()) {
-            // todo add autobuild key params here, also remove layer stuff from rest of the code elsewehre
+        for (Iterator> iter = predicates.long2ObjectEntrySet()
+                .iterator(); iter.hasNext();) {
+            Long2ObjectMap.Entry entry = iter.next();
+            // todo add autobuild key params here, also remove layer stuff from rest of the code elsewhere
             TraceabilityPredicate pred = entry.getValue();
             if (simpleIndex.getInt(pred) >= pred.simple.size()) continue;
 
-            TraceabilityPredicate.SimplePredicate simple = pred.simple.get(simpleIndex.getInt(pred));
+            int pointer = simpleIndex.getInt(pred);
+            TraceabilityPredicate.SimplePredicate simple = pred.simple.get(pointer);
             int count = globalCache.getInt(simple);
 
-            while ((simple.previewCount == -1 || count == simple.previewCount) &&
-                    (simple.minGlobalCount == -1 || count == simple.minGlobalCount)) {
-                // if the current predicate is used, move until the next free one
-                int newIndex = simpleIndex.put(pred, simpleIndex.getInt(pred) + 1) + 1;
-                if (newIndex >= pred.simple.size()) break;
-                simple = pred.simple.get(newIndex);
-                count = globalCache.getInt(simple);
+            try {
+                while ((simple.previewCount == -1 || count == simple.previewCount) &&
+                        (simple.minGlobalCount == -1 || count == simple.minGlobalCount)) {
+                    // if the current predicate is used, move until the next free one
+                    pointer++;
+                    simple = pred.simple.get(pointer);
+                    count = globalCache.getInt(simple);
+                }
+                simpleIndex.put(pred, pointer);
+            } catch (IndexOutOfBoundsException e) {
+                continue;
             }
+
             globalCache.put(simple, globalCache.getInt(simple) + 1);
 
             if (simple.candidates == null) continue;
@@ -886,8 +901,12 @@ public void autoBuild(EntityPlayer player, Map map) {
             cache.computeIfAbsent(simple, k -> finalSimple.candidates.get()[0]);
 
             if (!place.test(entry.getLongKey(), cache.get(simple))) return;
+
+            iter.remove();
         }
 
+        simpleIndex.clear();
+
         for (Long2ObjectMap.Entry entry : predicates.long2ObjectEntrySet()) {
             TraceabilityPredicate pred = entry.getValue();
             if (simpleIndex.getInt(pred) >= pred.simple.size()) continue;
@@ -895,8 +914,7 @@ public void autoBuild(EntityPlayer player, Map map) {
             TraceabilityPredicate.SimplePredicate simple = pred.simple.get(simpleIndex.getInt(pred));
             int count = globalCache.getInt(simple);
 
-            while ((simple.previewCount != -1 && count == simple.previewCount) ||
-                    (simple.maxGlobalCount != -1 && count == simple.maxGlobalCount)) {
+            while (count == simple.previewCount || count == simple.maxGlobalCount) {
                 // if the current predicate is used, move until the next free one
                 int newIndex = simpleIndex.put(pred, simpleIndex.getInt(pred) + 1) + 1;
                 if (newIndex >= pred.simple.size()) {
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index 29cc4e2d99a..98aa11f7754 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -85,13 +85,6 @@ public boolean isCenter() {
         return isCenter;
     }
 
-    public TraceabilityPredicate sort() {
-        // reverse so that all min layer and global counts are at the front
-        simple.sort(Collections
-                .reverseOrder(Comparator.comparingInt(a -> ((a.minLayerCount + 1) * 100 + a.minGlobalCount))));
-        return this;
-    }
-
     /**
      * Add tooltips for candidates. They are shown in JEI Pages.
      * Do NOT pass {@link I18n#format(String, Object...)} calls here! Everything is will be translated when it's needed.
@@ -302,6 +295,7 @@ public PatternError testLimited(BlockWorldState worldState,
         public PatternError testGlobal(BlockWorldState worldState, Object2IntMap cache) {
             PatternError result = predicate.apply(worldState);
 
+            if (cache != null && !cache.containsKey(this)) cache.put(this, 0);
             if ((minGlobalCount == -1 && maxGlobalCount == -1) || cache == null || result != null) return result;
 
             int count = cache.put(this, cache.getInt(this) + 1) + 1;
diff --git a/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
index 7dd000260ae..2564ad56dd1 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
@@ -1,8 +1,11 @@
 package gregtech.api.pattern.pattern;
 
 import gregtech.api.util.GTLog;
+import gregtech.api.util.GTUtility;
 import gregtech.api.util.RelativeDirection;
 
+import net.minecraft.util.math.MathHelper;
+
 import com.google.common.base.Preconditions;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -68,14 +71,19 @@ protected int checkRepeatAisle(int index, int offset, boolean flip) {
         return aisles.get(index).actualRepeats = aisle.maxRepeats;
     }
 
-    // todo more lang support(yay!), and actually use the map arg
+    // todo more lang support(yay!)
     @Override
     public int @NotNull [] getDefaultAisles(Map map) {
         IntList list = new IntArrayList();
-        for (int[] multi : multiAisles) {
-            for (int i = 0; i < multi[0]; i++) {
-                for (int j = multi[2]; j < multi[3]; j++) {
-                    for (int k = 0; k < aisles.get(j).minRepeats; k++) list.add(j);
+        for (int i = 0; i < multiAisles.size(); i++) {
+            int[] multi = multiAisles.get(i);
+            int multiRepeats = MathHelper.clamp(GTUtility.parseInt(map.get("multi." + i)), multi[0], multi[1]);
+            for (int j = 0; j < multiRepeats; j++) {
+                for (int k = multi[2]; k < multi[3]; k++) {
+                    int aisleRepeats = MathHelper.clamp(
+                            GTUtility.parseInt(map.get("multi." + i + "." + (k - multi[2]))), aisles.get(k).minRepeats,
+                            aisles.get(k).maxRepeats);
+                    for (int l = 0; l < aisleRepeats; l++) list.add(k);
                 }
             }
         }
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index f709ff12a39..39b2cf25f53 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -17,15 +17,11 @@
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
 
-import it.unimi.dsi.fastutil.chars.Char2IntMap;
-import it.unimi.dsi.fastutil.chars.Char2IntOpenHashMap;
 import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectRBTreeMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMap;
-import it.unimi.dsi.fastutil.objects.Object2CharMap;
-import it.unimi.dsi.fastutil.objects.Object2CharOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 import org.jetbrains.annotations.NotNull;
@@ -242,7 +238,7 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
                     // todo it caused an exception twice but never reproduced, maybe figure out or just remove
                     try {
                         cache.put(charPos.toLong(), new BlockInfo(worldState.getBlockState(),
-                                !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
+                                !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null));
                     } catch (IllegalArgumentException e) {
                         GTLog.logger.error("bruh");
                         throw e;
@@ -281,7 +277,7 @@ public int getRepetitionCount(int aisleI) {
 
     @Override
     public Long2ObjectSortedMap getDefaultShape(MultiblockControllerBase src,
-                                                                       Map keyMap) {
+                                                                       @NotNull Map keyMap) {
         Long2ObjectSortedMap map = new Long2ObjectRBTreeMap<>();
         EnumFacing absoluteAisle = directions[0].getRelativeFacing(src.getFrontFacing(), src.getUpwardsFacing());
         EnumFacing absoluteString = directions[1].getRelativeFacing(src.getFrontFacing(), src.getUpwardsFacing());
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index 25569c3a411..c3d7cfb8031 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -160,7 +160,7 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
             if (predicate != TraceabilityPredicate.ANY) {
                 TileEntity te = worldState.getTileEntity();
                 cache.put(pos.toLong(), new BlockInfo(worldState.getBlockState(),
-                        !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null, predicate));
+                        !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null));
             }
 
             PatternError result = predicate.test(worldState, globalCount, null);
@@ -181,7 +181,7 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
 
     @Override
     public Long2ObjectSortedMap getDefaultShape(MultiblockControllerBase src,
-                                                                       Map keyMap) {
+                                                                       @NotNull Map keyMap) {
         // todo add this
         return Long2ObjectSortedMaps.emptyMap();
     }
diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
index a9486d831fa..f481d98cf6d 100644
--- a/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java
@@ -150,7 +150,7 @@ public static FactoryBlockPattern start(RelativeDirection aisleDir, RelativeDire
      * @param blockMatcher The predicate to put
      */
     public FactoryBlockPattern where(char symbol, TraceabilityPredicate blockMatcher) {
-        this.symbolMap.put(symbol, new TraceabilityPredicate(blockMatcher).sort());
+        this.symbolMap.put(symbol, blockMatcher);
         if (blockMatcher.isCenter()) centerChar = symbol;
         return this;
     }
diff --git a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
index 843b53b63b0..293bcf21a64 100644
--- a/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java
@@ -13,7 +13,6 @@
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMap;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 import java.util.Map;
 
@@ -59,7 +58,7 @@ boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontFacing,
      *         respectively being. The map is sorted using the natural ordering(thus with x, y, z order).
      */
     Long2ObjectSortedMap getDefaultShape(MultiblockControllerBase src,
-                                                                @Nullable Map keyMap);
+                                                                @NotNull Map keyMap);
 
     /**
      * Gets the internal pattern state, you should use the one returned from
diff --git a/src/main/java/gregtech/api/util/BlockInfo.java b/src/main/java/gregtech/api/util/BlockInfo.java
index b576413cf31..79f23df05b1 100644
--- a/src/main/java/gregtech/api/util/BlockInfo.java
+++ b/src/main/java/gregtech/api/util/BlockInfo.java
@@ -1,7 +1,5 @@
 package gregtech.api.util;
 
-import gregtech.api.pattern.TraceabilityPredicate;
-
 import net.minecraft.block.Block;
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.init.Blocks;
@@ -20,7 +18,6 @@ public class BlockInfo {
 
     private final IBlockState blockState;
     private final TileEntity tileEntity;
-    private final TraceabilityPredicate predicate;
 
     public BlockInfo(Block block) {
         this(block.getDefaultState());
@@ -31,15 +28,10 @@ public BlockInfo(IBlockState blockState) {
     }
 
     public BlockInfo(IBlockState blockState, TileEntity tileEntity) {
-        this(blockState, tileEntity, null);
-    }
-
-    public BlockInfo(IBlockState blockState, TileEntity tileEntity, TraceabilityPredicate predicate) {
         // the predicate is an extremely scuffed way of displaying candidates during preview
         // ideally you would bind the predicate to the char in the code but uh yeah
         this.blockState = blockState;
         this.tileEntity = tileEntity;
-        this.predicate = predicate;
         Preconditions.checkArgument(tileEntity == null || blockState.getBlock().hasTileEntity(blockState),
                 "Cannot create block info with tile entity for block not having it");
     }
@@ -51,8 +43,4 @@ public IBlockState getBlockState() {
     public TileEntity getTileEntity() {
         return tileEntity;
     }
-
-    public TraceabilityPredicate getPredicate() {
-        return predicate;
-    }
 }
diff --git a/src/main/java/gregtech/api/util/GTUtility.java b/src/main/java/gregtech/api/util/GTUtility.java
index 15befed150b..88fc08016df 100644
--- a/src/main/java/gregtech/api/util/GTUtility.java
+++ b/src/main/java/gregtech/api/util/GTUtility.java
@@ -669,8 +669,8 @@ public static ItemStack toItem(World world, BlockPos pos) {
 
         // first check if the block is a GT machine
         TileEntity tileEntity = world.getTileEntity(pos);
-        if (tileEntity instanceof IGregTechTileEntity) {
-            stack = ((IGregTechTileEntity) tileEntity).getMetaTileEntity().getStackForm();
+        if (tileEntity instanceof IGregTechTileEntity igtte) {
+            stack = igtte.getMetaTileEntity().getStackForm();
         }
 
         if (stack.isEmpty()) {
@@ -976,4 +976,15 @@ public static EnumFacing cross(EnumFacing a, EnumFacing b) {
     public static int safeCastLongToInt(long v) {
         return v > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) v;
     }
+
+    /**
+     * Parse the string as an int and return, or return -1 if the string is not an int.
+     */
+    public static int parseInt(String s) {
+        try {
+            return Integer.parseInt(s);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
 }
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index a97a1741871..e42943e89f2 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -4,7 +4,6 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.GreggyBlockPos;
-import gregtech.api.util.BlockInfo;
 import gregtech.api.util.GregFakePlayer;
 import gregtech.client.renderer.scene.ImmediateWorldSceneRenderer;
 import gregtech.client.utils.TrackedDummyWorld;
@@ -37,7 +36,6 @@
 
 import org.lwjgl.opengl.GL11;
 
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
@@ -119,30 +117,26 @@ public static void renderControllerInList(MultiblockControllerBase src, Map blockMap = new HashMap<>();
-
-        // absolutely dog way of doing this, just setting the center at 128 so that both patterns going down and up can
-        // work
         MetaTileEntityHolder holder = new MetaTileEntityHolder();
-        world.setBlockState(SOURCE, src.getBlock().getDefaultState());
         holder.setMetaTileEntity(src);
         holder.getMetaTileEntity().onPlacement();
+        holder.getMetaTileEntity().setFrontFacing(src.getFrontFacing());
+        ((MultiblockControllerBase) holder.getMetaTileEntity()).setUpwardsFacing(src.getUpwardsFacing());
 
+        world.setBlockState(SOURCE, src.getBlock().getDefaultState());
         world.setTileEntity(SOURCE, holder);
 
         ((MultiblockControllerBase) holder.getMetaTileEntity()).autoBuild(new GregFakePlayer(world), keyMap);
+        ((MultiblockControllerBase) holder.getMetaTileEntity()).checkStructurePattern();
 
-        // todo fix
-        int finalMaxY = layer % 5;
-        world.setRenderFilter(pos -> pos.getY() - 127 == finalMaxY || finalMaxY == 0);
+        int finalMaxY = (int) (layer % (world.getMaxPos().y - world.getMinPos().y + 2));
+        world.setRenderFilter(pos -> pos.getY() - (int) world.getMinPos().y + 1 == finalMaxY || finalMaxY == 0);
 
         Minecraft mc = Minecraft.getMinecraft();
         BlockRendererDispatcher brd = mc.getBlockRendererDispatcher();
         Tessellator tes = Tessellator.getInstance();
         BufferBuilder buff = tes.getBuffer();
 
-        ((MultiblockControllerBase) holder.getMetaTileEntity()).checkStructurePattern();
-
         BlockRenderLayer oldLayer = MinecraftForgeClient.getRenderLayer();
         TargetBlockAccess targetBA = new TargetBlockAccess(world, BlockPos.ORIGIN);
 
@@ -150,7 +144,7 @@ public static void renderControllerInList(MultiblockControllerBase src, Map {
+                // curse you who didn't write the line below and wasted 4 hours for me to debug
+                buffer.writeVarInt(holder.getMetaTileEntity().getRegistry().getNetworkId());
                 buffer.writeVarInt(
                         holder.getMetaTileEntity().getRegistry()
                                 .getIdByObjectName(holder.getMetaTileEntity().metaTileEntityId));
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index f45e06546f4..5f6172c60cf 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -1,5 +1,6 @@
 package gregtech.integration.jei.multiblock;
 
+import gregtech.api.block.machines.MachineItemBlock;
 import gregtech.api.gui.GuiTextures;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
@@ -7,7 +8,11 @@
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.pattern.BlockWorldState;
 import gregtech.api.pattern.TraceabilityPredicate;
+import gregtech.api.pipenet.block.material.BlockMaterialPipe;
+import gregtech.api.unification.material.Material;
 import gregtech.api.util.BlockInfo;
+import gregtech.api.util.GTLog;
+import gregtech.api.util.GTStringUtils;
 import gregtech.api.util.GTUtility;
 import gregtech.api.util.GregFakePlayer;
 import gregtech.api.util.ItemStackHashStrategy;
@@ -16,6 +21,8 @@
 import gregtech.client.utils.RenderUtil;
 import gregtech.client.utils.TrackedDummyWorld;
 import gregtech.common.ConfigHolder;
+import gregtech.common.blocks.BlockFrame;
+import gregtech.common.blocks.BlockMaterialBase;
 
 import net.minecraft.block.Block;
 import net.minecraft.block.state.IBlockState;
@@ -37,7 +44,6 @@
 import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.math.RayTraceResult;
 import net.minecraft.util.text.TextFormatting;
-import net.minecraft.world.World;
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
 
@@ -46,6 +52,8 @@
 import codechicken.lib.render.pipeline.ColourMultiplier;
 import codechicken.lib.vec.Cuboid6;
 import codechicken.lib.vec.Translation;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
 import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
 import mezz.jei.api.IGuiHelper;
@@ -56,12 +64,12 @@
 import mezz.jei.api.recipe.IRecipeWrapper;
 import mezz.jei.gui.recipes.RecipeLayout;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 import org.lwjgl.input.Mouse;
 import org.lwjgl.opengl.GL11;
 
 import java.util.*;
 import java.util.Map.Entry;
+import java.util.function.ToLongFunction;
 import java.util.stream.Collectors;
 
 import javax.vecmath.Vector3f;
@@ -76,20 +84,6 @@ public class MultiblockInfoRecipeWrapper implements IRecipeWrapper {
     private static final int ICON_SIZE = 20;
     private static final int RIGHT_PADDING = 5;
 
-    private static class MBPattern {
-
-        final WorldSceneRenderer sceneRenderer;
-        final List parts;
-        final Map predicateMap;
-
-        public MBPattern(final WorldSceneRenderer sceneRenderer, final List parts,
-                         Map predicateMap) {
-            this.sceneRenderer = sceneRenderer;
-            this.parts = parts;
-            this.predicateMap = predicateMap;
-        }
-    }
-
     private final MultiblockControllerBase controller;
     private final MBPattern[] patterns;
     private final Map buttons = new HashMap<>();
@@ -351,7 +345,7 @@ public void drawInfo(@NotNull Minecraft minecraft, int recipeWidth, int recipeHe
             ItemStack itemStack = blockState.getBlock().getPickBlock(blockState, rayTraceResult, renderer.world,
                     rayTraceResult.getBlockPos(), minecraft.player);
             TraceabilityPredicate predicates = patterns[currentRendererPage].predicateMap
-                    .get(rayTraceResult.getBlockPos());
+                    .get(rayTraceResult.getBlockPos().toLong());
             if (predicates != null) {
                 BlockWorldState worldState = new BlockWorldState();
 
@@ -419,7 +413,8 @@ public boolean handleClick(@NotNull Minecraft minecraft, int mouseX, int mouseY,
                 predicates.clear();
                 this.father = null;
                 this.selected = selected;
-                TraceabilityPredicate predicate = patterns[currentRendererPage].predicateMap.get(this.selected);
+                TraceabilityPredicate predicate = patterns[currentRendererPage].predicateMap
+                        .get(this.selected.toLong());
                 if (predicate != null) {
                     predicates.addAll(predicate.simple);
                     predicates.removeIf(p -> p.candidates == null);
@@ -474,78 +469,29 @@ public List getTooltipStrings(int mouseX, int mouseY) {
         return Collections.emptyList();
     }
 
-    private static class PartInfo {
-
-        final ItemStack itemStack;
-        boolean isController = false;
-        boolean isTile = false;
-        final int blockId;
-        int amount = 0;
-
-        PartInfo(final ItemStack itemStack, final BlockInfo blockInfo) {
-            this.itemStack = itemStack;
-            this.blockId = Block.getIdFromBlock(blockInfo.getBlockState().getBlock());
-            TileEntity tileEntity = blockInfo.getTileEntity();
-            if (tileEntity != null) {
-                this.isTile = true;
-                if (tileEntity instanceof IGregTechTileEntity iGregTechTileEntity) {
-                    MetaTileEntity mte = iGregTechTileEntity.getMetaTileEntity();
-                    this.isController = mte instanceof MultiblockControllerBase;
-                }
-            }
-        }
-
-        @NotNull
-        ItemStack getItemStack() {
-            ItemStack result = this.itemStack.copy();
-            result.setCount(this.amount);
-            return result;
-        }
-    }
-
-    @NotNull
-    private static Collection gatherStructureBlocks(World world, @NotNull Map blocks,
-                                                              Set parts) {
-        Map partsMap = new Object2ObjectOpenCustomHashMap<>(
-                ItemStackHashStrategy.comparingAllButCount());
-        for (Entry entry : blocks.entrySet()) {
-            ItemStack stack = GTUtility.toItem(world, entry.getKey());
-
-            // if we got a stack, add it to the set and map
-            if (!stack.isEmpty()) {
-                parts.add(stack);
-
-                PartInfo partInfo = partsMap.get(stack);
-                if (partInfo == null) {
-                    partInfo = new PartInfo(stack, entry.getValue());
-                    partsMap.put(stack, partInfo);
-                }
-                partInfo.amount++;
-            }
-        }
-        return partsMap.values();
-    }
-
     @NotNull
     // todo substructure support
-    private MBPattern initializePattern(@NotNull MultiblockControllerBase src, @Nullable Map keyMap,
+    private MBPattern initializePattern(@NotNull MultiblockControllerBase src, @NotNull Map keyMap,
                                         @NotNull Set parts) {
+        Map partsMap = new Object2ObjectOpenCustomHashMap<>(
+                ItemStackHashStrategy.comparingAllButCount());
         TrackedDummyWorld world = new TrackedDummyWorld();
+
         ImmediateWorldSceneRenderer worldSceneRenderer = new ImmediateWorldSceneRenderer(world);
         worldSceneRenderer.setClearColor(ConfigHolder.client.multiblockPreviewColor);
 
-        Map blockMap = new HashMap<>();
-
-        // absolutely dog way of doing this, just setting the center at 128 so that both patterns going down and up can
-        // work
         MetaTileEntityHolder holder = new MetaTileEntityHolder();
-        world.setBlockState(SOURCE, src.getBlock().getDefaultState());
         holder.setMetaTileEntity(src);
         holder.getMetaTileEntity().onPlacement();
 
+        world.setBlockState(SOURCE, src.getBlock().getDefaultState());
         world.setTileEntity(SOURCE, holder);
 
-        ((MultiblockControllerBase) holder.getMetaTileEntity()).autoBuild(new GregFakePlayer(world), keyMap);
+        Long2ObjectMap predicates = ((MultiblockControllerBase) holder.getMetaTileEntity())
+                .getSubstructure("MAIN").getDefaultShape((MultiblockControllerBase) holder.getMetaTileEntity(), keyMap);
+        Long2ObjectMap copy = new Long2ObjectOpenHashMap<>(predicates);
+        ((MultiblockControllerBase) holder.getMetaTileEntity()).autoBuild(new GregFakePlayer(world), keyMap,
+                predicates);
 
         Vector3f size = world.getSize();
         Vector3f minPos = world.getMinPos();
@@ -568,25 +514,51 @@ private MBPattern initializePattern(@NotNull MultiblockControllerBase src, @Null
         world.setRenderFilter(
                 pos -> worldSceneRenderer.renderedBlocksMap.keySet().stream().anyMatch(c -> c.contains(pos)));
 
-        Map predicateMap = new HashMap<>();
+        for (BlockPos pos : world.renderedBlocks) {
+            ItemStack stack = GTUtility.toItem(world, pos);
+            // if we got a stack, add it to the set and map
+            if (!stack.isEmpty()) {
+                parts.add(stack);
+
+                if (!partsMap.containsKey(stack)) {
+                    partsMap.put(stack, stack);
+                } else partsMap.get(stack).setCount(partsMap.get(stack).getCount() + 1);
+            }
+        }
+
+        ToLongFunction comp = i -> {
+            if (i.getItem() instanceof MachineItemBlock) {
+                MetaTileEntity mte = GTUtility.getMetaTileEntity(i);
+                if (mte == null) return 0;
+                if (mte.getClass() == src.getClass()) return Long.MIN_VALUE;
+                return -(((long) i.getItemDamage() << 32) | i.getCount());
+            } else {
+                Block block = Block.getBlockFromItem(i.getItem());
+                Material mat = null;
+                long prio = 0;
+                if (block instanceof BlockMaterialBase base) {
+                    mat = base.getGtMaterial(i);
+                    if (base instanceof BlockFrame) prio = 1;
+                } else if (block instanceof BlockMaterialPipe pipe) {
+                    mat = pipe.getItemMaterial(i);
+                    prio = 2;
+                }
+
+                if (mat == null) return ((long) i.getItemDamage() << 48) | i.getCount();
+
+                return prio << 32 | (long) i.getCount() << 16 | mat.getId();
+            }
+        };
+
+        List sortedParts = new ArrayList<>(partsMap.values());
 
-        if (controller.isStructureFormed("MAIN")) {
-            controller.getSubstructure("MAIN").getCache().forEach((pos, blockInfo) -> predicateMap
-                    .put(BlockPos.fromLong(pos), blockInfo.getPredicate()));
+        for (ItemStack part : sortedParts) {
+            GTLog.logger.info(GTStringUtils.prettyPrintItemStack(part) + " with prio " + comp.applyAsLong(part));
         }
 
-        // todo fix this sort
-        List sortedParts = gatherStructureBlocks(worldSceneRenderer.world, blockMap, parts).stream()
-                .sorted((one, two) -> {
-                    if (one.isController) return -1;
-                    if (two.isController) return +1;
-                    if (one.isTile && !two.isTile) return -1;
-                    if (two.isTile && !one.isTile) return +1;
-                    if (one.blockId != two.blockId) return two.blockId - one.blockId;
-                    return two.amount - one.amount;
-                }).map(PartInfo::getItemStack).collect(Collectors.toList());
-
-        return new MBPattern(worldSceneRenderer, sortedParts, predicateMap);
+        sortedParts.sort(Comparator.comparingLong(comp));
+
+        return new MBPattern(worldSceneRenderer, sortedParts, copy);
     }
 
     @SideOnly(Side.CLIENT)
@@ -625,4 +597,47 @@ public static ItemStack getHoveredItemStack() {
         }
         return null;
     }
+
+    private static class PartInfo {
+
+        final ItemStack itemStack;
+        boolean isController = false;
+        boolean isTile = false;
+        final int blockId;
+        int amount = 0;
+
+        PartInfo(final ItemStack itemStack, final BlockInfo blockInfo) {
+            this.itemStack = itemStack;
+            this.blockId = Block.getIdFromBlock(blockInfo.getBlockState().getBlock());
+            TileEntity tileEntity = blockInfo.getTileEntity();
+            if (tileEntity != null) {
+                this.isTile = true;
+                if (tileEntity instanceof IGregTechTileEntity iGregTechTileEntity) {
+                    MetaTileEntity mte = iGregTechTileEntity.getMetaTileEntity();
+                    this.isController = mte instanceof MultiblockControllerBase;
+                }
+            }
+        }
+
+        @NotNull
+        ItemStack getItemStack() {
+            ItemStack result = this.itemStack.copy();
+            result.setCount(this.amount);
+            return result;
+        }
+    }
+
+    private static class MBPattern {
+
+        final WorldSceneRenderer sceneRenderer;
+        final List parts;
+        final Long2ObjectMap predicateMap;
+
+        public MBPattern(final WorldSceneRenderer sceneRenderer, final List parts,
+                         Long2ObjectMap predicateMap) {
+            this.sceneRenderer = sceneRenderer;
+            this.parts = parts;
+            this.predicateMap = predicateMap;
+        }
+    }
 }

From 8f9537d55615f5d63b145bfa3fd96504775e9695 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Tue, 24 Dec 2024 19:29:44 -0800
Subject: [PATCH 51/64] the todo crusade

---
 .../multiblock/MultiblockControllerBase.java  | 82 ++++++++++++-------
 .../api/pattern/pattern/BlockPattern.java     | 10 +--
 .../pattern/pattern/ExpandablePattern.java    | 41 +++++++++-
 .../gregtech/api/util/RelativeDirection.java  | 29 ++-----
 .../handler/MultiblockPreviewRenderer.java    |  2 +-
 .../behaviors/MultiblockBuilderBehavior.java  | 15 ++--
 .../electric/MetaTileEntityCleanroom.java     | 31 ++++---
 .../MetaTileEntityMultiblockPart.java         |  1 -
 .../MetaTileEntityCharcoalPileIgniter.java    | 11 ++-
 .../MultiblockInfoRecipeWrapper.java          | 19 +++--
 .../resources/assets/gregtech/lang/en_us.lang | 10 +++
 .../gregtech/api/util/TierByVoltageTest.java  | 19 -----
 12 files changed, 158 insertions(+), 112 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 0cc72d94fac..241bf8d67bf 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -71,11 +71,13 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableSet;
 import java.util.Objects;
+import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.BiConsumer;
 import java.util.function.BiPredicate;
@@ -136,7 +138,6 @@ public void update() {
     /**
      * @return structure pattern of this multiblock
      */
-    // todo fix vacuum freezer
     @NotNull
     protected abstract IBlockPattern createStructurePattern();
 
@@ -412,18 +413,23 @@ public void checkStructurePattern(String name) {
             if (result.isFormed()) {
                 // fast rebuild parts
                 if (result.getState() == PatternState.EnumCheckState.VALID_UNCACHED) {
-                    // add any new parts, because removal of parts is impossible
-                    // it is possible for old parts to persist, so check that
-                    List> addedParts = new ArrayList<>();
-
                     forEachMultiblockPart(name, part -> {
                         if (multiblockParts.contains(part)) return true;
 
-                        // todo move below into separate check
                         if (part.isAttachedToMultiBlock() && !part.canPartShare(this, name)) {
                             invalidateStructure(name);
                             return false;
                         }
+                        return true;
+                    });
+
+                    // add any new parts, because removal of parts is impossible
+                    // it is possible for old parts to persist, so check that
+                    List> addedParts = new ArrayList<>();
+
+                    forEachMultiblockPart(name, part -> {
+                        if (multiblockParts.contains(part)) return true;
+
                         part.addToMultiBlock(this, name);
                         if (part instanceof IMultiblockAbilityPartabilityPart) {
                             // noinspection unchecked
@@ -584,6 +590,21 @@ public IBlockPattern getSubstructure(String name) {
         return structures.get(name);
     }
 
+    public String trySubstructure(String name) {
+        if (structures.get(name) != null) return name;
+        return "MAIN";
+    }
+
+    public Set trySubstructure(Map map) {
+        // maybe lang?
+        Set set = new HashSet<>();
+        for (String key : map.keySet()) {
+            if (key.startsWith("substructure")) set.add(trySubstructure(map.get(key)));
+        }
+        if (set.isEmpty()) set.add("MAIN");
+        return set;
+    }
+
     @Override
     public void onRemoval() {
         super.onRemoval();
@@ -633,7 +654,6 @@ public NBTTagCompound writeToNBT(NBTTagCompound data) {
     public void writeInitialSyncData(PacketBuffer buf) {
         super.writeInitialSyncData(buf);
         buf.writeByte(upwardsFacing.getIndex());
-        // todo see if necessary to sync structure formed
     }
 
     @Override
@@ -649,7 +669,7 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
             this.upwardsFacing = EnumFacing.VALUES[buf.readByte()];
             scheduleRenderUpdate();
         } else if (dataId == STRUCTURE_FORMED) {
-            // todo rewrite this entire thing :skull:(including the server side code)
+            // todo rewrite this entire thing :skull:(including the server side code for multiblock abilities)
             String name = buf.readString(65536);
             if ("null".equals(name)) {
                 for (IBlockPattern pattern : structures.values()) {
@@ -804,17 +824,19 @@ protected Map computeNext() {
      * Autobuilds the multiblock, using the {@code substructure} string to select the substructure, or the main
      * structure if invalid.
      */
-    public void autoBuild(EntityPlayer player, Map map) {
+    public void autoBuild(EntityPlayer player, Map map, String substructure) {
         if (getWorld().isRemote) throw new IllegalArgumentException("client side call wuh");
 
-        Long2ObjectMap predicates = getSubstructure("MAIN").getDefaultShape(this, map);
+        IBlockPattern structure = getSubstructure(trySubstructure(substructure));
+
+        Long2ObjectMap predicates = structure.getDefaultShape(this, map);
         if (predicates == null) return;
 
         autoBuild(player, map, predicates);
     }
 
     /**
-     * Autobuild the multiblock, this is like {@link MultiblockControllerBase#autoBuild(EntityPlayer, Map)} but if
+     * Autobuild the multiblock, this is like {@link MultiblockControllerBase#autoBuild(EntityPlayer, Map, String)} but if
      * you have the predicate map for other uses. This does mutate the map passed in.
      */
     public void autoBuild(EntityPlayer player, Map map,
@@ -843,22 +865,28 @@ public void autoBuild(EntityPlayer player, Map map,
                     if (removed.hasTagCompound())
                         newHolder.getMetaTileEntity().initFromItemStackData(removed.getTagCompound());
 
+                    if (predicates.containsKey(pos.offset(newHolder.getMetaTileEntity().getFrontFacing()).toLong())) {
+                        EnumFacing valid = null;
+                        for (EnumFacing facing : EnumFacing.HORIZONTALS) {
+                            if (!predicates.containsKey(pos.offset(facing).toLong())) {
+                                valid = facing;
+                                break;
+                            }
+                        }
+                        if (valid != null) newHolder.getMetaTileEntity().setFrontFacing(valid);
+                        else {
+                            if (!predicates.containsKey(pos.offset(EnumFacing.UP).toLong())) {
+                                newHolder.getMetaTileEntity().setFrontFacing(EnumFacing.UP);
+                            } else if (!predicates.containsKey(pos.offset(EnumFacing.DOWN).toLong())) {
+                                newHolder.getMetaTileEntity().setFrontFacing(EnumFacing.DOWN);
+                            }
+                        }
+                    }
+
                     getWorld().setBlockState(pos, holder.getMetaTileEntity().getBlock().getDefaultState());
                     getWorld().setTileEntity(pos, newHolder);
 
                     // todo add relative facing fix to make hatches face air
-                    // // get the relative direction from the part facing, then use that to get the real enum facing
-                    // EnumFacing newFacing = FACING_MAP.get(holder.getMetaTileEntity().getFrontFacing())
-                    // .getRelativeFacing(frontFacing, upwardsFacing, false);
-                    // newHolder.getMetaTileEntity().setFrontFacing(newFacing);
-                    //
-                    // if (holder.getMetaTileEntity() instanceof MultiblockControllerBase holderBase) {
-                    // MultiblockControllerBase mteBase = (MultiblockControllerBase) newHolder.getMetaTileEntity();
-                    //
-                    // EnumFacing newUpFacing = FACING_MAP.get(holderBase.getUpwardsFacing())
-                    // .getRelativeFacing(frontFacing, upwardsFacing, false);
-                    // mteBase.setUpwardsFacing(newUpFacing);
-                    // }
                 } else return false;
             } else {
                 if (!hasAndRemoveItem(player, GTUtility.toItem(info.getBlockState())).isEmpty())
@@ -869,9 +897,7 @@ public void autoBuild(EntityPlayer player, Map map,
             return true;
         };
 
-        for (Iterator> iter = predicates.long2ObjectEntrySet()
-                .iterator(); iter.hasNext();) {
-            Long2ObjectMap.Entry entry = iter.next();
+        for (Long2ObjectMap.Entry entry : predicates.long2ObjectEntrySet()) {
             // todo add autobuild key params here, also remove layer stuff from rest of the code elsewhere
             TraceabilityPredicate pred = entry.getValue();
             if (simpleIndex.getInt(pred) >= pred.simple.size()) continue;
@@ -902,14 +928,14 @@ public void autoBuild(EntityPlayer player, Map map,
 
             if (!place.test(entry.getLongKey(), cache.get(simple))) return;
 
-            iter.remove();
+            entry.setValue(null);
         }
 
         simpleIndex.clear();
 
         for (Long2ObjectMap.Entry entry : predicates.long2ObjectEntrySet()) {
             TraceabilityPredicate pred = entry.getValue();
-            if (simpleIndex.getInt(pred) >= pred.simple.size()) continue;
+            if (pred == null || simpleIndex.getInt(pred) >= pred.simple.size()) continue;
 
             TraceabilityPredicate.SimplePredicate simple = pred.simple.get(simpleIndex.getInt(pred));
             int count = globalCache.getInt(simple);
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 39b2cf25f53..0f2175280a9 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -235,14 +235,8 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
 
                 if (predicate != TraceabilityPredicate.ANY) {
                     TileEntity te = worldState.getTileEntity();
-                    // todo it caused an exception twice but never reproduced, maybe figure out or just remove
-                    try {
-                        cache.put(charPos.toLong(), new BlockInfo(worldState.getBlockState(),
-                                !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null));
-                    } catch (IllegalArgumentException e) {
-                        GTLog.logger.error("bruh");
-                        throw e;
-                    }
+                    cache.put(charPos.toLong(), new BlockInfo(worldState.getBlockState(),
+                            !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null));
                 }
 
                 // GTLog.logger.info("Checked pos at " + charPos + " with flip " + flip);
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index c3d7cfb8031..3c13457d77e 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -11,6 +11,8 @@
 import gregtech.api.util.RelativeDirection;
 import gregtech.api.util.function.QuadFunction;
 
+import it.unimi.dsi.fastutil.longs.Long2ObjectRBTreeMap;
+
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
@@ -182,8 +184,43 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
     @Override
     public Long2ObjectSortedMap getDefaultShape(MultiblockControllerBase src,
                                                                        @NotNull Map keyMap) {
-        // todo add this
-        return Long2ObjectSortedMaps.emptyMap();
+        EnumFacing front = src.getFrontFacing();
+        EnumFacing up = src.getUpwardsFacing();
+
+        int[] bounds = boundsFunction.apply(src.getWorld(), new GreggyBlockPos(src.getPos()), front, up);
+        if (bounds == null) return Long2ObjectSortedMaps.emptyMap();
+
+        Long2ObjectSortedMap predicates = new Long2ObjectRBTreeMap<>();
+
+        GreggyBlockPos negativeCorner = new GreggyBlockPos();
+        GreggyBlockPos positiveCorner = new GreggyBlockPos();
+
+        EnumFacing[] absolutes = new EnumFacing[3];
+
+        for (int i = 0; i < 3; i++) {
+            RelativeDirection selected = directions[i];
+
+            absolutes[i] = selected.getRelativeFacing(front, up, false);
+
+            negativeCorner.set(i, -bounds[selected.oppositeOrdinal()]);
+            positiveCorner.set(i, bounds[selected.ordinal()]);
+        }
+
+        GreggyBlockPos translation = new GreggyBlockPos(src.getPos());
+
+        for (GreggyBlockPos pos : GreggyBlockPos.allInBox(negativeCorner, positiveCorner, EnumFacing.SOUTH,
+                EnumFacing.UP, EnumFacing.EAST)) {
+            TraceabilityPredicate predicate = predicateFunction.apply(pos, bounds);
+
+            int[] arr = pos.getAll();
+            pos.zero().offset(absolutes[0], arr[0]).offset(absolutes[1], arr[1]).offset(absolutes[2], arr[2]).add(translation);
+
+            if (predicate != TraceabilityPredicate.ANY && predicate != TraceabilityPredicate.AIR) {
+                predicates.put(pos.toLong(), predicate);
+            }
+        }
+
+        return predicates;
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/util/RelativeDirection.java b/src/main/java/gregtech/api/util/RelativeDirection.java
index 608db72674b..0b8020bd7cd 100644
--- a/src/main/java/gregtech/api/util/RelativeDirection.java
+++ b/src/main/java/gregtech/api/util/RelativeDirection.java
@@ -1,5 +1,7 @@
 package gregtech.api.util;
 
+import gregtech.api.pattern.GreggyBlockPos;
+
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
 
@@ -97,29 +99,12 @@ public static EnumFacing simulateAxisRotation(EnumFacing newFrontFacing, EnumFac
      * Offset a BlockPos relatively in any direction by any amount. Pass negative values to offset down, right or
      * backwards.
      */
-    // todo rework/remove this also
     public static BlockPos offsetPos(BlockPos pos, EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped,
                                      int upOffset, int leftOffset, int forwardOffset) {
-        if (upOffset == 0 && leftOffset == 0 && forwardOffset == 0) {
-            return pos;
-        }
-
-        int oX = 0, oY = 0, oZ = 0;
-        final EnumFacing relUp = UP.getRelativeFacing(frontFacing, upwardsFacing, isFlipped);
-        oX += relUp.getXOffset() * upOffset;
-        oY += relUp.getYOffset() * upOffset;
-        oZ += relUp.getZOffset() * upOffset;
-
-        final EnumFacing relLeft = LEFT.getRelativeFacing(frontFacing, upwardsFacing, isFlipped);
-        oX += relLeft.getXOffset() * leftOffset;
-        oY += relLeft.getYOffset() * leftOffset;
-        oZ += relLeft.getZOffset() * leftOffset;
-
-        final EnumFacing relForward = FRONT.getRelativeFacing(frontFacing, upwardsFacing, isFlipped);
-        oX += relForward.getXOffset() * forwardOffset;
-        oY += relForward.getYOffset() * forwardOffset;
-        oZ += relForward.getZOffset() * forwardOffset;
-
-        return pos.add(oX, oY, oZ);
+        GreggyBlockPos greg = new GreggyBlockPos(pos);
+        greg.offset(UP.getRelativeFacing(frontFacing, upwardsFacing, isFlipped), upOffset);
+        greg.offset(LEFT.getRelativeFacing(frontFacing, upwardsFacing, isFlipped), leftOffset);
+        greg.offset(FRONT.getRelativeFacing(frontFacing, upwardsFacing, isFlipped), forwardOffset);
+        return greg.immutable();
     }
 }
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index e42943e89f2..0adad4f9755 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -126,7 +126,7 @@ public static void renderControllerInList(MultiblockControllerBase src, Map map = getMap(player.getHeldItem(hand));
+        String structure = multiblock.trySubstructure(map).iterator().next();
+
         if (player.isSneaking()) {
             // If sneaking, try to build the multiblock.
             // Only try to auto-build if the structure is not already formed
-            if (!multiblock.isStructureFormed("MAIN")) {
-                multiblock.autoBuild(player, getMap(player.getHeldItem(hand)));
+            if (!multiblock.isStructureFormed(structure)) {
+                multiblock.autoBuild(player, map, structure);
                 return EnumActionResult.SUCCESS;
             }
             return EnumActionResult.PASS;
         } else {
             // If not sneaking, try to show structure debug info (if any) in chat.
-            if (!multiblock.isStructureFormed("MAIN")) {
-                PatternError error = multiblock.getSubstructure("MAIN").getPatternState().getError();
+            if (!multiblock.isStructureFormed(structure)) {
+                PatternError error = multiblock.getSubstructure(structure).getPatternState().getError();
                 if (error != null) {
                     player.sendMessage(
                             new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header"));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index 3025cb96f91..69353046201 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -84,9 +84,11 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 import static gregtech.api.gui.widgets.AdvancedTextWidget.withButton;
@@ -117,7 +119,7 @@ public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase
     /**
      * Reverse map from enum facing -> relative direction, refreshed on every setFrontFacing(...) call
      */
-    private final Map facingMap = new HashMap<>();
+    private final Map facingMap = new EnumMap<>(EnumFacing.class);
 
     public MetaTileEntityCleanroom(ResourceLocation metaTileEntityId) {
         super(metaTileEntityId);
@@ -172,10 +174,9 @@ protected void formStructure(String name) {
         // max progress is based on the dimensions of the structure: (x^3)-(x^2)
         // taller cleanrooms take longer than wider ones
         // minimum of 100 is a 5x5x5 cleanroom: 125-25=100 ticks
-        // todo fix
-        // this.cleanroomLogic.setMaxProgress(Math.max(100,
-        // ((lDist + rDist + 1) * (bDist + fDist + 1) * hDist) - ((lDist + rDist + 1) * (bDist + fDist + 1))));
-        this.cleanroomLogic.setMaxProgress(100);
+        int leftRight = bounds[2] + bounds[3] + 1;
+        int frontBack = bounds[4] + bounds[5] + 1;
+        this.cleanroomLogic.setMaxProgress(leftRight * frontBack * bounds[1]);
         this.cleanroomLogic.setMinEnergyTier(cleanroomFilter.getMinTier());
     }
 
@@ -396,30 +397,28 @@ protected void addDisplayText(List textList) {
                 .addCustom(tl -> {
                     if (isStructureFormed("MAIN")) return;
 
-                    // todo lang translations
-                    tl.add(getWithButton("North: ", EnumFacing.NORTH));
-                    tl.add(getWithButton("West: ", EnumFacing.WEST));
-                    tl.add(getWithButton("South: ", EnumFacing.SOUTH));
-                    tl.add(getWithButton("East: ", EnumFacing.EAST));
-                    tl.add(getWithButton("Height: ", EnumFacing.DOWN));
+                    tl.add(getWithButton(EnumFacing.NORTH));
+                    tl.add(getWithButton(EnumFacing.WEST));
+                    tl.add(getWithButton(EnumFacing.SOUTH));
+                    tl.add(getWithButton(EnumFacing.EAST));
+                    tl.add(getWithButton(EnumFacing.DOWN));
 
-                    tl.add(withButton(new TextComponentString(renderingAABB ? "[Disable Outline]" : "[Enable Outline]"),
-                            "render:" +
-                                    renderingAABB));
+                    tl.add(withButton(new TextComponentTranslation("gregtech.multiblock.render." + renderingAABB), "render:" + renderingAABB));
                 })
                 .addEnergyUsageExactLine(isClean() ? 4 : GTValues.VA[getEnergyTier()])
                 .addWorkingStatusLine()
                 .addProgressLine(getProgressPercent() / 100.0);
     }
 
-    protected ITextComponent getWithButton(String text, EnumFacing facing) {
+    protected ITextComponent getWithButton(EnumFacing facing) {
         RelativeDirection relative = facing == EnumFacing.DOWN ? RelativeDirection.DOWN : facingMap.get(facing);
         if (relative == null)
             return new TextComponentString("null value at facingMap.get(EnumFacing." + facing.getName() + ")");
 
         String name = relative.name();
 
-        ITextComponent button = new TextComponentString(text + bounds[relative.ordinal()]);
+        ITextComponent button = new TextComponentTranslation("gregtech.direction." + facing.getName().toLowerCase(
+                Locale.ROOT)).appendText(": " + bounds[relative.ordinal()]);
         button.appendText(" ");
         button.appendSibling(withButton(new TextComponentString("[-]"), name + ":-"));
         button.appendText(" ");
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
index aa06de607fa..7f916dc3edf 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
@@ -166,7 +166,6 @@ public List getControllers() {
         return controllers;
     }
 
-    // todo either remove the cache or fix it, currently doesn't render update properly(see large boiler active)
     public ICubeRenderer getBaseTexture() {
         MultiblockControllerBase controller = getController();
         if (controller != null) {
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index fa232bc1573..777701d67b3 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -43,6 +43,7 @@
 import net.minecraft.util.SoundCategory;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.text.TextComponentString;
+import net.minecraft.util.text.TextComponentTranslation;
 import net.minecraft.world.World;
 import net.minecraftforge.common.MinecraftForge;
 import net.minecraftforge.common.capabilities.Capability;
@@ -67,8 +68,10 @@
 import stanhebben.zenscript.annotations.ZenMethod;
 
 import java.util.Collection;
+import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -101,7 +104,7 @@ public class MetaTileEntityCharcoalPileIgniter extends MultiblockControllerBase
     /**
      * Reverse map from enum facing -> relative direction, refreshed on every setFrontFacing(...) call
      */
-    private final Map facingMap = new HashMap<>();
+    private final Map facingMap = new EnumMap<>(EnumFacing.class);
 
     public MetaTileEntityCharcoalPileIgniter(ResourceLocation metaTileEntityId) {
         super(metaTileEntityId);
@@ -255,7 +258,6 @@ protected void updateFacingMap() {
     @Override
     public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing,
                                       CuboidRayTraceResult hitResult) {
-        // todo add general direction lang(applies for cleanroom as well)
         if (!playerIn.isSneaking()) {
             if (getWorld().isRemote) return true;
 
@@ -265,7 +267,10 @@ public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFaci
                 bounds[dir.ordinal()] = (dir == RelativeDirection.DOWN ? MIN_DEPTH : MIN_RADIUS);
             }
 
-            playerIn.sendMessage(new TextComponentString(facing.name() + " radius: " + bounds[dir.ordinal()]));
+            playerIn.sendMessage(
+                    new TextComponentTranslation("gregtech.direction." + facing.name().toLowerCase(Locale.ROOT))
+                            .appendText(" ")
+                            .appendSibling(new TextComponentTranslation("gregtech.machine.miner.radius", bounds[dir.ordinal()])));
             getSubstructure("MAIN").clearCache();
             return true;
         }
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 5f6172c60cf..5f1105fbee1 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -470,8 +470,7 @@ public List getTooltipStrings(int mouseX, int mouseY) {
     }
 
     @NotNull
-    // todo substructure support
-    private MBPattern initializePattern(@NotNull MultiblockControllerBase src, @NotNull Map keyMap,
+    private MBPattern initializePattern(@NotNull MultiblockControllerBase src, @NotNull Map map,
                                         @NotNull Set parts) {
         Map partsMap = new Object2ObjectOpenCustomHashMap<>(
                 ItemStackHashStrategy.comparingAllButCount());
@@ -487,11 +486,17 @@ private MBPattern initializePattern(@NotNull MultiblockControllerBase src, @NotN
         world.setBlockState(SOURCE, src.getBlock().getDefaultState());
         world.setTileEntity(SOURCE, holder);
 
-        Long2ObjectMap predicates = ((MultiblockControllerBase) holder.getMetaTileEntity())
-                .getSubstructure("MAIN").getDefaultShape((MultiblockControllerBase) holder.getMetaTileEntity(), keyMap);
-        Long2ObjectMap copy = new Long2ObjectOpenHashMap<>(predicates);
-        ((MultiblockControllerBase) holder.getMetaTileEntity()).autoBuild(new GregFakePlayer(world), keyMap,
-                predicates);
+        Set structures = src.trySubstructure(map);
+
+        Long2ObjectMap copy = new Long2ObjectOpenHashMap<>();
+        for (String  structure : structures) {
+            Long2ObjectMap predicates = ((MultiblockControllerBase) holder.getMetaTileEntity())
+                    .getSubstructure(structure)
+                    .getDefaultShape((MultiblockControllerBase) holder.getMetaTileEntity(), map);
+            copy.putAll(predicates);
+            ((MultiblockControllerBase) holder.getMetaTileEntity()).autoBuild(new GregFakePlayer(world), map,
+                    predicates);
+        }
 
         Vector3f size = world.getSize();
         Vector3f minPos = world.getMinPos();
diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang
index 389d68cd3ce..ea52027ef64 100644
--- a/src/main/resources/assets/gregtech/lang/en_us.lang
+++ b/src/main/resources/assets/gregtech/lang/en_us.lang
@@ -5817,6 +5817,16 @@ gregtech.multiblock.hpca.info_coolant_name=PCB Coolant
 gregtech.multiblock.hpca.info_bridging_enabled=Bridging Enabled
 gregtech.multiblock.hpca.info_bridging_disabled=Bridging Disabled
 
+gregtech.multiblock.render.true=Enable Outline
+gregtech.multiblock.render.false=Disable Outline
+
+gregtech.direction.east=East
+gregtech.direction.west=West
+gregtech.direction.north=North
+gregtech.direction.south=South
+gregtech.direction.up=Up
+gregtech.direction.down=Down
+
 gregtech.command.usage=Usage: /gregtech 
 gregtech.command.worldgen.usage=Usage: /gregtech worldgen 
 gregtech.command.worldgen.reload.usage=Usage: /gregtech worldgen reload
diff --git a/src/test/java/gregtech/api/util/TierByVoltageTest.java b/src/test/java/gregtech/api/util/TierByVoltageTest.java
index 4c4e920d6a6..d0f6fdc75a5 100644
--- a/src/test/java/gregtech/api/util/TierByVoltageTest.java
+++ b/src/test/java/gregtech/api/util/TierByVoltageTest.java
@@ -10,25 +10,6 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class TierByVoltageTest {
-
-    @Test
-    public void temp() {
-        // todo remove this
-        int iters = 0;
-        // for (GreggyBlockPos a : GreggyBlockPos.allInBox(new GreggyBlockPos(0, 0, 0), new GreggyBlockPos(2, 3, 4),
-        // EnumFacing.NORTH, EnumFacing.UP, EnumFacing.WEST)) {
-        // System.out.println(a);
-        // iters++;
-        // }
-
-        for (BlockPos.MutableBlockPos a : BlockPos.getAllInBoxMutable(new BlockPos(0, 0, 0), new BlockPos(2, 3, 4))) {
-            System.out.println(a.toString());
-            iters++;
-        }
-
-        assertEquals(3 * 4 * 5, iters);
-    }
-
     @Test
     public void testV() {
         expectTier(V[ULV], ULV, ULV);

From 40c8276fb50ae5728f3563ef8275f3a8cbf355ec Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Wed, 25 Dec 2024 19:10:38 -0800
Subject: [PATCH 52/64] MultiblockShapeInfo is half dead

---
 .../multiblock/MultiblockControllerBase.java  | 169 +++++++++++-------
 .../api/pattern/TraceabilityPredicate.java    |  30 ++--
 .../api/pattern/pattern/BlockPattern.java     |   1 +
 .../java/gregtech/api/util/GTUtility.java     |  10 +-
 .../electric/MetaTileEntityCleanroom.java     |  60 ++-----
 .../MetaTileEntityElectricBlastFurnace.java   |  41 ++---
 .../electric/MetaTileEntityFusionReactor.java |  62 ++-----
 .../MetaTileEntityPowerSubstation.java        |  42 ++---
 .../MetaTileEntityResearchStation.java        |   1 +
 9 files changed, 188 insertions(+), 228 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 241bf8d67bf..e3069d43a9a 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -5,6 +5,7 @@
 import gregtech.api.capability.GregtechCapabilities;
 import gregtech.api.capability.IMultiblockController;
 import gregtech.api.capability.IMultipleRecipeMaps;
+import gregtech.api.metatileentity.ITieredMetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.MetaTileEntityHolder;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
@@ -43,6 +44,7 @@
 import net.minecraft.util.EnumHand;
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.MathHelper;
 import net.minecraft.world.World;
 import net.minecraftforge.common.capabilities.Capability;
 import net.minecraftforge.fml.relauncher.Side;
@@ -219,7 +221,7 @@ public TextureAtlasSprite getFrontDefaultTexture() {
     }
 
     public static TraceabilityPredicate tilePredicate(@NotNull BiPredicate predicate,
-                                                      @Nullable Supplier candidates) {
+                                                      @Nullable Function, BlockInfo[]> candidates) {
         return new TraceabilityPredicate(worldState -> {
             TileEntity tileEntity = worldState.getTileEntity();
             if (!(tileEntity instanceof IGregTechTileEntity)) return PatternError.PLACEHOLDER;
@@ -235,8 +237,16 @@ public static TraceabilityPredicate metaTileEntities(MetaTileEntity... metaTileE
                 getCandidates(metaTileEntities));
     }
 
-    private static Supplier getCandidates(MetaTileEntity... metaTileEntities) {
-        return () -> Arrays.stream(metaTileEntities).filter(Objects::nonNull).map(tile -> {
+    @SafeVarargs
+    public static  TraceabilityPredicate tieredMTEs(BiPredicate, T> pred, T... metaTileEntities) {
+        ResourceLocation[] ids = Arrays.stream(metaTileEntities).filter(Objects::nonNull)
+                .map(tile -> tile.metaTileEntityId).toArray(ResourceLocation[]::new);
+        return tilePredicate((state, tile) -> ArrayUtils.contains(ids, tile.metaTileEntityId),
+                getCandidates(pred, metaTileEntities));
+    }
+
+    private static Function, BlockInfo[]> getCandidates(MetaTileEntity... metaTileEntities) {
+        return map -> Arrays.stream(metaTileEntities).filter(Objects::nonNull).map(tile -> {
             MetaTileEntityHolder holder = new MetaTileEntityHolder();
             holder.setMetaTileEntity(tile);
             holder.getMetaTileEntity().onPlacement();
@@ -245,8 +255,27 @@ private static Supplier getCandidates(MetaTileEntity... metaTileEnt
         }).toArray(BlockInfo[]::new);
     }
 
-    private static Supplier getCandidates(IBlockState... allowedStates) {
-        return () -> Arrays.stream(allowedStates).map(state -> new BlockInfo(state, null)).toArray(BlockInfo[]::new);
+    // generic hell
+    @SafeVarargs
+    public static  Function, BlockInfo[]> getCandidates(BiPredicate, T> pred, T... metaTileEntities) {
+        return map -> Arrays.stream(metaTileEntities).filter(Objects::nonNull)
+            .filter(i -> pred.test(map, i))
+            .map(tile -> {
+                MetaTileEntityHolder holder = new MetaTileEntityHolder();
+                holder.setMetaTileEntity(tile);
+                holder.getMetaTileEntity().onPlacement();
+                holder.getMetaTileEntity().setFrontFacing(EnumFacing.SOUTH);
+                return new BlockInfo(tile.getBlock().getDefaultState(), holder);
+        }).toArray(BlockInfo[]::new);
+    }
+
+    private static Function, BlockInfo[]> getCandidates(String key, IBlockState... allowedStates) {
+        return map -> {
+            if (map.containsKey(key)) {
+                return new BlockInfo[] { new BlockInfo(allowedStates[MathHelper.clamp(GTUtility.parseInt(map.get(key)), 0, allowedStates.length - 1)]) };
+            }
+            return Arrays.stream(allowedStates).map(BlockInfo::new).toArray(BlockInfo[]::new);
+        };
     }
 
     public static TraceabilityPredicate abilities(MultiblockAbility... allowedAbilities) {
@@ -258,10 +287,14 @@ public static TraceabilityPredicate abilities(MultiblockAbility... allowedAbi
     }
 
     public static TraceabilityPredicate states(IBlockState... allowedStates) {
+        return states(null, allowedStates);
+    }
+
+    public static TraceabilityPredicate states(String key, IBlockState... allowedStates) {
         return new TraceabilityPredicate(
                 worldState -> ArrayUtils.contains(allowedStates, worldState.getBlockState()) ? null :
                         PatternError.PLACEHOLDER,
-                getCandidates(allowedStates));
+                getCandidates(key, allowedStates));
     }
 
     /**
@@ -281,10 +314,14 @@ public static TraceabilityPredicate frames(Material... frameMaterials) {
     }
 
     public static TraceabilityPredicate blocks(Block... block) {
+        return blocks(null, block);
+    }
+
+    public static TraceabilityPredicate blocks(String key, Block... block) {
         return new TraceabilityPredicate(
                 worldState -> ArrayUtils.contains(block, worldState.getBlockState().getBlock()) ? null :
                         PatternError.PLACEHOLDER,
-                getCandidates(Arrays.stream(block).map(Block::getDefaultState).toArray(IBlockState[]::new)));
+                getCandidates(key, Arrays.stream(block).map(Block::getDefaultState).toArray(IBlockState[]::new)));
     }
 
     public static TraceabilityPredicate air() {
@@ -556,28 +593,33 @@ public void invalidateStructure() {
 
     public void invalidateStructure(String name) {
         if (!getSubstructure(name).getPatternState().isFormed()) return;
-        // invalidate the main structure
-        if ("MAIN".equals(name)) {
-            this.multiblockParts.forEach(part -> part.removeFromMultiBlock(this));
-            this.multiblockAbilities.clear();
-            this.multiblockParts.clear();
-            structures.forEach((s, p) -> {
-                p.getPatternState().setFormed(false);
-                p.getPatternState().setFlipped(false);
-            });
-            writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString("null"));
-        } else {
-            getSubstructure(name).getPatternState().setFormed(false);
-            writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString(name).writeBoolean(false));
+        // i am sorry
+        Object[] added = { null };
+        List dummyList = new ArrayList<>() {
+            @Override
+            public boolean add(Object e) {
+                added[0] = e;
+                return true;
+            }
+        };
 
-            multiblockParts.removeIf(part -> {
-                if (name.equals(part.getSubstructureName())) {
-                    part.removeFromMultiBlock(this);
-                    return true;
+        multiblockParts.removeIf(part -> {
+            if (name.equals(part.getSubstructureName())) {
+                if (part instanceof IMultiblockAbilityPart) {
+                    //noinspection unchecked
+                    IMultiblockAbilityPart ability = (IMultiblockAbilityPart) part;
+                    added[0] = null;
+                    ability.registerAbilities(dummyList);
+                    if (added[0] != null) multiblockAbilities.get(ability.getAbility()).remove(added[0]);
                 }
-                return false;
-            });
-        }
+                part.removeFromMultiBlock(this);
+                return true;
+            }
+            return false;
+        });
+
+        getSubstructure(name).getPatternState().setFormed(false);
+        writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString(name).writeBoolean(false));
     }
 
     protected void invalidStructureCaches() {
@@ -669,15 +711,8 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
             this.upwardsFacing = EnumFacing.VALUES[buf.readByte()];
             scheduleRenderUpdate();
         } else if (dataId == STRUCTURE_FORMED) {
-            // todo rewrite this entire thing :skull:(including the server side code for multiblock abilities)
-            String name = buf.readString(65536);
-            if ("null".equals(name)) {
-                for (IBlockPattern pattern : structures.values()) {
-                    pattern.getPatternState().setFormed(false);
-                }
-            } else {
-                getSubstructure(name).getPatternState().setFormed(buf.readBoolean());
-            }
+            String name = buf.readString(Short.MAX_VALUE);
+            getSubstructure(name).getPatternState().setFormed(buf.readBoolean());
 
             if (!isStructureFormed("MAIN")) {
                 GregTechAPI.soundManager.stopTileSound(getPos());
@@ -842,7 +877,7 @@ public void autoBuild(EntityPlayer player, Map map, String subst
     public void autoBuild(EntityPlayer player, Map map,
                           Long2ObjectMap predicates) {
         if (getWorld().isRemote) throw new IllegalArgumentException("client side call wuh");
-
+        modifyAutoBuild(map);
         // for each symbol, which simple predicate is being used
         // this advances whenever a minimum has been satisfied(if any), or a maximum has been reached(if any)
         // preview counts are treated as exactly that many
@@ -858,36 +893,34 @@ public void autoBuild(EntityPlayer player, Map map,
 
             if (info.getTileEntity() instanceof MetaTileEntityHolder holder) {
                 ItemStack removed = hasAndRemoveItem(player, holder.getMetaTileEntity().getStackForm());
-                if (!removed.isEmpty()) {
-                    MetaTileEntityHolder newHolder = new MetaTileEntityHolder();
-                    newHolder.setMetaTileEntity(holder.getMetaTileEntity());
-                    newHolder.getMetaTileEntity().onPlacement();
-                    if (removed.hasTagCompound())
-                        newHolder.getMetaTileEntity().initFromItemStackData(removed.getTagCompound());
-
-                    if (predicates.containsKey(pos.offset(newHolder.getMetaTileEntity().getFrontFacing()).toLong())) {
-                        EnumFacing valid = null;
-                        for (EnumFacing facing : EnumFacing.HORIZONTALS) {
-                            if (!predicates.containsKey(pos.offset(facing).toLong())) {
-                                valid = facing;
-                                break;
-                            }
+                if (removed.isEmpty()) return false;
+
+                MetaTileEntityHolder newHolder = new MetaTileEntityHolder();
+                newHolder.setMetaTileEntity(holder.getMetaTileEntity());
+                newHolder.getMetaTileEntity().onPlacement();
+                if (removed.hasTagCompound())
+                    newHolder.getMetaTileEntity().initFromItemStackData(removed.getTagCompound());
+
+                if (predicates.containsKey(pos.offset(newHolder.getMetaTileEntity().getFrontFacing()).toLong())) {
+                    EnumFacing valid = null;
+                    for (EnumFacing facing : EnumFacing.HORIZONTALS) {
+                        if (!predicates.containsKey(pos.offset(facing).toLong())) {
+                            valid = facing;
+                            break;
                         }
-                        if (valid != null) newHolder.getMetaTileEntity().setFrontFacing(valid);
-                        else {
-                            if (!predicates.containsKey(pos.offset(EnumFacing.UP).toLong())) {
-                                newHolder.getMetaTileEntity().setFrontFacing(EnumFacing.UP);
-                            } else if (!predicates.containsKey(pos.offset(EnumFacing.DOWN).toLong())) {
-                                newHolder.getMetaTileEntity().setFrontFacing(EnumFacing.DOWN);
-                            }
+                    }
+                    if (valid != null) newHolder.getMetaTileEntity().setFrontFacing(valid);
+                    else {
+                        if (!predicates.containsKey(pos.offset(EnumFacing.UP).toLong())) {
+                            newHolder.getMetaTileEntity().setFrontFacing(EnumFacing.UP);
+                        } else if (!predicates.containsKey(pos.offset(EnumFacing.DOWN).toLong())) {
+                            newHolder.getMetaTileEntity().setFrontFacing(EnumFacing.DOWN);
                         }
                     }
+                }
 
-                    getWorld().setBlockState(pos, holder.getMetaTileEntity().getBlock().getDefaultState());
-                    getWorld().setTileEntity(pos, newHolder);
-
-                    // todo add relative facing fix to make hatches face air
-                } else return false;
+                getWorld().setBlockState(pos, holder.getMetaTileEntity().getBlock().getDefaultState());
+                getWorld().setTileEntity(pos, newHolder);
             } else {
                 if (!hasAndRemoveItem(player, GTUtility.toItem(info.getBlockState())).isEmpty())
                     getWorld().setBlockState(pos, info.getBlockState());
@@ -924,7 +957,7 @@ public void autoBuild(EntityPlayer player, Map map,
             if (simple.candidates == null) continue;
 
             TraceabilityPredicate.SimplePredicate finalSimple = simple;
-            cache.computeIfAbsent(simple, k -> finalSimple.candidates.get()[0]);
+            cache.computeIfAbsent(simple, k -> finalSimple.candidates.apply(map)[0]);
 
             if (!place.test(entry.getLongKey(), cache.get(simple))) return;
 
@@ -956,12 +989,18 @@ public void autoBuild(EntityPlayer player, Map map,
             if (simple.candidates == null) continue;
 
             TraceabilityPredicate.SimplePredicate finalSimple = simple;
-            cache.computeIfAbsent(simple, k -> finalSimple.candidates.get()[0]);
+            cache.computeIfAbsent(simple, k -> finalSimple.candidates.apply(map)[0]);
 
             if (!place.test(entry.getLongKey(), cache.get(simple))) return;
         }
     }
 
+    /**
+     * Called right before the autobuild code starts, modify the map like if you want it to be "height"
+     * instead of "multi.1.0"
+     */
+    protected void modifyAutoBuild(Map map) {}
+
     /**
      * @return The item stack that is removed from the player's inventory(or AE system, satchels, etc).
      *         If the player is in creative mode, return a copy of the input stack.
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index 98aa11f7754..0f24c13ff6a 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -1,11 +1,15 @@
 package gregtech.api.pattern;
 
+import com.google.common.collect.Iterators;
+
 import gregtech.api.GregTechAPI;
 import gregtech.api.metatileentity.MetaTileEntity;
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.util.BlockInfo;
 
+import gregtech.api.util.GTUtility;
+
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.resources.I18n;
 import net.minecraft.init.Blocks;
@@ -36,12 +40,12 @@ public class TraceabilityPredicate {
     public static Supplier HEATING_COILS = () -> new TraceabilityPredicate(
             worldState -> GregTechAPI.HEATING_COILS.containsKey(worldState.getBlockState()) ? null :
                     PatternError.PLACEHOLDER,
-            () -> GregTechAPI.HEATING_COILS.entrySet().stream()
+            map -> GregTechAPI.HEATING_COILS.entrySet().stream()
+                    .filter(e -> e.getValue().getTier() == GTUtility.parseInt(map.get("coilTier"), 1))
                     // sort to make autogenerated jei previews not pick random coils each game load
                     .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
                     .map(entry -> new BlockInfo(entry.getKey(), null))
-                    .toArray(BlockInfo[]::new),
-            null)
+                    .toArray(BlockInfo[]::new))
                     .addTooltips("gregtech.multiblock.pattern.error.coils");
 
     public final List simple = new ArrayList<>();
@@ -59,17 +63,12 @@ public TraceabilityPredicate(TraceabilityPredicate predicate) {
     }
 
     public TraceabilityPredicate(Function predicate,
-                                 Supplier candidates,
-                                 Function, BlockInfo> buildFunction) {
-        simple.add(new SimplePredicate(predicate, candidates, buildFunction));
-    }
-
-    public TraceabilityPredicate(Function predicate, Supplier candidates) {
-        this(predicate, candidates, null);
+                                 Function, BlockInfo[]> candidates) {
+        simple.add(new SimplePredicate(predicate, candidates));
     }
 
     public TraceabilityPredicate(Function predicate) {
-        this(predicate, null, null);
+        this(predicate, null);
     }
 
     /**
@@ -223,9 +222,8 @@ public TraceabilityPredicate or(TraceabilityPredicate other) {
 
     public static class SimplePredicate {
 
-        public final Supplier candidates;
+        public final Function, BlockInfo[]> candidates;
         public final Function predicate;
-        public final Function, BlockInfo> buildFunction;
 
         @SideOnly(Side.CLIENT)
         private List toolTips;
@@ -238,11 +236,9 @@ public static class SimplePredicate {
         public int previewCount = -1;
 
         public SimplePredicate(Function predicate,
-                               Supplier candidates,
-                               @Nullable Function, BlockInfo> buildFunction) {
+                               Function, BlockInfo[]> candidates) {
             this.predicate = predicate;
             this.candidates = candidates;
-            this.buildFunction = buildFunction;
         }
 
         @SideOnly(Side.CLIENT)
@@ -316,7 +312,7 @@ public PatternError testLayer(BlockWorldState worldState, Object2IntMap getCandidates() {
-            return candidates == null ? Collections.emptyList() : Arrays.stream(this.candidates.get())
+            return candidates == null ? Collections.emptyList() : Arrays.stream(this.candidates.apply(Collections.emptyMap()))
                     .filter(info -> info.getBlockState().getBlock() != Blocks.AIR).map(info -> {
                         IBlockState blockState = info.getBlockState();
                         MetaTileEntity metaTileEntity = info.getTileEntity() instanceof IGregTechTileEntity ?
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 0f2175280a9..d6e52ec5b0c 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -212,6 +212,7 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
      */
     public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing, int aisleIndex,
                               int aisleOffset, boolean flip) {
+        // todo use a temporary cache to not double count sometimes
         // absolute facings from the relative facings
         EnumFacing absoluteAisle = directions[0].getRelativeFacing(frontFacing, upFacing, flip);
         EnumFacing absoluteString = directions[1].getRelativeFacing(frontFacing, upFacing, flip);
diff --git a/src/main/java/gregtech/api/util/GTUtility.java b/src/main/java/gregtech/api/util/GTUtility.java
index 88fc08016df..9b74b325009 100644
--- a/src/main/java/gregtech/api/util/GTUtility.java
+++ b/src/main/java/gregtech/api/util/GTUtility.java
@@ -978,13 +978,17 @@ public static int safeCastLongToInt(long v) {
     }
 
     /**
-     * Parse the string as an int and return, or return -1 if the string is not an int.
+     * Parse the string as an int and return, or return def if the string is not an int.
      */
-    public static int parseInt(String s) {
+    public static int parseInt(String s, int def) {
         try {
             return Integer.parseInt(s);
         } catch (NumberFormatException e) {
-            return -1;
+            return def;
         }
     }
+
+    public static int parseInt(String s) {
+        return parseInt(s, -1);
+    }
 }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index 69353046201..fafd7d07c17 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -3,6 +3,7 @@
 import gregtech.api.GTValues;
 import gregtech.api.GregTechAPI;
 import gregtech.api.block.ICleanroomFilter;
+import gregtech.api.block.IHeatingCoilBlockStats;
 import gregtech.api.capability.GregtechDataCodes;
 import gregtech.api.capability.GregtechTileCapabilities;
 import gregtech.api.capability.IEnergyContainer;
@@ -80,6 +81,7 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -87,6 +89,7 @@
 import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -219,7 +222,7 @@ public boolean allowsFlip() {
     @NotNull
     @Override
     protected IBlockPattern createStructurePattern() {
-        TraceabilityPredicate wallPredicate = states(getCasingState(), getGlassState());
+        TraceabilityPredicate wallPredicate = states("cleanroomGlass", getCasingState(), getGlassState());
         TraceabilityPredicate energyPredicate = autoAbilities().or(abilities(MultiblockAbility.INPUT_ENERGY)
                 .setMinGlobalLimited(1).setMaxGlobalLimited(3));
 
@@ -278,10 +281,11 @@ protected TraceabilityPredicate filterPredicate() {
         return new TraceabilityPredicate(
                 worldState -> GregTechAPI.CLEANROOM_FILTERS.containsKey(worldState.getBlockState()) ? null :
                         PatternError.PLACEHOLDER,
-                () -> GregTechAPI.CLEANROOM_FILTERS.entrySet().stream()
-                        .filter(entry -> entry.getValue().getCleanroomType() != null)
-                        .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
-                        .map(entry -> new BlockInfo(entry.getKey(), null))
+                map -> GregTechAPI.CLEANROOM_FILTERS.entrySet().stream()
+                        .filter(e -> e.getValue().getCleanroomType() != null)
+                        .filter(e -> e.getValue().getCleanroomType() == CleanroomType.getByName(map.get("cleanroomType")))
+                        .sorted(Comparator.comparingInt(e -> e.getValue().getTier()))
+                        .map(e -> new BlockInfo(e.getKey(), null))
                         .toArray(BlockInfo[]::new))
                                 .addTooltips("gregtech.multiblock.pattern.error.filters");
     }
@@ -458,6 +462,16 @@ protected static int getFactor(String str) {
         return "+".equals(str) ? 1 : -1;
     }
 
+    @NotNull
+    @Override
+    public Iterator> getPreviewBuilds() {
+        return GregTechAPI.CLEANROOM_FILTERS.values().stream()
+                .filter(i -> i.getCleanroomType() != null)
+                .sorted(Comparator.comparingInt(ICleanroomFilter::getTier))
+                .map(i -> Collections.singletonMap("cleanroomType", i.getCleanroomType().getName()))
+                .iterator();
+    }
+
     @Override
     protected void addWarningText(List textList) {
         MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
@@ -705,42 +719,6 @@ public void getSubItems(CreativeTabs creativeTab, NonNullList subItem
         }
     }
 
-    @Override
-    public List getMatchingShapes() {
-        ArrayList shapeInfo = new ArrayList<>();
-        MultiblockShapeInfo.Builder builder = MultiblockShapeInfo.builder()
-                .aisle("XXXXX", "XIHLX", "XXDXX", "XXXXX", "XXXXX")
-                .aisle("XXXXX", "X   X", "G   G", "X   X", "XFFFX")
-                .aisle("XXXXX", "X   X", "G   G", "X   X", "XFSFX")
-                .aisle("XXXXX", "X   X", "G   G", "X   X", "XFFFX")
-                .aisle("XMXEX", "XXOXX", "XXRXX", "XXXXX", "XXXXX")
-                .where('X', MetaBlocks.CLEANROOM_CASING.getState(BlockCleanroomCasing.CasingType.PLASCRETE))
-                .where('G', MetaBlocks.TRANSPARENT_CASING.getState(BlockGlassCasing.CasingType.CLEANROOM_GLASS))
-                .where('S', MetaTileEntities.CLEANROOM, EnumFacing.SOUTH)
-                .where(' ', Blocks.AIR.getDefaultState())
-                .where('E', MetaTileEntities.ENERGY_INPUT_HATCH[GTValues.LV], EnumFacing.SOUTH)
-                .where('I', MetaTileEntities.PASSTHROUGH_HATCH_ITEM, EnumFacing.NORTH)
-                .where('L', MetaTileEntities.PASSTHROUGH_HATCH_FLUID, EnumFacing.NORTH)
-                .where('H', MetaTileEntities.HULL[GTValues.HV], EnumFacing.NORTH)
-                .where('D', MetaTileEntities.DIODES[GTValues.HV], EnumFacing.NORTH)
-                .where('M',
-                        () -> ConfigHolder.machines.enableMaintenance ? MetaTileEntities.MAINTENANCE_HATCH :
-                                MetaBlocks.CLEANROOM_CASING.getState(BlockCleanroomCasing.CasingType.PLASCRETE),
-                        EnumFacing.SOUTH)
-                .where('O',
-                        Blocks.IRON_DOOR.getDefaultState().withProperty(BlockDoor.FACING, EnumFacing.NORTH)
-                                .withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.LOWER))
-                .where('R', Blocks.IRON_DOOR.getDefaultState().withProperty(BlockDoor.FACING, EnumFacing.NORTH)
-                        .withProperty(BlockDoor.HALF, BlockDoor.EnumDoorHalf.UPPER));
-
-        GregTechAPI.CLEANROOM_FILTERS.entrySet().stream()
-                .filter(entry -> entry.getValue().getCleanroomType() != null)
-                .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
-                .forEach(entry -> shapeInfo.add(builder.shallowCopy().where('F', entry.getKey()).build()));
-
-        return shapeInfo;
-    }
-
     @Override
     protected boolean shouldShowVoidingModeButton() {
         return false;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index 1bfb924c941..d2d82693d36 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -47,7 +47,12 @@
 import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IntSummaryStatistics;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.IntStream;
 
 import static gregtech.api.util.RelativeDirection.*;
 
@@ -130,6 +135,16 @@ public boolean checkRecipe(@NotNull Recipe recipe, boolean consumeIfSuccess) {
                 .build();
     }
 
+    @NotNull
+    @Override
+    public Iterator> getPreviewBuilds() {
+        return GregTechAPI.HEATING_COILS.values().stream()
+                .mapToInt(IHeatingCoilBlockStats::getTier)
+                .sorted()
+                .mapToObj(i -> Collections.singletonMap("coilTier", Integer.toString(i)))
+                .iterator();
+    }
+
     protected IBlockState getCasingState() {
         return MetaBlocks.METAL_CASING.getState(MetalCasingType.INVAR_HEATPROOF);
     }
@@ -176,32 +191,6 @@ public SoundEvent getBreakdownSound() {
         return GTSoundEvents.BREAKDOWN_ELECTRICAL;
     }
 
-    @Override
-    public List getMatchingShapes() {
-        List shapeInfo = new ArrayList<>();
-        MultiblockShapeInfo.Builder builder = MultiblockShapeInfo.builder()
-                .aisle("EEM", "CCC", "CCC", "XXX")
-                .aisle("FXD", "C#C", "C#C", "XHX")
-                .aisle("ISO", "CCC", "CCC", "XXX")
-                .where('X', MetaBlocks.METAL_CASING.getState(MetalCasingType.INVAR_HEATPROOF))
-                .where('S', MetaTileEntities.ELECTRIC_BLAST_FURNACE, EnumFacing.SOUTH)
-                .where('#', Blocks.AIR.getDefaultState())
-                .where('E', MetaTileEntities.ENERGY_INPUT_HATCH[GTValues.LV], EnumFacing.NORTH)
-                .where('I', MetaTileEntities.ITEM_IMPORT_BUS[GTValues.LV], EnumFacing.SOUTH)
-                .where('O', MetaTileEntities.ITEM_EXPORT_BUS[GTValues.LV], EnumFacing.SOUTH)
-                .where('F', MetaTileEntities.FLUID_IMPORT_HATCH[GTValues.LV], EnumFacing.WEST)
-                .where('D', MetaTileEntities.FLUID_EXPORT_HATCH[GTValues.LV], EnumFacing.EAST)
-                .where('H', MetaTileEntities.MUFFLER_HATCH[GTValues.LV], EnumFacing.UP)
-                .where('M', () -> ConfigHolder.machines.enableMaintenance ? MetaTileEntities.MAINTENANCE_HATCH :
-                        MetaBlocks.METAL_CASING.getState(MetalCasingType.INVAR_HEATPROOF), EnumFacing.NORTH)
-                .dot('C', 1, "gregtech.material.tungsten_carbide");
-        shapeInfo.add(builder.build());
-        // GregTechAPI.HEATING_COILS.entrySet().stream()
-        // .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
-        // .forEach(entry -> shapeInfo.add(builder.shallowCopy().where('C', entry.getKey()).build()));
-        return shapeInfo;
-    }
-
     @NotNull
     @Override
     public List getDataInfo() {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
index 54a68b95ee9..77d5832d276 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
@@ -1,6 +1,8 @@
 package gregtech.common.metatileentities.multi.electric;
 
 import gregtech.api.GTValues;
+import gregtech.api.GregTechAPI;
+import gregtech.api.block.IHeatingCoilBlockStats;
 import gregtech.api.capability.GregtechDataCodes;
 import gregtech.api.capability.IEnergyContainer;
 import gregtech.api.capability.impl.EnergyContainerHandler;
@@ -80,8 +82,12 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.function.DoubleSupplier;
+import java.util.stream.IntStream;
 
 import static gregtech.api.recipes.logic.OverclockingLogic.PERFECT_HALF_DURATION_FACTOR;
 import static gregtech.api.recipes.logic.OverclockingLogic.PERFECT_HALF_VOLTAGE_FACTOR;
@@ -141,13 +147,11 @@ protected BlockPattern createStructurePattern() {
                 .aisle("######ICI######", "####GGAAAGG####", "######ICI######")
                 .aisle("###############", "######OGO######", "###############")
                 .where('S', selfPredicate())
-                .where('G', states(getCasingState(), getGlassState()))
+                .where('G', states("fusionGlass", getCasingState(), getGlassState()))
                 .where('E',
-                        states(getCasingState(), getGlassState()).or(metaTileEntities(Arrays
-                                .stream(MetaTileEntities.ENERGY_INPUT_HATCH)
-                                .filter(mte -> mte != null && tier <= mte.getTier() && mte.getTier() <= GTValues.UV)
-                                .toArray(MetaTileEntity[]::new))
-                                        .setMinGlobalLimited(1).setPreviewCount(16)))
+                        states("fusionGlass", getCasingState(), getGlassState())
+                                .or(tieredMTEs((map, mte) -> tier <= mte.getTier() && mte.getTier() <= GTValues.UV, MetaTileEntities.ENERGY_INPUT_HATCH)
+                                .setMinGlobalLimited(1).setPreviewCount(16)))
                 .where('C', states(getCasingState()))
                 .where('K', states(getCoilState()))
                 .where('O', states(getCasingState(), getGlassState()).or(abilities(MultiblockAbility.EXPORT_FLUIDS)))
@@ -158,48 +162,12 @@ protected BlockPattern createStructurePattern() {
                 .build();
     }
 
+    @NotNull
     @Override
-    public List getMatchingShapes() {
-        List shapeInfos = new ArrayList<>();
-
-        MultiblockShapeInfo.Builder baseBuilder = MultiblockShapeInfo.builder()
-                .aisle("###############", "######WGW######", "###############")
-                .aisle("######DCD######", "####GG###GG####", "######UCU######")
-                .aisle("####CC###CC####", "###w##EGE##s###", "####CC###CC####")
-                .aisle("###C#######C###", "##nKeG###GeKn##", "###C#######C###")
-                .aisle("##C#########C##", "#G#s#######w#G#", "##C#########C##")
-                .aisle("##C#########C##", "#G#G#######G#G#", "##C#########C##")
-                .aisle("#D###########D#", "N#S#########N#S", "#U###########U#")
-                .aisle("#C###########C#", "G#G#########G#G", "#C###########C#")
-                .aisle("#D###########D#", "N#S#########N#S", "#U###########U#")
-                .aisle("##C#########C##", "#G#G#######G#G#", "##C#########C##")
-                .aisle("##C#########C##", "#G#s#######w#G#", "##C#########C##")
-                .aisle("###C#######C###", "##eKnG###GnKe##", "###C#######C###")
-                .aisle("####CC###CC####", "###w##WGW##s###", "####CC###CC####")
-                .aisle("######DCD######", "####GG###GG####", "######UCU######")
-                .aisle("###############", "######EME######", "###############")
-                .where('M', MetaTileEntities.FUSION_REACTOR[tier - GTValues.LuV], EnumFacing.SOUTH)
-                .where('C', getCasingState())
-                .where('G', MetaBlocks.TRANSPARENT_CASING.getState(
-                        BlockGlassCasing.CasingType.FUSION_GLASS))
-                .where('K', getCoilState())
-                .where('W', MetaTileEntities.FLUID_EXPORT_HATCH[tier], EnumFacing.NORTH)
-                .where('E', MetaTileEntities.FLUID_EXPORT_HATCH[tier], EnumFacing.SOUTH)
-                .where('S', MetaTileEntities.FLUID_EXPORT_HATCH[tier], EnumFacing.EAST)
-                .where('N', MetaTileEntities.FLUID_EXPORT_HATCH[tier], EnumFacing.WEST)
-                .where('w', MetaTileEntities.ENERGY_INPUT_HATCH[tier], EnumFacing.WEST)
-                .where('e', MetaTileEntities.ENERGY_INPUT_HATCH[tier], EnumFacing.SOUTH)
-                .where('s', MetaTileEntities.ENERGY_INPUT_HATCH[tier], EnumFacing.EAST)
-                .where('n', MetaTileEntities.ENERGY_INPUT_HATCH[tier], EnumFacing.NORTH)
-                .where('U', MetaTileEntities.FLUID_IMPORT_HATCH[tier], EnumFacing.UP)
-                .where('D', MetaTileEntities.FLUID_IMPORT_HATCH[tier], EnumFacing.DOWN)
-                .where('#', Blocks.AIR.getDefaultState());
-
-        shapeInfos.add(baseBuilder.shallowCopy()
-                .where('G', getCasingState())
-                .build());
-        shapeInfos.add(baseBuilder.build());
-        return shapeInfos;
+    public Iterator> getPreviewBuilds() {
+        return IntStream.of(0, 1)
+                .mapToObj(i -> Collections.singletonMap("fusionGlass", Integer.toString(i)))
+                .iterator();
     }
 
     @SideOnly(Side.CLIENT)
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index eefde52c9fa..87746bfc024 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -2,6 +2,7 @@
 
 import gregtech.api.GTValues;
 import gregtech.api.GregTechAPI;
+import gregtech.api.block.IHeatingCoilBlockStats;
 import gregtech.api.capability.GregtechDataCodes;
 import gregtech.api.capability.GregtechTileCapabilities;
 import gregtech.api.capability.IControllable;
@@ -14,6 +15,7 @@
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.util.BlockInfo;
+import gregtech.api.util.GTUtility;
 import gregtech.api.util.TextComponentUtil;
 import gregtech.api.util.TextFormattingUtil;
 import gregtech.client.renderer.ICubeRenderer;
@@ -52,6 +54,7 @@
 import java.time.Duration;
 import java.util.*;
 import java.util.function.Supplier;
+import java.util.stream.IntStream;
 
 import static gregtech.api.util.RelativeDirection.*;
 
@@ -249,35 +252,15 @@ protected BlockPattern createStructurePattern() {
                 .build();
     }
 
+    @NotNull
     @Override
-    public List getMatchingShapes() {
-        List shapeInfo = new ArrayList<>();
-        MultiblockShapeInfo.Builder builder = MultiblockShapeInfo.builder()
-                .aisle("CCCCC", "CCCCC", "GGGGG", "GGGGG", "GGGGG")
-                .aisle("CCCCC", "CCCCC", "GBBBG", "GBBBG", "GGGGG")
-                .aisle("CCCCC", "CCCCC", "GBBBG", "GBBBG", "GGGGG")
-                .aisle("CCCCC", "CCCCC", "GBBBG", "GBBBG", "GGGGG")
-                .aisle("ICSCO", "NCMCT", "GGGGG", "GGGGG", "GGGGG")
-                .where('S', MetaTileEntities.POWER_SUBSTATION, EnumFacing.SOUTH)
-                .where('C', getCasingState())
-                .where('G', getGlassState())
-                .where('I', MetaTileEntities.ENERGY_INPUT_HATCH[GTValues.HV], EnumFacing.SOUTH)
-                .where('N', MetaTileEntities.SUBSTATION_ENERGY_INPUT_HATCH[0], EnumFacing.SOUTH)
-                .where('O', MetaTileEntities.ENERGY_OUTPUT_HATCH[GTValues.HV], EnumFacing.SOUTH)
-                .where('T', MetaTileEntities.SUBSTATION_ENERGY_OUTPUT_HATCH[0], EnumFacing.SOUTH)
-                .where('M',
-                        () -> ConfigHolder.machines.enableMaintenance ? MetaTileEntities.MAINTENANCE_HATCH :
-                                MetaBlocks.METAL_CASING.getState(MetalCasingType.PALLADIUM_SUBSTATION),
-                        EnumFacing.SOUTH);
-
-        GregTechAPI.PSS_BATTERIES.entrySet().stream()
-                // filter out empty batteries in example structures, though they are still
-                // allowed in the predicate (so you can see them on right-click)
-                .filter(entry -> entry.getValue().getCapacity() > 0)
-                .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
-                .forEach(entry -> shapeInfo.add(builder.shallowCopy().where('B', entry.getKey()).build()));
-
-        return shapeInfo;
+    public Iterator> getPreviewBuilds() {
+        return GregTechAPI.PSS_BATTERIES.values().stream()
+                .mapToInt(IBatteryData::getTier)
+                .filter(i -> i != -1)
+                .sorted()
+                .mapToObj(i -> Collections.singletonMap("batteryTier", Integer.toString(i)))
+                .iterator();
     }
 
     protected IBlockState getCasingState() {
@@ -291,7 +274,8 @@ protected IBlockState getGlassState() {
     protected static final Supplier BATTERY_PREDICATE = () -> new TraceabilityPredicate(
             worldState -> GregTechAPI.PSS_BATTERIES.containsKey(worldState.getBlockState()) ? null :
                     PatternError.PLACEHOLDER,
-            () -> GregTechAPI.PSS_BATTERIES.entrySet().stream()
+            map -> GregTechAPI.PSS_BATTERIES.entrySet().stream()
+                    .filter(e -> e.getValue().getTier() == GTUtility.parseInt(map.get("batteryTier"), GTValues.EV))
                     .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
                     .map(entry -> new BlockInfo(entry.getKey(), null))
                     .toArray(BlockInfo[]::new))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
index 1c951296e28..5cbf781795b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
@@ -14,6 +14,7 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
 import gregtech.api.pattern.MultiblockShapeInfo;
+import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;

From 99c6dcdb6a9b251cae9f03b74e3c40b334dfd15b Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Wed, 25 Dec 2024 21:09:09 -0800
Subject: [PATCH 53/64] spotless and stuff

---
 .../multiblock/MultiblockControllerBase.java  | 53 ++++++++++++-------
 .../api/pattern/TraceabilityPredicate.java    | 46 ++++++----------
 .../api/pattern/pattern/BlockPattern.java     | 18 ++++---
 .../pattern/pattern/ExpandablePattern.java    |  7 ++-
 .../java/gregtech/api/util/BlockInfo.java     | 14 +++++
 .../behaviors/MultiblockBuilderBehavior.java  |  5 +-
 .../electric/MetaTileEntityCleanroom.java     | 12 ++---
 .../MetaTileEntityElectricBlastFurnace.java   |  8 ---
 .../electric/MetaTileEntityFusionReactor.java | 11 ++--
 .../multi/electric/MetaTileEntityHPCA.java    |  7 ++-
 .../MetaTileEntityPowerSubstation.java        |  7 +--
 .../MetaTileEntityResearchStation.java        |  5 +-
 .../MetaTileEntityCharcoalPileIgniter.java    | 47 +++++-----------
 .../MultiblockInfoRecipeWrapper.java          |  2 +-
 .../gregtech/api/util/TierByVoltageTest.java  |  4 +-
 15 files changed, 108 insertions(+), 138 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index e3069d43a9a..fc88d3ce62a 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -234,58 +234,69 @@ public static TraceabilityPredicate metaTileEntities(MetaTileEntity... metaTileE
         ResourceLocation[] ids = Arrays.stream(metaTileEntities).filter(Objects::nonNull)
                 .map(tile -> tile.metaTileEntityId).toArray(ResourceLocation[]::new);
         return tilePredicate((state, tile) -> ArrayUtils.contains(ids, tile.metaTileEntityId),
-                getCandidates(metaTileEntities));
+                getCandidates(() -> EnumFacing.NORTH, metaTileEntities));
     }
 
     @SafeVarargs
-    public static  TraceabilityPredicate tieredMTEs(BiPredicate, T> pred, T... metaTileEntities) {
+    public static <
+            T extends MetaTileEntity & ITieredMetaTileEntity> TraceabilityPredicate tieredMTEs(BiPredicate, T> pred,
+                                                                                               T... metaTileEntities) {
         ResourceLocation[] ids = Arrays.stream(metaTileEntities).filter(Objects::nonNull)
                 .map(tile -> tile.metaTileEntityId).toArray(ResourceLocation[]::new);
         return tilePredicate((state, tile) -> ArrayUtils.contains(ids, tile.metaTileEntityId),
                 getCandidates(pred, metaTileEntities));
     }
 
-    private static Function, BlockInfo[]> getCandidates(MetaTileEntity... metaTileEntities) {
+    public static Function, BlockInfo[]> getCandidates(Supplier facing,
+                                                                           MetaTileEntity... metaTileEntities) {
         return map -> Arrays.stream(metaTileEntities).filter(Objects::nonNull).map(tile -> {
             MetaTileEntityHolder holder = new MetaTileEntityHolder();
             holder.setMetaTileEntity(tile);
             holder.getMetaTileEntity().onPlacement();
-            holder.getMetaTileEntity().setFrontFacing(EnumFacing.SOUTH);
+            holder.getMetaTileEntity().setFrontFacing(facing.get());
             return new BlockInfo(tile.getBlock().getDefaultState(), holder);
         }).toArray(BlockInfo[]::new);
     }
 
     // generic hell
     @SafeVarargs
-    public static  Function, BlockInfo[]> getCandidates(BiPredicate, T> pred, T... metaTileEntities) {
+    public static <
+            T extends MetaTileEntity & ITieredMetaTileEntity> Function, BlockInfo[]> getCandidates(BiPredicate, T> pred,
+                                                                                                                       T... metaTileEntities) {
         return map -> Arrays.stream(metaTileEntities).filter(Objects::nonNull)
-            .filter(i -> pred.test(map, i))
-            .map(tile -> {
-                MetaTileEntityHolder holder = new MetaTileEntityHolder();
-                holder.setMetaTileEntity(tile);
-                holder.getMetaTileEntity().onPlacement();
-                holder.getMetaTileEntity().setFrontFacing(EnumFacing.SOUTH);
-                return new BlockInfo(tile.getBlock().getDefaultState(), holder);
-        }).toArray(BlockInfo[]::new);
+                .filter(i -> pred.test(map, i))
+                .map(tile -> {
+                    MetaTileEntityHolder holder = new MetaTileEntityHolder();
+                    holder.setMetaTileEntity(tile);
+                    holder.getMetaTileEntity().onPlacement();
+                    holder.getMetaTileEntity().setFrontFacing(EnumFacing.SOUTH);
+                    return new BlockInfo(tile.getBlock().getDefaultState(), holder);
+                }).toArray(BlockInfo[]::new);
     }
 
-    private static Function, BlockInfo[]> getCandidates(String key, IBlockState... allowedStates) {
+    public static Function, BlockInfo[]> getCandidates(String key, IBlockState... allowedStates) {
         return map -> {
             if (map.containsKey(key)) {
-                return new BlockInfo[] { new BlockInfo(allowedStates[MathHelper.clamp(GTUtility.parseInt(map.get(key)), 0, allowedStates.length - 1)]) };
+                return new BlockInfo[] { new BlockInfo(allowedStates[MathHelper.clamp(GTUtility.parseInt(map.get(key)),
+                        0, allowedStates.length - 1)]) };
             }
             return Arrays.stream(allowedStates).map(BlockInfo::new).toArray(BlockInfo[]::new);
         };
     }
 
-    public static TraceabilityPredicate abilities(MultiblockAbility... allowedAbilities) {
+    public static TraceabilityPredicate abilities(Supplier facing,
+                                                  MultiblockAbility... allowedAbilities) {
         return tilePredicate((state, tile) -> tile instanceof IMultiblockAbilityPart &&
                 ArrayUtils.contains(allowedAbilities, ((IMultiblockAbilityPart) tile).getAbility()),
-                getCandidates(Arrays.stream(allowedAbilities)
+                getCandidates(facing, Arrays.stream(allowedAbilities)
                         .flatMap(ability -> MultiblockAbility.REGISTRY.get(ability).stream())
                         .toArray(MetaTileEntity[]::new)));
     }
 
+    public static TraceabilityPredicate abilities(MultiblockAbility... allowedAbilities) {
+        return abilities(() -> EnumFacing.NORTH, allowedAbilities);
+    }
+
     public static TraceabilityPredicate states(IBlockState... allowedStates) {
         return states(null, allowedStates);
     }
@@ -596,6 +607,7 @@ public void invalidateStructure(String name) {
         // i am sorry
         Object[] added = { null };
         List dummyList = new ArrayList<>() {
+
             @Override
             public boolean add(Object e) {
                 added[0] = e;
@@ -606,7 +618,7 @@ public boolean add(Object e) {
         multiblockParts.removeIf(part -> {
             if (name.equals(part.getSubstructureName())) {
                 if (part instanceof IMultiblockAbilityPart) {
-                    //noinspection unchecked
+                    // noinspection unchecked
                     IMultiblockAbilityPart ability = (IMultiblockAbilityPart) part;
                     added[0] = null;
                     ability.registerAbilities(dummyList);
@@ -871,7 +883,8 @@ public void autoBuild(EntityPlayer player, Map map, String subst
     }
 
     /**
-     * Autobuild the multiblock, this is like {@link MultiblockControllerBase#autoBuild(EntityPlayer, Map, String)} but if
+     * Autobuild the multiblock, this is like {@link MultiblockControllerBase#autoBuild(EntityPlayer, Map, String)} but
+     * if
      * you have the predicate map for other uses. This does mutate the map passed in.
      */
     public void autoBuild(EntityPlayer player, Map map,
@@ -898,6 +911,7 @@ public void autoBuild(EntityPlayer player, Map map,
                 MetaTileEntityHolder newHolder = new MetaTileEntityHolder();
                 newHolder.setMetaTileEntity(holder.getMetaTileEntity());
                 newHolder.getMetaTileEntity().onPlacement();
+                newHolder.getMetaTileEntity().setFrontFacing(holder.getMetaTileEntity().getFrontFacing());
                 if (removed.hasTagCompound())
                     newHolder.getMetaTileEntity().initFromItemStackData(removed.getTagCompound());
 
@@ -931,7 +945,6 @@ public void autoBuild(EntityPlayer player, Map map,
         };
 
         for (Long2ObjectMap.Entry entry : predicates.long2ObjectEntrySet()) {
-            // todo add autobuild key params here, also remove layer stuff from rest of the code elsewhere
             TraceabilityPredicate pred = entry.getValue();
             if (simpleIndex.getInt(pred) >= pred.simple.size()) continue;
 
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index 0f24c13ff6a..3c390bbc77c 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -1,26 +1,18 @@
 package gregtech.api.pattern;
 
-import com.google.common.collect.Iterators;
-
 import gregtech.api.GregTechAPI;
-import gregtech.api.metatileentity.MetaTileEntity;
-import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
 import gregtech.api.util.BlockInfo;
-
 import gregtech.api.util.GTUtility;
 
-import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.resources.I18n;
 import net.minecraft.init.Blocks;
-import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
 import net.minecraftforge.fml.common.FMLCommonHandler;
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
 
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
-import org.jetbrains.annotations.Nullable;
 
 import java.util.*;
 import java.util.function.Function;
@@ -41,12 +33,13 @@ public class TraceabilityPredicate {
             worldState -> GregTechAPI.HEATING_COILS.containsKey(worldState.getBlockState()) ? null :
                     PatternError.PLACEHOLDER,
             map -> GregTechAPI.HEATING_COILS.entrySet().stream()
-                    .filter(e -> e.getValue().getTier() == GTUtility.parseInt(map.get("coilTier"), 1))
+                    .filter(e -> !map.containsKey("coilTier") ||
+                            e.getValue().getTier() == GTUtility.parseInt(map.get("coilTier"), 1))
                     // sort to make autogenerated jei previews not pick random coils each game load
                     .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
                     .map(entry -> new BlockInfo(entry.getKey(), null))
                     .toArray(BlockInfo[]::new))
-                    .addTooltips("gregtech.multiblock.pattern.error.coils");
+                            .addTooltips("gregtech.multiblock.pattern.error.coils");
 
     public final List simple = new ArrayList<>();
     protected boolean isCenter;
@@ -283,18 +276,19 @@ public PatternError testRaw(BlockWorldState worldState) {
         public PatternError testLimited(BlockWorldState worldState,
                                         Object2IntMap globalCache,
                                         Object2IntMap layerCache) {
-            PatternError error = testGlobal(worldState, globalCache);
+            PatternError error = testGlobal(worldState, globalCache, layerCache);
             if (error != null) return error;
             return testLayer(worldState, layerCache);
         }
 
-        public PatternError testGlobal(BlockWorldState worldState, Object2IntMap cache) {
+        public PatternError testGlobal(BlockWorldState worldState, Object2IntMap global,
+                                       Object2IntMap layer) {
             PatternError result = predicate.apply(worldState);
 
-            if (cache != null && !cache.containsKey(this)) cache.put(this, 0);
-            if ((minGlobalCount == -1 && maxGlobalCount == -1) || cache == null || result != null) return result;
+            if (!global.containsKey(this)) global.put(this, 0);
+            if ((minGlobalCount == -1 && maxGlobalCount == -1) || result != null || layer == null) return result;
 
-            int count = cache.put(this, cache.getInt(this) + 1) + 1;
+            int count = layer.put(this, layer.getInt(this) + 1) + 1 + global.getInt(this);
             if (maxGlobalCount == -1 || count <= maxGlobalCount) return null;
 
             return new SinglePredicateError(this, 0);
@@ -303,27 +297,19 @@ public PatternError testGlobal(BlockWorldState worldState, Object2IntMap cache) {
             PatternError result = predicate.apply(worldState);
 
-            if ((minLayerCount == -1 && maxLayerCount == -1) || cache == null || result != null) return result;
+            if ((minLayerCount == -1 && maxLayerCount == -1) || result != null) return result;
 
-            int count = cache.put(this, cache.getInt(this) + 1) + 1;
-            if (maxLayerCount == -1 || count <= maxLayerCount) return null;
+            if (maxLayerCount == -1 || cache.getInt(this) <= maxLayerCount) return null;
 
             return new SinglePredicateError(this, 2);
         }
 
         public List getCandidates() {
-            return candidates == null ? Collections.emptyList() : Arrays.stream(this.candidates.apply(Collections.emptyMap()))
-                    .filter(info -> info.getBlockState().getBlock() != Blocks.AIR).map(info -> {
-                        IBlockState blockState = info.getBlockState();
-                        MetaTileEntity metaTileEntity = info.getTileEntity() instanceof IGregTechTileEntity ?
-                                ((IGregTechTileEntity) info.getTileEntity()).getMetaTileEntity() : null;
-                        if (metaTileEntity != null) {
-                            return metaTileEntity.getStackForm();
-                        } else {
-                            return new ItemStack(Item.getItemFromBlock(blockState.getBlock()), 1,
-                                    blockState.getBlock().damageDropped(blockState));
-                        }
-                    }).collect(Collectors.toList());
+            return candidates == null ? Collections.emptyList() :
+                    Arrays.stream(this.candidates.apply(Collections.emptyMap()))
+                            .filter(info -> info.getBlockState().getBlock() != Blocks.AIR)
+                            .map(BlockInfo::toItem)
+                            .collect(Collectors.toList());
         }
     }
 
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index d6e52ec5b0c..351d435b3c0 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -8,7 +8,6 @@
 import gregtech.api.pattern.PatternError;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.util.BlockInfo;
-import gregtech.api.util.GTLog;
 import gregtech.api.util.RelativeDirection;
 
 import net.minecraft.block.state.IBlockState;
@@ -254,15 +253,20 @@ public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing,
             // offset the string start once after every string
             stringStart.offset(absoluteString);
             charPos.from(stringStart);
+        }
 
-            // layer minimum checks
-            for (Object2IntMap.Entry entry : layerCount.object2IntEntrySet()) {
-                if (entry.getIntValue() < entry.getKey().minLayerCount) {
-                    state.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 3));
-                    return false;
-                }
+        // layer minimum checks
+        for (Object2IntMap.Entry entry : layerCount.object2IntEntrySet()) {
+            if (entry.getIntValue() < entry.getKey().minLayerCount) {
+                state.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 3));
+                return false;
             }
         }
+
+        for (Object2IntMap.Entry entry : layerCount.object2IntEntrySet()) {
+            globalCount.put(entry.getKey(), globalCount.getInt(entry.getKey()) + entry.getIntValue());
+        }
+
         return true;
     }
 
diff --git a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
index 3c13457d77e..e20bb02046a 100644
--- a/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java
@@ -11,8 +11,6 @@
 import gregtech.api.util.RelativeDirection;
 import gregtech.api.util.function.QuadFunction;
 
-import it.unimi.dsi.fastutil.longs.Long2ObjectRBTreeMap;
-
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.tileentity.TileEntity;
 import net.minecraft.util.EnumFacing;
@@ -21,6 +19,7 @@
 
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectRBTreeMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMaps;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
@@ -156,7 +155,6 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
             // this basically reshuffles the coordinates into absolute form from relative form
             pos.zero().offset(absolutes[0], arr[0]).offset(absolutes[1], arr[1]).offset(absolutes[2], arr[2]);
             // translate from the origin to the center
-            // set the pos with world coordinates
             worldState.setPos(pos.add(translation));
 
             if (predicate != TraceabilityPredicate.ANY) {
@@ -213,7 +211,8 @@ public Long2ObjectSortedMap getDefaultShape(MultiblockCon
             TraceabilityPredicate predicate = predicateFunction.apply(pos, bounds);
 
             int[] arr = pos.getAll();
-            pos.zero().offset(absolutes[0], arr[0]).offset(absolutes[1], arr[1]).offset(absolutes[2], arr[2]).add(translation);
+            pos.zero().offset(absolutes[0], arr[0]).offset(absolutes[1], arr[1]).offset(absolutes[2], arr[2])
+                    .add(translation);
 
             if (predicate != TraceabilityPredicate.ANY && predicate != TraceabilityPredicate.AIR) {
                 predicates.put(pos.toLong(), predicate);
diff --git a/src/main/java/gregtech/api/util/BlockInfo.java b/src/main/java/gregtech/api/util/BlockInfo.java
index 79f23df05b1..9b3a4bd730b 100644
--- a/src/main/java/gregtech/api/util/BlockInfo.java
+++ b/src/main/java/gregtech/api/util/BlockInfo.java
@@ -1,8 +1,12 @@
 package gregtech.api.util;
 
+import gregtech.api.metatileentity.MetaTileEntity;
+import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
+
 import net.minecraft.block.Block;
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.init.Blocks;
+import net.minecraft.item.ItemStack;
 import net.minecraft.tileentity.TileEntity;
 
 import com.google.common.base.Preconditions;
@@ -36,6 +40,16 @@ public BlockInfo(IBlockState blockState, TileEntity tileEntity) {
                 "Cannot create block info with tile entity for block not having it");
     }
 
+    public ItemStack toItem() {
+        MetaTileEntity metaTileEntity = tileEntity instanceof IGregTechTileEntity igtte ? igtte.getMetaTileEntity() :
+                null;
+        if (metaTileEntity != null) {
+            return metaTileEntity.getStackForm();
+        } else {
+            return GTUtility.toItem(blockState);
+        }
+    }
+
     public IBlockState getBlockState() {
         return blockState;
     }
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index 742fbeaafbd..3ec73cbb82c 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -9,10 +9,9 @@
 import gregtech.api.mui.GTGuis;
 import gregtech.api.mui.factory.MetaItemGuiFactory;
 import gregtech.api.pattern.PatternError;
-import gregtech.api.pattern.pattern.IBlockPattern;
 import gregtech.api.util.GTUtility;
+import gregtech.client.renderer.handler.BlockPosHighlightRenderer;
 
-import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.item.Item;
@@ -45,7 +44,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -195,6 +193,7 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
                     player.sendMessage(
                             new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header"));
                     player.sendMessage(new TextComponentString(error.getErrorInfo()));
+                    if (error.getPos() != null) BlockPosHighlightRenderer.renderBlockBoxHighLight(error.getPos(), 5000);
                     return EnumActionResult.SUCCESS;
                 }
             }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index fafd7d07c17..19b7b8c2c94 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -3,7 +3,6 @@
 import gregtech.api.GTValues;
 import gregtech.api.GregTechAPI;
 import gregtech.api.block.ICleanroomFilter;
-import gregtech.api.block.IHeatingCoilBlockStats;
 import gregtech.api.capability.GregtechDataCodes;
 import gregtech.api.capability.GregtechTileCapabilities;
 import gregtech.api.capability.IEnergyContainer;
@@ -25,7 +24,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
 import gregtech.api.pattern.GreggyBlockPos;
-import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternError;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.FactoryExpandablePattern;
@@ -43,7 +41,6 @@
 import gregtech.common.blocks.BlockCleanroomCasing;
 import gregtech.common.blocks.BlockGlassCasing;
 import gregtech.common.blocks.MetaBlocks;
-import gregtech.common.metatileentities.MetaTileEntities;
 import gregtech.common.metatileentities.multi.MetaTileEntityCokeOven;
 import gregtech.common.metatileentities.multi.MetaTileEntityPrimitiveBlastFurnace;
 import gregtech.common.metatileentities.multi.MetaTileEntityPrimitiveWaterPump;
@@ -54,7 +51,6 @@
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.resources.I18n;
 import net.minecraft.creativetab.CreativeTabs;
-import net.minecraft.init.Blocks;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.network.PacketBuffer;
@@ -81,13 +77,11 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.EnumMap;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -283,7 +277,8 @@ protected TraceabilityPredicate filterPredicate() {
                         PatternError.PLACEHOLDER,
                 map -> GregTechAPI.CLEANROOM_FILTERS.entrySet().stream()
                         .filter(e -> e.getValue().getCleanroomType() != null)
-                        .filter(e -> e.getValue().getCleanroomType() == CleanroomType.getByName(map.get("cleanroomType")))
+                        .filter(e -> !map.containsKey("cleanroomType") ||
+                                e.getValue().getCleanroomType() == CleanroomType.getByName(map.get("cleanroomType")))
                         .sorted(Comparator.comparingInt(e -> e.getValue().getTier()))
                         .map(e -> new BlockInfo(e.getKey(), null))
                         .toArray(BlockInfo[]::new))
@@ -407,7 +402,8 @@ protected void addDisplayText(List textList) {
                     tl.add(getWithButton(EnumFacing.EAST));
                     tl.add(getWithButton(EnumFacing.DOWN));
 
-                    tl.add(withButton(new TextComponentTranslation("gregtech.multiblock.render." + renderingAABB), "render:" + renderingAABB));
+                    tl.add(withButton(new TextComponentTranslation("gregtech.multiblock.render." + renderingAABB),
+                            "render:" + renderingAABB));
                 })
                 .addEnergyUsageExactLine(isClean() ? 4 : GTValues.VA[getEnergyTier()])
                 .addWorkingStatusLine()
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index d2d82693d36..6948688894c 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -11,7 +11,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockAbility;
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;
@@ -22,17 +21,13 @@
 import gregtech.api.util.TextFormattingUtil;
 import gregtech.client.renderer.ICubeRenderer;
 import gregtech.client.renderer.texture.Textures;
-import gregtech.common.ConfigHolder;
 import gregtech.common.blocks.BlockMetalCasing.MetalCasingType;
 import gregtech.common.blocks.MetaBlocks;
-import gregtech.common.metatileentities.MetaTileEntities;
 import gregtech.core.sound.GTSoundEvents;
 
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.resources.I18n;
-import net.minecraft.init.Blocks;
 import net.minecraft.item.ItemStack;
-import net.minecraft.util.EnumFacing;
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.util.SoundEvent;
 import net.minecraft.util.text.ITextComponent;
@@ -46,13 +41,10 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.IntSummaryStatistics;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.IntStream;
 
 import static gregtech.api.util.RelativeDirection.*;
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
index 77d5832d276..0477c884f2c 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
@@ -1,8 +1,6 @@
 package gregtech.common.metatileentities.multi.electric;
 
 import gregtech.api.GTValues;
-import gregtech.api.GregTechAPI;
-import gregtech.api.block.IHeatingCoilBlockStats;
 import gregtech.api.capability.GregtechDataCodes;
 import gregtech.api.capability.IEnergyContainer;
 import gregtech.api.capability.impl.EnergyContainerHandler;
@@ -25,7 +23,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.MultiblockWithDisplayBase;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
-import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;
@@ -61,7 +58,6 @@
 import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
 import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
-import net.minecraft.init.Blocks;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.network.PacketBuffer;
@@ -80,8 +76,6 @@
 import org.jetbrains.annotations.Nullable;
 import org.lwjgl.opengl.GL11;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -150,8 +144,9 @@ protected BlockPattern createStructurePattern() {
                 .where('G', states("fusionGlass", getCasingState(), getGlassState()))
                 .where('E',
                         states("fusionGlass", getCasingState(), getGlassState())
-                                .or(tieredMTEs((map, mte) -> tier <= mte.getTier() && mte.getTier() <= GTValues.UV, MetaTileEntities.ENERGY_INPUT_HATCH)
-                                .setMinGlobalLimited(1).setPreviewCount(16)))
+                                .or(tieredMTEs((map, mte) -> tier <= mte.getTier() && mte.getTier() <= GTValues.UV,
+                                        MetaTileEntities.ENERGY_INPUT_HATCH)
+                                                .setMinGlobalLimited(1).setPreviewCount(16)))
                 .where('C', states(getCasingState()))
                 .where('K', states(getCoilState()))
                 .where('O', states(getCasingState(), getGlassState()).or(abilities(MultiblockAbility.EXPORT_FLUIDS)))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
index 9dfe5939489..a6a9f6694d0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
@@ -70,17 +70,14 @@ public class MetaTileEntityHPCA extends MultiblockWithDisplayBase
 
     private static final double IDLE_TEMPERATURE = 200;
     private static final double DAMAGE_TEMPERATURE = 1000;
-
     private IEnergyContainer energyContainer;
     private IFluidHandler coolantHandler;
     private final HPCAGridHandler hpcaHandler;
-
     private boolean isActive;
     private boolean isWorkingEnabled = true;
     private boolean hasNotEnoughEnergy;
 
     private double temperature = IDLE_TEMPERATURE; // start at idle temperature
-
     private final ProgressWidget.TimedProgressSupplier progressSupplier;
 
     public MetaTileEntityHPCA(ResourceLocation metaTileEntityId) {
@@ -206,7 +203,9 @@ private void consumeEnergy() {
                 .where('S', selfPredicate())
                 .where('A', states(getAdvancedState()))
                 .where('V', states(getVentState()))
-                .where('X', abilities(MultiblockAbility.HPCA_COMPONENT))
+                .where('X',
+                        abilities(() -> RIGHT.getRelativeFacing(frontFacing, upwardsFacing),
+                                MultiblockAbility.HPCA_COMPONENT))
                 .where('C', states(getCasingState()).setMinGlobalLimited(5)
                         .or(maintenancePredicate())
                         .or(abilities(MultiblockAbility.INPUT_ENERGY).setMinGlobalLimited(1))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index 87746bfc024..58ff589be56 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -2,7 +2,6 @@
 
 import gregtech.api.GTValues;
 import gregtech.api.GregTechAPI;
-import gregtech.api.block.IHeatingCoilBlockStats;
 import gregtech.api.capability.GregtechDataCodes;
 import gregtech.api.capability.GregtechTileCapabilities;
 import gregtech.api.capability.IControllable;
@@ -24,9 +23,7 @@
 import gregtech.common.ConfigHolder;
 import gregtech.common.blocks.BlockGlassCasing;
 import gregtech.common.blocks.BlockMetalCasing;
-import gregtech.common.blocks.BlockMetalCasing.MetalCasingType;
 import gregtech.common.blocks.MetaBlocks;
-import gregtech.common.metatileentities.MetaTileEntities;
 
 import net.minecraft.block.state.IBlockState;
 import net.minecraft.client.resources.I18n;
@@ -54,7 +51,6 @@
 import java.time.Duration;
 import java.util.*;
 import java.util.function.Supplier;
-import java.util.stream.IntStream;
 
 import static gregtech.api.util.RelativeDirection.*;
 
@@ -275,7 +271,8 @@ protected IBlockState getGlassState() {
             worldState -> GregTechAPI.PSS_BATTERIES.containsKey(worldState.getBlockState()) ? null :
                     PatternError.PLACEHOLDER,
             map -> GregTechAPI.PSS_BATTERIES.entrySet().stream()
-                    .filter(e -> e.getValue().getTier() == GTUtility.parseInt(map.get("batteryTier"), GTValues.EV))
+                    .filter(e -> !map.containsKey("batteryTier") ||
+                            e.getValue().getTier() == GTUtility.parseInt(map.get("batteryTier"), GTValues.EV))
                     .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier()))
                     .map(entry -> new BlockInfo(entry.getKey(), null))
                     .toArray(BlockInfo[]::new))
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
index 5cbf781795b..c115bc1db33 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
@@ -14,7 +14,6 @@
 import gregtech.api.metatileentity.multiblock.MultiblockDisplayText;
 import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController;
 import gregtech.api.pattern.MultiblockShapeInfo;
-import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.BlockPattern;
 import gregtech.api.pattern.pattern.FactoryBlockPattern;
 import gregtech.api.recipes.Recipe;
@@ -45,8 +44,6 @@
 import java.util.Collections;
 import java.util.List;
 
-import static gregtech.api.util.RelativeDirection.*;
-
 public class MetaTileEntityResearchStation extends RecipeMapMultiblockController
                                            implements IOpticalComputationReceiver {
 
@@ -139,7 +136,7 @@ protected BlockPattern createStructurePattern() {
                         .or(abilities(MultiblockAbility.INPUT_ENERGY).setMinGlobalLimited(1))
                         .or(maintenancePredicate())
                         .or(abilities(MultiblockAbility.COMPUTATION_DATA_RECEPTION).setExactLimit(1)))
-                .where('H', abilities(MultiblockAbility.OBJECT_HOLDER))
+                .where('H', abilities(() -> getFrontFacing().getOpposite(), MultiblockAbility.OBJECT_HOLDER))
                 .build();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index 777701d67b3..2cf862b0d88 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -11,7 +11,6 @@
 import gregtech.api.metatileentity.interfaces.IGregTechTileEntity;
 import gregtech.api.metatileentity.multiblock.IMultiblockPart;
 import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
-import gregtech.api.pattern.MultiblockShapeInfo;
 import gregtech.api.pattern.PatternError;
 import gregtech.api.pattern.TraceabilityPredicate;
 import gregtech.api.pattern.pattern.FactoryExpandablePattern;
@@ -23,7 +22,6 @@
 import gregtech.client.utils.TooltipHelper;
 import gregtech.common.blocks.MetaBlocks;
 import gregtech.common.items.behaviors.LighterBehaviour;
-import gregtech.common.metatileentities.MetaTileEntities;
 
 import net.minecraft.block.Block;
 import net.minecraft.client.resources.I18n;
@@ -42,7 +40,6 @@
 import net.minecraft.util.ResourceLocation;
 import net.minecraft.util.SoundCategory;
 import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.text.TextComponentString;
 import net.minecraft.util.text.TextComponentTranslation;
 import net.minecraft.world.World;
 import net.minecraftforge.common.MinecraftForge;
@@ -60,7 +57,6 @@
 import crafttweaker.annotations.ZenRegister;
 import crafttweaker.api.block.IBlock;
 import crafttweaker.api.minecraft.CraftTweakerMC;
-import it.unimi.dsi.fastutil.objects.ObjectArrayList;
 import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -68,12 +64,14 @@
 import stanhebben.zenscript.annotations.ZenMethod;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.EnumMap;
-import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.IntStream;
 
 @ZenClass("mods.gregtech.machines.CharcoalPileIgniter")
 @ZenRegister
@@ -150,14 +148,13 @@ protected void formStructure(String name) {
     @Override
     protected IBlockPattern createStructurePattern() {
         TraceabilityPredicate floorPredicate = blocks(Blocks.BRICK_BLOCK);
-        TraceabilityPredicate wallPredicate = blocks(WALL_BLOCKS.toArray(new Block[0]));
+        TraceabilityPredicate wallPredicate = blocks("walls", WALL_BLOCKS.toArray(new Block[0]));
         TraceabilityPredicate logPredicate = logPredicate();
 
         // basically cleanroom code
         return FactoryExpandablePattern.start(RelativeDirection.UP, RelativeDirection.RIGHT, RelativeDirection.FRONT)
                 .boundsFunction((w, c, f, u) -> bounds)
                 .predicateFunction((c, b) -> {
-                    // controller always at origin
                     if (c.origin()) return selfPredicate();
 
                     int intersects = 0;
@@ -173,23 +170,26 @@ protected IBlockPattern createStructurePattern() {
                     // char dir is front, so its bounds[4] and bounds[5]
                     if (c.z() == b[4] || c.z() == -b[5]) intersects++;
 
-                    // GTLog.logger.info(intersects + " intersects at " + c);
-
-                    // more than or equal to 2 intersects means it is an edge
                     if (intersects >= 2) return any();
 
-                    // 1 intersect means it is a face
                     if (intersects == 1) {
                         if (botAisle) return floorPredicate;
                         return wallPredicate;
                     }
 
-                    // intersects == 0, so its not a face
                     return logPredicate;
                 })
                 .build();
     }
 
+    @NotNull
+    @Override
+    public Iterator> getPreviewBuilds() {
+        return IntStream.range(0, WALL_BLOCKS.size())
+                .mapToObj(i -> Collections.singletonMap("walls", Integer.toString(i)))
+                .iterator();
+    }
+
     @NotNull
     private TraceabilityPredicate logPredicate() {
         return new TraceabilityPredicate(
@@ -270,7 +270,8 @@ public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFaci
             playerIn.sendMessage(
                     new TextComponentTranslation("gregtech.direction." + facing.name().toLowerCase(Locale.ROOT))
                             .appendText(" ")
-                            .appendSibling(new TextComponentTranslation("gregtech.machine.miner.radius", bounds[dir.ordinal()])));
+                            .appendSibling(new TextComponentTranslation("gregtech.machine.miner.radius",
+                                    bounds[dir.ordinal()])));
             getSubstructure("MAIN").clearCache();
             return true;
         }
@@ -308,26 +309,6 @@ public void addInformation(ItemStack stack, @Nullable World player, @NotNull Lis
         }
     }
 
-    @Override
-    public List getMatchingShapes() {
-        List shapeInfos = new ObjectArrayList<>();
-        for (Block block : WALL_BLOCKS) {
-            shapeInfos.add(MultiblockShapeInfo.builder()
-                    .aisle("     ", " XXX ", " XXX ", " XXX ", "     ")
-                    .aisle(" BBB ", "XCCCX", "XCCCX", "XCCCX", " DDD ")
-                    .aisle(" BBB ", "XCCCX", "XCCCX", "XCCCX", " DSD ")
-                    .aisle(" BBB ", "XCCCX", "XCCCX", "XCCCX", " DDD ")
-                    .aisle("     ", " XXX ", " XXX ", " XXX ", "     ")
-                    .where('S', MetaTileEntities.CHARCOAL_PILE_IGNITER, EnumFacing.NORTH)
-                    .where('B', Blocks.BRICK_BLOCK.getDefaultState())
-                    .where('X', block.getDefaultState())
-                    .where('D', block.getDefaultState())
-                    .where('C', Blocks.LOG.getDefaultState())
-                    .build());
-        }
-        return shapeInfos;
-    }
-
     @Override
     public NBTTagCompound writeToNBT(NBTTagCompound data) {
         super.writeToNBT(data);
diff --git a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
index 5f1105fbee1..59a3610a532 100644
--- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
+++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java
@@ -489,7 +489,7 @@ private MBPattern initializePattern(@NotNull MultiblockControllerBase src, @NotN
         Set structures = src.trySubstructure(map);
 
         Long2ObjectMap copy = new Long2ObjectOpenHashMap<>();
-        for (String  structure : structures) {
+        for (String structure : structures) {
             Long2ObjectMap predicates = ((MultiblockControllerBase) holder.getMetaTileEntity())
                     .getSubstructure(structure)
                     .getDefaultShape((MultiblockControllerBase) holder.getMetaTileEntity(), map);
diff --git a/src/test/java/gregtech/api/util/TierByVoltageTest.java b/src/test/java/gregtech/api/util/TierByVoltageTest.java
index d0f6fdc75a5..fafe266f771 100644
--- a/src/test/java/gregtech/api/util/TierByVoltageTest.java
+++ b/src/test/java/gregtech/api/util/TierByVoltageTest.java
@@ -1,15 +1,13 @@
 package gregtech.api.util;
 
-import net.minecraft.util.math.BlockPos;
-
 import org.junit.jupiter.api.Test;
 
 import static gregtech.api.GTValues.*;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class TierByVoltageTest {
+
     @Test
     public void testV() {
         expectTier(V[ULV], ULV, ULV);

From d3c68652ebe70fcb5c7664665b18960b4e580528 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Wed, 25 Dec 2024 21:42:20 -0800
Subject: [PATCH 54/64] bruh

---
 .../primitive/MetaTileEntityCharcoalPileIgniter.java             | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index 2cf862b0d88..9f6f8303776 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -25,6 +25,7 @@
 
 import net.minecraft.block.Block;
 import net.minecraft.client.resources.I18n;
+import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.init.Blocks;
 import net.minecraft.init.SoundEvents;
 import net.minecraft.item.ItemFireball;

From 1c15477b9d09a5665543c6315ac9df4f8068e41b Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Wed, 25 Dec 2024 22:38:23 -0800
Subject: [PATCH 55/64] addressing the deprecations

---
 .../api/capability/IMultiblockController.java |  4 +++
 .../impl/SteamMultiblockRecipeLogic.java      |  2 +-
 .../multiblock/FuelMultiblockController.java  |  8 +++---
 .../multiblock/IMultiblockPart.java           |  8 +++---
 .../multiblock/MultiblockControllerBase.java  | 28 +++++++------------
 .../multiblock/MultiblockWithDisplayBase.java | 10 +++----
 .../RecipeMapMultiblockController.java        |  6 ++--
 ...ecipeMapPrimitiveMultiblockController.java |  2 +-
 .../RecipeMapSteamMultiblockController.java   |  6 ++--
 .../gregtech/api/pattern/GreggyBlockPos.java  |  6 ++--
 .../AdvancedMonitorPluginBehavior.java        |  6 ++--
 .../multi/MetaTileEntityLargeBoiler.java      | 14 +++++-----
 .../multi/MetaTileEntityMultiblockTank.java   |  6 ++--
 .../MetaTileEntityPrimitiveBlastFurnace.java  |  2 +-
 .../MetaTileEntityPrimitiveWaterPump.java     |  2 +-
 .../MetaTileEntityActiveTransformer.java      |  2 +-
 .../electric/MetaTileEntityAssemblyLine.java  |  2 +-
 .../electric/MetaTileEntityCleanroom.java     | 14 +++++-----
 .../electric/MetaTileEntityCrackingUnit.java  |  4 +--
 .../electric/MetaTileEntityDataBank.java      |  6 ++--
 .../MetaTileEntityDistillationTower.java      |  4 +--
 .../MetaTileEntityElectricBlastFurnace.java   |  4 +--
 .../electric/MetaTileEntityFluidDrill.java    | 10 +++----
 .../electric/MetaTileEntityFusionReactor.java |  4 +--
 .../multi/electric/MetaTileEntityHPCA.java    | 14 +++++-----
 .../electric/MetaTileEntityLargeMiner.java    | 14 +++++-----
 .../electric/MetaTileEntityMultiSmelter.java  |  4 +--
 .../electric/MetaTileEntityNetworkSwitch.java |  8 +++---
 .../MetaTileEntityPowerSubstation.java        |  8 +++---
 .../MetaTileEntityProcessingArray.java        |  4 +--
 .../electric/MetaTileEntityPyrolyseOven.java  |  4 +--
 .../MetaTileEntityResearchStation.java        |  6 ++--
 .../MetaTileEntityCentralMonitor.java         | 12 ++++----
 .../MetaTileEntityLargeCombustionEngine.java  | 10 +++----
 .../generator/MetaTileEntityLargeTurbine.java | 12 ++++----
 .../MetaTileEntityComputationHatch.java       |  6 ++--
 .../MetaTileEntityObjectHolder.java           |  2 +-
 .../multi/steam/MetaTileEntitySteamOven.java  |  2 +-
 .../MetaTileEntityCharcoalPileIgniter.java    |  6 ++--
 .../provider/MaintenanceDataProvider.java     |  2 +-
 .../provider/MultiblockDataProvider.java      |  2 +-
 .../provider/MaintenanceInfoProvider.java     |  2 +-
 .../provider/MultiblockInfoProvider.java      |  2 +-
 43 files changed, 137 insertions(+), 143 deletions(-)

diff --git a/src/main/java/gregtech/api/capability/IMultiblockController.java b/src/main/java/gregtech/api/capability/IMultiblockController.java
index 6fb31c54c0c..73e867d427f 100644
--- a/src/main/java/gregtech/api/capability/IMultiblockController.java
+++ b/src/main/java/gregtech/api/capability/IMultiblockController.java
@@ -4,6 +4,10 @@ public interface IMultiblockController {
 
     boolean isStructureFormed(String name);
 
+    default boolean isStructureFormed() {
+        return isStructureFormed("MAIN");
+    }
+
     default boolean isStructureObstructed() {
         return false;
     }
diff --git a/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java b/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java
index a414689b121..2c05b255d40 100644
--- a/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java
+++ b/src/main/java/gregtech/api/capability/impl/SteamMultiblockRecipeLogic.java
@@ -79,7 +79,7 @@ private void combineSteamTanks() {
     public void update() {
         // Fixes an annoying GTCE bug in AbstractRecipeLogic
         RecipeMapSteamMultiblockController controller = (RecipeMapSteamMultiblockController) metaTileEntity;
-        if (isActive && !controller.isStructureFormed("MAIN")) {
+        if (isActive && !controller.isStructureFormed()) {
             progressTime = 0;
             wasActiveAndNeedsUpdate = true;
         }
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/FuelMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/FuelMultiblockController.java
index 2a9d50fd4d7..790a150ec0c 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/FuelMultiblockController.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/FuelMultiblockController.java
@@ -44,7 +44,7 @@ protected void initializeAbilities() {
     protected void addDisplayText(List textList) {
         MultiblockFuelRecipeLogic recipeLogic = (MultiblockFuelRecipeLogic) recipeMapWorkable;
 
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeLogic.isWorkingEnabled(), recipeLogic.isActive())
                 .addEnergyProductionLine(getMaxVoltage(), recipeLogic.getRecipeEUt())
                 .addFuelNeededLine(recipeLogic.getRecipeFluidInputInfo(), recipeLogic.getPreviousRecipeDuration())
@@ -62,13 +62,13 @@ protected long getMaxVoltage() {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
                 .addLowDynamoTierLine(isDynamoTierTooLow())
                 .addMaintenanceProblemLines(getMaintenanceProblems());
     }
 
     protected boolean isDynamoTierTooLow() {
-        if (isStructureFormed("MAIN")) {
+        if (isStructureFormed()) {
             IEnergyContainer energyContainer = recipeMapWorkable.getEnergyContainer();
             if (energyContainer != null && energyContainer.getEnergyCapacity() > 0) {
                 long maxVoltage = Math.max(energyContainer.getInputVoltage(), energyContainer.getOutputVoltage());
@@ -144,7 +144,7 @@ protected void addFuelText(List textList) {
         int fuelCapacity = 0;
         FluidStack fuelStack = null;
         MultiblockFuelRecipeLogic recipeLogic = (MultiblockFuelRecipeLogic) recipeMapWorkable;
-        if (isStructureFormed("MAIN") && recipeLogic.getInputFluidStack() != null && getInputFluidInventory() != null) {
+        if (isStructureFormed() && recipeLogic.getInputFluidStack() != null && getInputFluidInventory() != null) {
             fuelStack = recipeLogic.getInputFluidStack().copy();
             fuelStack.amount = Integer.MAX_VALUE;
             int[] fuelAmount = getTotalFluidAmount(fuelStack, getInputFluidInventory());
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
index fbec530f467..68cc7693c7d 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
@@ -7,10 +7,6 @@ public interface IMultiblockPart {
 
     boolean isAttachedToMultiBlock();
 
-    /**
-     * Use {@link IMultiblockPart#addToMultiBlock(MultiblockControllerBase, String)} instead!
-     */
-    @Deprecated
     default void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
         addToMultiBlock(controllerBase, "MAIN");
     }
@@ -32,6 +28,10 @@ default void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
 
     boolean canPartShare(MultiblockControllerBase target, String substructureName);
 
+    default boolean canPartShare(MultiblockControllerBase target) {
+        return canPartShare(target, "MAIN");
+    }
+
     default boolean canPartShare() {
         return true;
     }
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index fc88d3ce62a..0443397a07f 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -126,7 +126,7 @@ public void update() {
             }
             // DummyWorld is the world for the JEI preview. We do not want to update the Multi in this world,
             // besides initially forming it in checkStructurePattern
-            if (isStructureFormed("MAIN") && !(getWorld() instanceof DummyWorld)) {
+            if (isStructureFormed() && !(getWorld() instanceof DummyWorld)) {
                 updateFormedValid();
             }
         }
@@ -172,7 +172,7 @@ public void setUpwardsFacing(EnumFacing upwardsFacing) {
     }
 
     public boolean isFlipped() {
-        return getSubstructure("MAIN").getPatternState().isFlipped();
+        return getSubstructure().getPatternState().isFlipped();
     }
 
     /** Should not be called outside of structure formation logic! */
@@ -353,7 +353,7 @@ public static TraceabilityPredicate heatingCoils() {
      * INVALID_CACHED,
      * so the pattern will not have its cache cleared, and the controller will not attempt to form the pattern again
      * unless the cache is invalidated(either through code or through it failing).
-     * Example: {@code allSameType(GregTechAPI.HEATING_COILS, getSubstructure("MAIN"))}
+     * Example: {@code allSameType(GregTechAPI.HEATING_COILS, getSubstructure())}
      * 
      * @param info    The info, such as GregTechAPI.HEATING_COILS
      * @param pattern Pattern, used to get the cache. It will also be used to set the error.
@@ -586,18 +586,10 @@ protected void formStructure(String name) {
         writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString(name).writeBoolean(true));
     }
 
-    /**
-     * Use {@link MultiblockControllerBase#formStructure(String)} instead!
-     */
-    @Deprecated
     protected void formStructure() {
         formStructure("MAIN");
     }
 
-    /**
-     * Use {@link MultiblockControllerBase#invalidateStructure(String)} instead!
-     */
-    @Deprecated
     public void invalidateStructure() {
         invalidateStructure("MAIN");
     }
@@ -643,6 +635,10 @@ protected void invalidStructureCaches() {
     public IBlockPattern getSubstructure(String name) {
         return structures.get(name);
     }
+    
+    public IBlockPattern getSubstructure() {
+        return getSubstructure("MAIN");
+    }
 
     public String trySubstructure(String name) {
         if (structures.get(name) != null) return name;
@@ -726,7 +722,7 @@ public void receiveCustomData(int dataId, PacketBuffer buf) {
             String name = buf.readString(Short.MAX_VALUE);
             getSubstructure(name).getPatternState().setFormed(buf.readBoolean());
 
-            if (!isStructureFormed("MAIN")) {
+            if (!isStructureFormed()) {
                 GregTechAPI.soundManager.stopTileSound(getPos());
             }
         }
@@ -743,12 +739,8 @@ public  T getCapability(Capability capability, EnumFacing side) {
         return null;
     }
 
-    /**
-     * Use {@link MultiblockControllerBase#isStructureFormed(String)} instead!
-     */
-    @Deprecated
     public boolean isStructureFormed() {
-        return isStructureFormed("MAIN");
+        return isStructureFormed();
     }
 
     public boolean isStructureFormed(String name) {
@@ -799,7 +791,7 @@ public boolean onRightClick(EntityPlayer playerIn, EnumHand hand, EnumFacing fac
         if (super.onRightClick(playerIn, hand, facing, hitResult))
             return true;
 
-        if (this.getWorld().isRemote && !this.isStructureFormed("MAIN") && playerIn.isSneaking() &&
+        if (this.getWorld().isRemote && !this.isStructureFormed() && playerIn.isSneaking() &&
                 playerIn.getHeldItem(hand).isEmpty()) {
             MultiblockPreviewRenderer.renderMultiBlockPreview(this, playerIn, 60000);
             return true;
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java
index 3bcf15174c1..b1a26a7d4ee 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockWithDisplayBase.java
@@ -269,7 +269,7 @@ public boolean isMufflerFaceFree() {
         if (hasMufflerMechanics() && getAbilities(MultiblockAbility.MUFFLER_HATCH).size() == 0)
             return false;
 
-        return isStructureFormed("MAIN") && hasMufflerMechanics() &&
+        return isStructureFormed() && hasMufflerMechanics() &&
                 getAbilities(MultiblockAbility.MUFFLER_HATCH).get(0).isFrontFaceFree();
     }
 
@@ -302,7 +302,7 @@ protected void setRecoveryItems(ItemStack... recoveryItems) {
      * @return whether the current multiblock is active or not
      */
     public boolean isActive() {
-        return isStructureFormed("MAIN");
+        return isStructureFormed();
     }
 
     @Override
@@ -366,7 +366,7 @@ protected TraceabilityPredicate maintenancePredicate() {
      * to use translation, use TextComponentTranslation
      */
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"));
+        MultiblockDisplayText.builder(textList, isStructureFormed());
     }
 
     /**
@@ -512,7 +512,7 @@ protected Widget getFlexButton(int x, int y, int width, int height) {
      * Recommended to only display warnings if the structure is already formed.
      */
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
                 .addMaintenanceProblemLines(getMaintenanceProblems());
     }
 
@@ -521,7 +521,7 @@ protected void addWarningText(List textList) {
      * Prioritized over any warnings provided by {@link MultiblockWithDisplayBase#addWarningText}.
      */
     protected void addErrorText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .addMufflerObstructedLine(hasMufflerMechanics() && !isMufflerFaceFree());
     }
 
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java
index 967d27b76e0..c1885f1056b 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapMultiblockController.java
@@ -113,7 +113,7 @@ protected void updateFormedValid() {
 
     @Override
     public boolean isActive() {
-        return isStructureFormed("MAIN") && recipeMapWorkable.isActive() && recipeMapWorkable.isWorkingEnabled();
+        return isStructureFormed() && recipeMapWorkable.isActive() && recipeMapWorkable.isWorkingEnabled();
     }
 
     protected void initializeAbilities() {
@@ -144,7 +144,7 @@ protected boolean allowSameFluidFillForOutputs() {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(recipeMapWorkable.getEnergyContainer())
                 .addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
@@ -155,7 +155,7 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
                 .addLowPowerLine(recipeMapWorkable.isHasNotEnoughEnergy())
                 .addMaintenanceProblemLines(getMaintenanceProblems());
     }
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java
index 3d54883b449..c7cbe408016 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapPrimitiveMultiblockController.java
@@ -90,7 +90,7 @@ public SoundEvent getSound() {
 
     @Override
     protected boolean openGUIOnRightClick() {
-        return isStructureFormed("MAIN");
+        return isStructureFormed();
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java
index f567d4bb2a5..668434b31f6 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/RecipeMapSteamMultiblockController.java
@@ -101,7 +101,7 @@ private void resetTileAbilities() {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addCustom(tl -> {
                     // custom steam tank line
@@ -127,9 +127,9 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN") && recipeMapWorkable.isHasNotEnoughEnergy()) {
+                    if (isStructureFormed() && recipeMapWorkable.isHasNotEnoughEnergy()) {
                         tl.add(TextComponentUtil.translationWithColor(
                                 TextFormatting.YELLOW,
                                 "gregtech.multiblock.steam.low_steam"));
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index dd5dc468db9..a3214fe0bf2 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -55,8 +55,7 @@ public GreggyBlockPos(long l) {
      * @param value The value of said coordinate
      */
     public GreggyBlockPos set(EnumFacing.Axis axis, int value) {
-        pos[axis.ordinal()] = value;
-        return this;
+        return set(axis.ordinal(), value);
     }
 
     /**
@@ -66,8 +65,7 @@ public GreggyBlockPos set(EnumFacing.Axis axis, int value) {
      * @param other The pos to take the other axis' value from.
      */
     public GreggyBlockPos set(EnumFacing.Axis axis, GreggyBlockPos other) {
-        pos[axis.ordinal()] = other.pos[axis.ordinal()];
-        return this;
+        return set(axis.ordinal(), other.pos[axis.ordinal()]);
     }
 
     /**
diff --git a/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
index ede9ff3e270..a705e606a83 100644
--- a/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java
@@ -247,10 +247,10 @@ public void update() {
                 }
             } else { // check multi-block valid
                 if (holder != null && holder.getMetaTileEntity() instanceof MultiblockControllerBase entity) {
-                    if (entity.isStructureFormed("MAIN")) {
+                    if (entity.isStructureFormed()) {
                         if (!isValid) {
-                            if (entity.getSubstructure("MAIN").getPatternState().getState().isValid()) {
-                                validPos = entity.getSubstructure("MAIN").getCache().keySet().stream()
+                            if (entity.getSubstructure().getPatternState().getState().isValid()) {
+                                validPos = entity.getSubstructure().getCache().keySet().stream()
                                         .map(BlockPos::fromLong)
                                         .collect(Collectors.toSet());
                                 writePluginData(GregtechDataCodes.UPDATE_ADVANCED_VALID_POS, buf -> {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
index d9de77f0a11..c512cb16c26 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java
@@ -96,10 +96,10 @@ private void resetTileAbilities() {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeLogic.isWorkingEnabled(), recipeLogic.isActive())
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
                         // Steam Output line
                         ITextComponent steamOutput = TextComponentUtil.stringWithColor(
                                 TextFormatting.AQUA,
@@ -149,7 +149,7 @@ private TextFormatting getNumberColor(int number) {
     @Override
     protected void addWarningText(List textList) {
         super.addWarningText(textList);
-        if (isStructureFormed("MAIN")) {
+        if (isStructureFormed()) {
             int[] waterAmount = getWaterAmount();
             if (waterAmount[0] == 0) {
                 textList.add(TextComponentUtil.translationWithColor(TextFormatting.YELLOW,
@@ -234,7 +234,7 @@ protected ICubeRenderer getFrontOverlay() {
     }
 
     private boolean isFireboxPart(IMultiblockPart sourcePart) {
-        return isStructureFormed("MAIN") && (((MetaTileEntity) sourcePart).getPos().getY() < getPos().getY());
+        return isStructureFormed() && (((MetaTileEntity) sourcePart).getPos().getY() < getPos().getY());
     }
 
     @SideOnly(Side.CLIENT)
@@ -321,7 +321,7 @@ protected boolean shouldShowVoidingModeButton() {
 
     @Override
     public double getFillPercentage(int index) {
-        if (!isStructureFormed("MAIN")) return 0;
+        if (!isStructureFormed()) return 0;
         int[] waterAmount = getWaterAmount();
         if (waterAmount[1] == 0) return 0; // no water capacity
         return (1.0 * waterAmount[0]) / waterAmount[1];
@@ -334,7 +334,7 @@ public TextureArea getProgressBarTexture(int index) {
 
     @Override
     public void addBarHoverText(List hoverList, int index) {
-        if (!isStructureFormed("MAIN")) {
+        if (!isStructureFormed()) {
             hoverList.add(TextComponentUtil.translationWithColor(TextFormatting.GRAY,
                     "gregtech.multiblock.invalid_structure"));
         } else {
@@ -360,7 +360,7 @@ public void addBarHoverText(List hoverList, int index) {
      * If there is no water in the boiler (or the structure isn't formed, both of these values will be zero.
      */
     private int[] getWaterAmount() {
-        if (!isStructureFormed("MAIN")) return new int[] { 0, 0 };
+        if (!isStructureFormed()) return new int[] { 0, 0 };
         List tanks = getAbilities(MultiblockAbility.IMPORT_FLUIDS);
         int filled = 0, capacity = 0;
         for (IFluidTank tank : tanks) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
index 349798a673c..719dac5bd54 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java
@@ -118,14 +118,14 @@ public boolean hasMaintenanceMechanics() {
     @Override
     public boolean onRightClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing,
                                 CuboidRayTraceResult hitResult) {
-        if (!isStructureFormed("MAIN"))
+        if (!isStructureFormed())
             return false;
         return super.onRightClick(playerIn, hand, facing, hitResult);
     }
 
     @Override
     protected boolean openGUIOnRightClick() {
-        return isStructureFormed("MAIN");
+        return isStructureFormed();
     }
 
     @Override
@@ -162,7 +162,7 @@ public void addInformation(ItemStack stack, @Nullable World player, @NotNull Lis
     @Override
     public  T getCapability(Capability capability, EnumFacing side) {
         if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
-            if (isStructureFormed("MAIN")) {
+            if (isStructureFormed()) {
                 return CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY.cast(fluidInventory);
             } else {
                 return null;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
index 746c58c93d0..05aa41f2b00 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java
@@ -109,7 +109,7 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
         super.renderMetaTileEntity(renderState, translation, pipeline);
         getFrontOverlay().renderOrientedState(renderState, translation, pipeline, getFrontFacing(),
                 recipeMapWorkable.isActive(), recipeMapWorkable.isWorkingEnabled());
-        if (recipeMapWorkable.isActive() && isStructureFormed("MAIN")) {
+        if (recipeMapWorkable.isActive() && isStructureFormed()) {
             EnumFacing back = getFrontFacing().getOpposite();
             Matrix4 offset = translation.copy().translate(back.getXOffset(), -0.3, back.getZOffset());
             CubeRendererState op = Textures.RENDER_STATE.get();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
index aeebfd0be3f..3f26d10fa59 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java
@@ -56,7 +56,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     @Override
     public void update() {
         super.update();
-        if (!getWorld().isRemote && getOffsetTimer() % 20 == 0 && isStructureFormed("MAIN")) {
+        if (!getWorld().isRemote && getOffsetTimer() % 20 == 0 && isStructureFormed()) {
             if (biomeModifier == 0) {
                 biomeModifier = getAmount();
             } else if (biomeModifier > 0) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
index 36c83c111ec..58abfc8ef6d 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java
@@ -164,7 +164,7 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(true, isActive()) // set to true because we only want a two-state system (running or
                                                     // not running)
                 .setWorkingStatusKeys(
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
index 5f5c77c5a1d..0bb09b2620f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
@@ -158,7 +158,7 @@ public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
             }
         } else {
             // controller rendering
-            if (isStructureFormed("MAIN")) {
+            if (isStructureFormed()) {
                 return Textures.GRATE_CASING_STEEL_FRONT;
             } else {
                 return Textures.SOLID_STEEL_CASING;
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
index 19b7b8c2c94..dc10f9c0028 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCleanroom.java
@@ -144,7 +144,7 @@ protected void formStructure(String name) {
         renderingAABB = false;
         writeCustomData(GregtechDataCodes.RENDER_UPDATE, buf -> buf.writeBoolean(false));
 
-        ICleanroomFilter type = allSameType(GregTechAPI.CLEANROOM_FILTERS, getSubstructure("MAIN"),
+        ICleanroomFilter type = allSameType(GregTechAPI.CLEANROOM_FILTERS, getSubstructure(),
                 "gregtech.multiblock.pattern.error.filters");
         if (type == null) {
             invalidateStructure(name);
@@ -360,12 +360,12 @@ protected boolean isMachineBanned(MetaTileEntity metaTileEntity) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(cleanroomLogic.isWorkingEnabled(), cleanroomLogic.isActive())
                 .addEnergyUsageLine(energyContainer)
                 .addCustom(tl -> {
                     // Cleanliness status line
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
                         ITextComponent cleanState;
                         if (isClean()) {
                             cleanState = TextComponentUtil.translationWithColor(
@@ -394,7 +394,7 @@ protected void addDisplayText(List textList) {
                     }
                 })
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN")) return;
+                    if (isStructureFormed()) return;
 
                     tl.add(getWithButton(EnumFacing.NORTH));
                     tl.add(getWithButton(EnumFacing.WEST));
@@ -451,7 +451,7 @@ protected void handleDisplayClick(String componentData, Widget.ClickData clickDa
 
         writeCustomData(GregtechDataCodes.UPDATE_STRUCTURE_SIZE, buf -> buf.writeVarIntArray(bounds));
 
-        getSubstructure("MAIN").clearCache();
+        getSubstructure().clearCache();
     }
 
     protected static int getFactor(String str) {
@@ -470,10 +470,10 @@ public Iterator> getPreviewBuilds() {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
                 .addLowPowerLine(!drainEnergy(true))
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN") && !isClean()) {
+                    if (isStructureFormed() && !isClean()) {
                         tl.add(TextComponentUtil.translationWithColor(
                                 TextFormatting.YELLOW,
                                 "gregtech.multiblock.cleanroom.warning_contaminated"));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
index facda2238bf..fe3795d0ea7 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityCrackingUnit.java
@@ -81,13 +81,13 @@ public SoundEvent getBreakdownSound() {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(getEnergyContainer())
                 .addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
                 .addCustom(tl -> {
                     // Coil energy discount line
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
                         ITextComponent energyDiscount = TextComponentUtil.stringWithColor(TextFormatting.AQUA,
                                 (100 - 10 * coilTier) + "%");
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
index 351a67fb205..d61a74f1af3 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDataBank.java
@@ -202,7 +202,7 @@ public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
             }
         } else {
             // controller rendering
-            if (isStructureFormed("MAIN")) {
+            if (isStructureFormed()) {
                 return Textures.HIGH_POWER_CASING;
             } else {
                 return Textures.COMPUTER_CASING;
@@ -250,7 +250,7 @@ public void addInformation(ItemStack stack, @Nullable World world, @NotNull List
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(true, isActive() && isWorkingEnabled()) // transform into two-state system for display
                 .setWorkingStatusKeys(
                         "gregtech.multiblock.idling",
@@ -262,7 +262,7 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
                 .addLowPowerLine(hasNotEnoughEnergy)
                 .addMaintenanceProblemLines(getMaintenanceProblems());
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
index 056e43c26f0..f9005cde40c 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
@@ -87,7 +87,7 @@ public boolean allowsExtendedFacing() {
 
     @Override
     protected void addDisplayText(List textList) {
-        if (isStructureFormed("MAIN")) {
+        if (isStructureFormed()) {
             FluidStack stackInTank = importFluids.drain(Integer.MAX_VALUE, false);
             if (stackInTank != null && stackInTank.amount > 0) {
                 ITextComponent fluidName = TextComponentUtil.setColor(GTUtility.getFluidTranslation(stackInTank),
@@ -105,7 +105,7 @@ protected void addDisplayText(List textList) {
     protected void formStructure(String name) {
         super.formStructure(name);
         if (this.handler == null) return;
-        handler.determineLayerCount((BlockPattern) getSubstructure("MAIN"));
+        handler.determineLayerCount((BlockPattern) getSubstructure());
         handler.determineOrderedFluidOutputs();
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
index 6948688894c..53faada3037 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java
@@ -64,13 +64,13 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(getEnergyContainer())
                 .addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
                 .addCustom(tl -> {
                     // Coil heat capacity line
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
                         ITextComponent heatString = TextComponentUtil.stringWithColor(
                                 TextFormatting.RED,
                                 TextFormattingUtil.formatNumbers(blastFurnaceTemperature) + "K");
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
index 8f51424e8a7..41dd80f7ed3 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFluidDrill.java
@@ -159,7 +159,7 @@ public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(minerLogic.isWorkingEnabled(), minerLogic.isActive())
                 .setWorkingStatusKeys(
                         "gregtech.multiblock.idling",
@@ -167,7 +167,7 @@ protected void addDisplayText(List textList) {
                         "gregtech.multiblock.miner.drilling")
                 .addEnergyUsageLine(energyContainer)
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
                         if (minerLogic.getDrilledFluid() != null) {
                             // Fluid name
                             Fluid drilledFluid = minerLogic.getDrilledFluid();
@@ -204,10 +204,10 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
-                .addLowPowerLine(isStructureFormed("MAIN") && !drainEnergy(true))
+        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
+                .addLowPowerLine(isStructureFormed() && !drainEnergy(true))
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN") && minerLogic.isInventoryFull()) {
+                    if (isStructureFormed() && minerLogic.isInventoryFull()) {
                         tl.add(TextComponentUtil.translationWithColor(
                                 TextFormatting.YELLOW,
                                 "gregtech.machine.miner.invfull"));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
index 0477c884f2c..6e0a82f9fb8 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityFusionReactor.java
@@ -270,7 +270,7 @@ protected void updateFormedValid() {
                 setFusionRingColor(0xFF000000 |
                         recipeMapWorkable.getPreviousRecipe().getFluidOutputs().get(0).getFluid().getColor());
             }
-        } else if (!recipeMapWorkable.isWorking() && isStructureFormed("MAIN")) {
+        } else if (!recipeMapWorkable.isWorking() && isStructureFormed()) {
             setFusionRingColor(NO_COLOR);
         }
     }
@@ -529,7 +529,7 @@ public ProgressWidget getWidget(MetaTileEntityFusionReactor instance) {
                         x, y, width, height, texture, moveType)
                                 .setIgnoreColor(true)
                                 .setHoverTextConsumer(
-                                        tl -> MultiblockDisplayText.builder(tl, instance.isStructureFormed("MAIN"))
+                                        tl -> MultiblockDisplayText.builder(tl, instance.isStructureFormed())
                                                 .setWorkingStatus(instance.recipeMapWorkable.isWorkingEnabled(),
                                                         instance.recipeMapWorkable.isActive())
                                                 .addWorkingStatusLine());
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
index a6a9f6694d0..2336c0960f4 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityHPCA.java
@@ -123,7 +123,7 @@ public int getMaxCWUt(@NotNull Collection seen) {
     public boolean canBridge(@NotNull Collection seen) {
         seen.add(this);
         // don't show a problem if the structure is not yet formed
-        return !isStructureFormed("MAIN") || hpcaHandler.hasHPCABridge();
+        return !isStructureFormed() || hpcaHandler.hasHPCABridge();
     }
 
     @Override
@@ -131,7 +131,7 @@ public void update() {
         super.update();
         // we need to know what components we have on the client
         if (getWorld().isRemote) {
-            if (isStructureFormed("MAIN")) {
+            if (isStructureFormed()) {
                 hpcaHandler.tryGatherClientComponents(getWorld(), getPos(), getFrontFacing(), getUpwardsFacing(),
                         isFlipped());
             } else {
@@ -375,7 +375,7 @@ protected ModularUI.Builder createUITemplate(EntityPlayer entityPlayer) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(true, hpcaHandler.getAllocatedCWUt() > 0) // transform into two-state system for
                 // display
                 .setWorkingStatusKeys(
@@ -383,7 +383,7 @@ protected void addDisplayText(List textList) {
                         "gregtech.multiblock.idling",
                         "gregtech.multiblock.data_bank.providing")
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
                         // Energy Usage
                         ITextComponent voltageName = new TextComponentString(
                                 GTValues.VNF[GTUtility.getTierByVoltage(hpcaHandler.getMaxEUt())]);
@@ -418,10 +418,10 @@ private TextFormatting getDisplayTemperatureColor() {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
                 .addLowPowerLine(hasNotEnoughEnergy)
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
                         if (temperature > 500) {
                             // Temperature warning
                             tl.add(TextComponentUtil.translationWithColor(
@@ -444,7 +444,7 @@ protected void addWarningText(List textList) {
     @Override
     protected void addErrorText(List textList) {
         super.addErrorText(textList);
-        if (isStructureFormed("MAIN")) {
+        if (isStructureFormed()) {
             if (temperature > 1000) {
                 textList.add(TextComponentUtil.translationWithColor(TextFormatting.RED,
                         "gregtech.multiblock.hpca.error_temperature"));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
index 52161722432..f5fb25a8d6b 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityLargeMiner.java
@@ -234,7 +234,7 @@ public void addToolUsages(ItemStack stack, @Nullable World world, List t
     protected void addDisplayText(List textList) {
         super.addDisplayText(textList);
 
-        if (this.isStructureFormed("MAIN")) {
+        if (this.isStructureFormed()) {
             if (energyContainer != null && energyContainer.getEnergyCapacity() > 0) {
                 int energyContainer = getEnergyTier();
                 long maxVoltage = GTValues.V[energyContainer];
@@ -270,7 +270,7 @@ else if (!this.isWorkingEnabled())
     }
 
     private void addDisplayText2(List textList) {
-        if (this.isStructureFormed("MAIN")) {
+        if (this.isStructureFormed()) {
             ITextComponent mCoords = new TextComponentString("    ")
                     .appendSibling(new TextComponentTranslation("gregtech.machine.miner.minex",
                             this.minerLogic.getMineX().get()))
@@ -286,10 +286,10 @@ private void addDisplayText2(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
-                .addLowPowerLine(isStructureFormed("MAIN") && !drainEnergy(true))
+        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
+                .addLowPowerLine(isStructureFormed() && !drainEnergy(true))
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN") && isInventoryFull) {
+                    if (isStructureFormed() && isInventoryFull) {
                         tl.add(TextComponentUtil.translationWithColor(
                                 TextFormatting.YELLOW,
                                 "gregtech.machine.miner.invfull"));
@@ -300,7 +300,7 @@ protected void addWarningText(List textList) {
     @Override
     protected void addErrorText(List textList) {
         super.addErrorText(textList);
-        if (isStructureFormed("MAIN") && !drainFluid(true)) {
+        if (isStructureFormed() && !drainFluid(true)) {
             textList.add(TextComponentUtil.translationWithColor(TextFormatting.RED,
                     "gregtech.machine.miner.multi.needsfluid"));
         }
@@ -445,7 +445,7 @@ private void setCurrentMode(int mode) {
     @Override
     public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing,
                                       CuboidRayTraceResult hitResult) {
-        if (getWorld().isRemote || !this.isStructureFormed("MAIN"))
+        if (getWorld().isRemote || !this.isStructureFormed())
             return true;
 
         if (!this.isActive()) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
index 6c93be32502..35ee902b448 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityMultiSmelter.java
@@ -58,12 +58,12 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(recipeMapWorkable.getEnergyContainer())
                 .addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
                         // Heating coil discount
                         if (heatingCoilDiscount > 1) {
                             ITextComponent coilDiscount = TextComponentUtil.stringWithColor(
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
index 53f3d6d0818..cd28a1dbeac 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityNetworkSwitch.java
@@ -75,7 +75,7 @@ public void invalidateStructure(String name) {
 
     @Override
     protected int getEnergyUsage() {
-        return isStructureFormed("MAIN") ? computationHandler.getEUt() : 0;
+        return isStructureFormed() ? computationHandler.getEUt() : 0;
     }
 
     @Override
@@ -87,7 +87,7 @@ public int requestCWUt(int cwut, boolean simulate, @NotNull Collection seen) {
         seen.add(this);
-        return isStructureFormed("MAIN") ? computationHandler.getMaxCWUt(seen) : 0;
+        return isStructureFormed() ? computationHandler.getMaxCWUt(seen) : 0;
     }
 
     // allows chaining Network Switches together
@@ -145,7 +145,7 @@ public void addInformation(ItemStack stack, @Nullable World world, @NotNull List
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(true, isActive() && isWorkingEnabled()) // transform into two-state system for display
                 .setWorkingStatusKeys(
                         "gregtech.multiblock.idling",
@@ -159,7 +159,7 @@ protected void addDisplayText(List textList) {
     @Override
     protected void addWarningText(List textList) {
         super.addWarningText(textList);
-        if (isStructureFormed("MAIN") && computationHandler.hasNonBridgingConnections()) {
+        if (isStructureFormed() && computationHandler.hasNonBridgingConnections()) {
             textList.add(TextComponentUtil.translationWithColor(
                     TextFormatting.YELLOW,
                     "gregtech.multiblock.computation.non_bridging.detailed"));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index 58ff589be56..89ef8257355 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -128,7 +128,7 @@ protected void formStructure(String name) {
 
     protected List determineBatteryParts() {
         List data = new ArrayList<>();
-        for (BlockInfo info : getSubstructure("MAIN").getCache().values()) {
+        for (BlockInfo info : getSubstructure().getCache().values()) {
             if (GregTechAPI.PSS_BATTERIES.containsKey(info.getBlockState())) {
                 data.add(GregTechAPI.PSS_BATTERIES.get(info.getBlockState()));
             }
@@ -300,14 +300,14 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(true, isActive() && isWorkingEnabled()) // transform into two-state system for display
                 .setWorkingStatusKeys(
                         "gregtech.multiblock.idling",
                         "gregtech.multiblock.idling",
                         "gregtech.machine.active_transformer.routing")
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN") && energyBank != null) {
+                    if (isStructureFormed() && energyBank != null) {
                         BigInteger energyStored = energyBank.getStored();
                         BigInteger energyCapacity = energyBank.getCapacity();
 
@@ -391,7 +391,7 @@ protected void addDisplayText(List textList) {
     @Override
     protected void addWarningText(List textList) {
         super.addWarningText(textList);
-        if (isStructureFormed("MAIN")) {
+        if (isStructureFormed()) {
             if (averageInLastSec < averageOutLastSec) { // decreasing
                 BigInteger timeToDrainSeconds = energyBank.getStored()
                         .divide(BigInteger.valueOf((averageOutLastSec - averageInLastSec) * 20));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
index 35beb44f6d6..dedc16cca1a 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java
@@ -117,12 +117,12 @@ public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) {
     protected void addDisplayText(List textList) {
         ProcessingArrayWorkable logic = (ProcessingArrayWorkable) recipeMapWorkable;
 
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(recipeMapWorkable.getEnergyContainer())
                 .addEnergyTierLine(logic.currentMachineStack == ItemStack.EMPTY ? -1 : logic.machineTier)
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
 
                         // Machine mode text
                         // Shared text components for both states
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
index 558e2287d20..cbe1fefeae6 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPyrolyseOven.java
@@ -101,12 +101,12 @@ protected void formStructure(String name) {
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .addEnergyUsageLine(recipeMapWorkable.getEnergyContainer())
                 .addEnergyTierLine(GTUtility.getTierByVoltage(recipeMapWorkable.getMaxVoltage()))
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
                         int processingSpeed = coilTier == 0 ? 75 : 50 * (coilTier + 1);
                         ITextComponent speedIncrease = TextComponentUtil.stringWithColor(
                                 getSpeedColor(processingSpeed),
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
index c115bc1db33..f6180243331 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
@@ -84,7 +84,7 @@ protected void formStructure(String name) {
     @Override
     public void checkStructurePattern() {
         super.checkStructurePattern();
-        if (isStructureFormed("MAIN") && objectHolder.getFrontFacing() != getFrontFacing().getOpposite()) {
+        if (isStructureFormed() && objectHolder.getFrontFacing() != getFrontFacing().getOpposite()) {
             invalidateStructure("MAIN");
         }
     }
@@ -221,7 +221,7 @@ public void addInformation(ItemStack stack, @Nullable World world, @NotNull List
 
     @Override
     protected void addDisplayText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeMapWorkable.isWorkingEnabled(), recipeMapWorkable.isActive())
                 .setWorkingStatusKeys(
                         "gregtech.multiblock.idling",
@@ -237,7 +237,7 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
                 .addLowPowerLine(recipeMapWorkable.isHasNotEnoughEnergy())
                 .addLowComputationLine(getRecipeMapWorkable().isHasNotEnoughComputation())
                 .addMaintenanceProblemLines(getMaintenanceProblems());
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index a6aa5318cbd..973b45c351f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -256,7 +256,7 @@ private void setActive(boolean isActive) {
     }
 
     public boolean isActive() {
-        return isStructureFormed("MAIN") && isActive;
+        return isStructureFormed() && isActive;
     }
 
     private void clearScreens() {
@@ -281,7 +281,7 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
     protected void addDisplayText(List textList) {
         super.addDisplayText(textList);
         textList.add(new TextComponentTranslation("gregtech.multiblock.central_monitor.height", this.height));
-        if (!isStructureFormed("MAIN")) {
+        if (!isStructureFormed()) {
             ITextComponent buttonText = new TextComponentTranslation(
                     "gregtech.multiblock.central_monitor.height_modify", height);
             buttonText.appendText(" ");
@@ -343,7 +343,7 @@ public void receiveCustomData(int id, PacketBuffer buf) {
         } else if (id == GregtechDataCodes.UPDATE_ACTIVE) {
             this.isActive = buf.readBoolean();
         } else if (id == GregtechDataCodes.STRUCTURE_FORMED) {
-            if (!this.isStructureFormed("MAIN")) {
+            if (!this.isStructureFormed()) {
                 clearScreens();
             }
         }
@@ -431,7 +431,7 @@ protected void formStructure(String name) {
         netCovers = new HashSet<>();
         remoteCovers = new HashSet<>();
         inputEnergy = new EnergyContainerList(this.getAbilities(MultiblockAbility.INPUT_ENERGY));
-        width = ((BlockPattern) getSubstructure("MAIN")).getRepetitionCount(1);
+        width = ((BlockPattern) getSubstructure()).getRepetitionCount(1);
         screens = new MetaTileEntityMonitorScreen[width][height];
         for (IMultiblockPart part : this.getMultiblockParts()) {
             if (part instanceof MetaTileEntityMonitorScreen screen) {
@@ -462,7 +462,7 @@ public ICubeRenderer getBaseTexture(IMultiblockPart iMultiblockPart) {
 
     @Override
     public boolean shouldRenderInPass(int pass) {
-        if (this.isStructureFormed("MAIN")) {
+        if (this.isStructureFormed()) {
             return pass == 0;
         }
         return false;
@@ -477,7 +477,7 @@ public boolean isGlobalRenderer() {
     @Override
     @SideOnly(Side.CLIENT)
     public void renderMetaTileEntity(double x, double y, double z, float partialTicks) {
-        if (!this.isStructureFormed("MAIN")) return;
+        if (!this.isStructureFormed()) return;
         RenderUtil.useStencil(() -> {
             GlStateManager.pushMatrix();
             RenderUtil.moveToFace(x, y, z, this.frontFacing);
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
index dfc609ab929..bd9c580e979 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeCombustionEngine.java
@@ -70,7 +70,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
     protected void addDisplayText(List textList) {
         LargeCombustionEngineWorkableHandler recipeLogic = ((LargeCombustionEngineWorkableHandler) recipeMapWorkable);
 
-        MultiblockDisplayText.Builder builder = MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.Builder builder = MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeLogic.isWorkingEnabled(), recipeLogic.isActive());
 
         if (isExtreme) {
@@ -81,7 +81,7 @@ protected void addDisplayText(List textList) {
 
         builder.addFuelNeededLine(recipeLogic.getRecipeFluidInputInfo(), recipeLogic.getPreviousRecipeDuration())
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN") && recipeLogic.isOxygenBoosted) {
+                    if (isStructureFormed() && recipeLogic.isOxygenBoosted) {
                         String key = isExtreme ? "gregtech.multiblock.large_combustion_engine.liquid_oxygen_boosted" :
                                 "gregtech.multiblock.large_combustion_engine.oxygen_boosted";
                         tl.add(TextComponentUtil.translationWithColor(TextFormatting.AQUA, key));
@@ -93,7 +93,7 @@ protected void addDisplayText(List textList) {
     @Override
     protected void addErrorText(List textList) {
         super.addErrorText(textList);
-        if (isStructureFormed("MAIN")) {
+        if (isStructureFormed()) {
             if (checkIntakesObstructed()) {
                 textList.add(TextComponentUtil.translationWithColor(TextFormatting.RED,
                         "gregtech.multiblock.large_combustion_engine.obstructed"));
@@ -281,7 +281,7 @@ public void addBarHoverText(List hoverList, int index) {
             // Lubricant
             int lubricantStored = 0;
             int lubricantCapacity = 0;
-            if (isStructureFormed("MAIN") && getInputFluidInventory() != null) {
+            if (isStructureFormed() && getInputFluidInventory() != null) {
                 // Hunt for tanks with lubricant in them
                 int[] lubricantAmount = getTotalFluidAmount(Materials.Lubricant.getFluid(Integer.MAX_VALUE),
                         getInputFluidInventory());
@@ -302,7 +302,7 @@ public void addBarHoverText(List hoverList, int index) {
             if (isBoostAllowed()) {
                 int oxygenStored = 0;
                 int oxygenCapacity = 0;
-                if (isStructureFormed("MAIN") && getInputFluidInventory() != null) {
+                if (isStructureFormed() && getInputFluidInventory() != null) {
                     // Hunt for tanks with Oxygen or LOX (depending on tier) in them
                     FluidStack oxygenStack = isExtreme ?
                             Materials.Oxygen.getFluid(FluidStorageKeys.LIQUID, Integer.MAX_VALUE) :
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
index a0aeda378d6..80ebb0be31f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/generator/MetaTileEntityLargeTurbine.java
@@ -90,7 +90,7 @@ public void invalidateStructure(String name) {
     public boolean isRotorFaceFree() {
         IRotorHolder rotorHolder = getRotorHolder();
         if (rotorHolder != null)
-            return isStructureFormed("MAIN") && getRotorHolder().isFrontFaceFree();
+            return isStructureFormed() && getRotorHolder().isFrontFaceFree();
         return false;
     }
 
@@ -116,11 +116,11 @@ protected long getMaxVoltage() {
     protected void addDisplayText(List textList) {
         MultiblockFuelRecipeLogic recipeLogic = (MultiblockFuelRecipeLogic) recipeMapWorkable;
 
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"))
+        MultiblockDisplayText.builder(textList, isStructureFormed())
                 .setWorkingStatus(recipeLogic.isWorkingEnabled(), recipeLogic.isActive())
                 .addEnergyProductionLine(getMaxVoltage(), recipeLogic.getRecipeEUt())
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
                         IRotorHolder rotorHolder = getRotorHolder();
                         if (rotorHolder.getRotorEfficiency() > 0) {
                             ITextComponent efficiencyInfo = TextComponentUtil.stringWithColor(
@@ -139,9 +139,9 @@ protected void addDisplayText(List textList) {
 
     @Override
     protected void addWarningText(List textList) {
-        MultiblockDisplayText.builder(textList, isStructureFormed("MAIN"), false)
+        MultiblockDisplayText.builder(textList, isStructureFormed(), false)
                 .addCustom(tl -> {
-                    if (isStructureFormed("MAIN")) {
+                    if (isStructureFormed()) {
                         IRotorHolder rotorHolder = getRotorHolder();
                         if (rotorHolder.getRotorEfficiency() > 0) {
                             if (rotorHolder.getRotorDurabilityPercent() <= MIN_DURABILITY_TO_WARN) {
@@ -159,7 +159,7 @@ protected void addWarningText(List textList) {
     @Override
     protected void addErrorText(List textList) {
         super.addErrorText(textList);
-        if (isStructureFormed("MAIN")) {
+        if (isStructureFormed()) {
             if (!isRotorFaceFree()) {
                 textList.add(TextComponentUtil.translationWithColor(TextFormatting.RED,
                         "gregtech.multiblock.turbine.obstructed"));
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java
index 14694c83868..85c2bc25173 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityComputationHatch.java
@@ -53,7 +53,7 @@ public boolean isTransmitter() {
     public int requestCWUt(int cwut, boolean simulate, @NotNull Collection seen) {
         seen.add(this);
         var controller = getController();
-        if (controller == null || !controller.isStructureFormed("MAIN")) return 0;
+        if (controller == null || !controller.isStructureFormed()) return 0;
         if (isTransmitter()) {
             // Ask the Multiblock controller, which *should* be an IOpticalComputationProvider
             if (controller instanceof IOpticalComputationProvider provider) {
@@ -74,7 +74,7 @@ public int requestCWUt(int cwut, boolean simulate, @NotNull Collection seen) {
         seen.add(this);
         var controller = getController();
-        if (controller == null || !controller.isStructureFormed("MAIN")) return 0;
+        if (controller == null || !controller.isStructureFormed()) return 0;
         if (isTransmitter()) {
             // Ask the Multiblock controller, which *should* be an IOpticalComputationProvider
             if (controller instanceof IOpticalComputationProvider provider) {
@@ -96,7 +96,7 @@ public boolean canBridge(@NotNull Collection seen)
         seen.add(this);
         var controller = getController();
         // return true here so that unlinked hatches don't cause problems in multis like the Network Switch
-        if (controller == null || !controller.isStructureFormed("MAIN")) return true;
+        if (controller == null || !controller.isStructureFormed()) return true;
         if (isTransmitter()) {
             // Ask the Multiblock controller, which *should* be an IOpticalComputationProvider
             if (controller instanceof IOpticalComputationProvider provider) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
index ce580c08774..b0827af76d1 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityObjectHolder.java
@@ -103,7 +103,7 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation,
     public void setFrontFacing(EnumFacing frontFacing) {
         super.setFrontFacing(frontFacing);
         var controller = getController();
-        if (controller != null && controller.isStructureFormed("MAIN")) {
+        if (controller != null && controller.isStructureFormed()) {
             controller.checkStructurePattern();
         }
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
index ae5ff782df2..e582373455e 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/steam/MetaTileEntitySteamOven.java
@@ -77,7 +77,7 @@ public IBlockState getFireboxState() {
     }
 
     private boolean isFireboxPart(IMultiblockPart sourcePart) {
-        return isStructureFormed("MAIN") && (((MetaTileEntity) sourcePart).getPos().getY() < getPos().getY());
+        return isStructureFormed() && (((MetaTileEntity) sourcePart).getPos().getY() < getPos().getY());
     }
 
     @SideOnly(Side.CLIENT)
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index 9f6f8303776..eda1a7a194f 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -213,7 +213,7 @@ private void updateMaxProgressTime() {
     public void update() {
         super.update();
         if (getWorld() != null) {
-            if (!getWorld().isRemote && !this.isStructureFormed("MAIN") && getOffsetTimer() % 20 == 0) {
+            if (!getWorld().isRemote && !this.isStructureFormed() && getOffsetTimer() % 20 == 0) {
                 this.reinitializeStructurePattern();
             } else if (isActive) {
                 BlockPos pos = getPos();
@@ -273,7 +273,7 @@ public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFaci
                             .appendText(" ")
                             .appendSibling(new TextComponentTranslation("gregtech.machine.miner.radius",
                                     bounds[dir.ordinal()])));
-            getSubstructure("MAIN").clearCache();
+            getSubstructure().clearCache();
             return true;
         }
         return super.onScrewdriverClick(playerIn, hand, facing, hitResult);
@@ -413,7 +413,7 @@ public static void onItemUse(@NotNull PlayerInteractEvent.RightClickBlock event)
             mte = ((IGregTechTileEntity) tileEntity).getMetaTileEntity();
         }
         if (mte instanceof MetaTileEntityCharcoalPileIgniter &&
-                ((IMultiblockController) mte).isStructureFormed("MAIN")) {
+                ((IMultiblockController) mte).isStructureFormed()) {
             if (event.getSide().isClient()) {
                 event.setCanceled(true);
                 event.getEntityPlayer().swingArm(EnumHand.MAIN_HAND);
diff --git a/src/main/java/gregtech/integration/hwyla/provider/MaintenanceDataProvider.java b/src/main/java/gregtech/integration/hwyla/provider/MaintenanceDataProvider.java
index ad53210eab8..38719913fd4 100644
--- a/src/main/java/gregtech/integration/hwyla/provider/MaintenanceDataProvider.java
+++ b/src/main/java/gregtech/integration/hwyla/provider/MaintenanceDataProvider.java
@@ -67,7 +67,7 @@ public List getWailaBody(ItemStack itemStack, List tooltip, IWai
 
             IMultiblockController controller = accessor.getTileEntity()
                     .getCapability(GregtechCapabilities.CAPABILITY_MULTIBLOCK_CONTROLLER, null);
-            if (controller == null || !controller.isStructureFormed("MAIN")) {
+            if (controller == null || !controller.isStructureFormed()) {
                 return tooltip;
             }
 
diff --git a/src/main/java/gregtech/integration/hwyla/provider/MultiblockDataProvider.java b/src/main/java/gregtech/integration/hwyla/provider/MultiblockDataProvider.java
index e42d2d8d4d9..06f2b7c6580 100644
--- a/src/main/java/gregtech/integration/hwyla/provider/MultiblockDataProvider.java
+++ b/src/main/java/gregtech/integration/hwyla/provider/MultiblockDataProvider.java
@@ -37,7 +37,7 @@ public void register(@NotNull IWailaRegistrar registrar) {
     @Override
     protected NBTTagCompound getNBTData(IMultiblockController capability, NBTTagCompound tag) {
         NBTTagCompound subTag = new NBTTagCompound();
-        subTag.setBoolean("Formed", capability.isStructureFormed("MAIN"));
+        subTag.setBoolean("Formed", capability.isStructureFormed());
         subTag.setBoolean("Obstructed", capability.isStructureObstructed());
         tag.setTag("gregtech.IMultiblockController", subTag);
         return tag;
diff --git a/src/main/java/gregtech/integration/theoneprobe/provider/MaintenanceInfoProvider.java b/src/main/java/gregtech/integration/theoneprobe/provider/MaintenanceInfoProvider.java
index 211b1206b1e..bf448048a1e 100644
--- a/src/main/java/gregtech/integration/theoneprobe/provider/MaintenanceInfoProvider.java
+++ b/src/main/java/gregtech/integration/theoneprobe/provider/MaintenanceInfoProvider.java
@@ -47,7 +47,7 @@ protected void addProbeInfo(IMaintenance capability, IProbeInfo probeInfo, Entit
             if (tileEntity.hasCapability(GregtechCapabilities.CAPABILITY_MULTIBLOCK_CONTROLLER, null)) {
                 // noinspection ConstantConditions
                 if (tileEntity.getCapability(GregtechCapabilities.CAPABILITY_MULTIBLOCK_CONTROLLER, null)
-                        .isStructureFormed("MAIN")) {
+                        .isStructureFormed()) {
                     if (capability.hasMaintenanceProblems()) {
                         if (player.isSneaking()) {
                             int problems = capability.getMaintenanceProblems();
diff --git a/src/main/java/gregtech/integration/theoneprobe/provider/MultiblockInfoProvider.java b/src/main/java/gregtech/integration/theoneprobe/provider/MultiblockInfoProvider.java
index 8a2cec68153..368e7b91318 100644
--- a/src/main/java/gregtech/integration/theoneprobe/provider/MultiblockInfoProvider.java
+++ b/src/main/java/gregtech/integration/theoneprobe/provider/MultiblockInfoProvider.java
@@ -31,7 +31,7 @@ protected Capability getCapability() {
     protected void addProbeInfo(@NotNull IMultiblockController capability, @NotNull IProbeInfo probeInfo,
                                 @NotNull EntityPlayer player, @NotNull TileEntity tileEntity,
                                 @NotNull IProbeHitData data) {
-        if (capability.isStructureFormed("MAIN")) {
+        if (capability.isStructureFormed()) {
             probeInfo.text(TextStyleClass.OK + "{*gregtech.top.valid_structure*}");
             if (capability.isStructureObstructed()) {
                 probeInfo.text(TextFormatting.RED + "{*gregtech.top.obstructed_structure*}");

From 7d57405ff82cd2b5156840a23ba5169100c0a11f Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Thu, 26 Dec 2024 17:38:15 -0800
Subject: [PATCH 56/64] not beating the n squared allegations

---
 .../multiblock/MultiblockControllerBase.java      | 15 +++------------
 1 file changed, 3 insertions(+), 12 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 0443397a07f..85dc9f0edc0 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -596,25 +596,16 @@ public void invalidateStructure() {
 
     public void invalidateStructure(String name) {
         if (!getSubstructure(name).getPatternState().isFormed()) return;
-        // i am sorry
-        Object[] added = { null };
-        List dummyList = new ArrayList<>() {
-
-            @Override
-            public boolean add(Object e) {
-                added[0] = e;
-                return true;
-            }
-        };
+        List dummyList = new ArrayList<>();
 
         multiblockParts.removeIf(part -> {
             if (name.equals(part.getSubstructureName())) {
                 if (part instanceof IMultiblockAbilityPart) {
                     // noinspection unchecked
                     IMultiblockAbilityPart ability = (IMultiblockAbilityPart) part;
-                    added[0] = null;
+                    dummyList.clear();
                     ability.registerAbilities(dummyList);
-                    if (added[0] != null) multiblockAbilities.get(ability.getAbility()).remove(added[0]);
+                    multiblockAbilities.get(ability.getAbility()).removeIf(dummyList::contains);
                 }
                 part.removeFromMultiBlock(this);
                 return true;

From 0be978cc475f104771c478bf3078442cabfc75f9 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Thu, 26 Dec 2024 17:39:02 -0800
Subject: [PATCH 57/64] vrej

---
 .../java/gregtech/api/pattern/pattern/BasicAisleStrategy.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
index 2564ad56dd1..1b8c7624705 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
@@ -14,7 +14,7 @@
 import java.util.*;
 
 /**
- * The default aisle strategy, supporting repeatable (multi) aisles.
+ * The default aisle strategy, supporting repeatable (multi) aisles. Multi aisles may lead to cache issues though.
  */
 public class BasicAisleStrategy extends AisleStrategy {
 

From fc6acde980132acf34458d0d81cdca6e2108fee3 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Thu, 26 Dec 2024 17:39:28 -0800
Subject: [PATCH 58/64] :moyai:

---
 .../api/metatileentity/multiblock/MultiblockControllerBase.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 85dc9f0edc0..ac55a49056a 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -626,7 +626,7 @@ protected void invalidStructureCaches() {
     public IBlockPattern getSubstructure(String name) {
         return structures.get(name);
     }
-    
+
     public IBlockPattern getSubstructure() {
         return getSubstructure("MAIN");
     }

From b2eebd8232a467e06273e2e23dabf7ddf211ef34 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Thu, 26 Dec 2024 18:04:19 -0800
Subject: [PATCH 59/64] lowercase

---
 .../api/capability/IMultiblockController.java    |  2 +-
 .../multiblock/IMultiblockPart.java              |  4 ++--
 .../multiblock/MultiblockControllerBase.java     | 16 ++++++++--------
 .../handler/MultiblockPreviewRenderer.java       |  2 +-
 src/main/java/gregtech/common/EventHandlers.java |  2 +-
 .../MetaTileEntityActiveTransformer.java         |  2 +-
 .../MetaTileEntityLargeChemicalReactor.java      |  2 +-
 .../electric/MetaTileEntityPowerSubstation.java  |  2 +-
 .../electric/MetaTileEntityResearchStation.java  |  4 ++--
 .../MetaTileEntityCentralMonitor.java            |  2 +-
 .../MetaTileEntityMultiblockPart.java            |  2 +-
 .../MetaTileEntityCharcoalPileIgniter.java       |  2 +-
 12 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/src/main/java/gregtech/api/capability/IMultiblockController.java b/src/main/java/gregtech/api/capability/IMultiblockController.java
index 73e867d427f..a32ee04d804 100644
--- a/src/main/java/gregtech/api/capability/IMultiblockController.java
+++ b/src/main/java/gregtech/api/capability/IMultiblockController.java
@@ -5,7 +5,7 @@ public interface IMultiblockController {
     boolean isStructureFormed(String name);
 
     default boolean isStructureFormed() {
-        return isStructureFormed("MAIN");
+        return isStructureFormed("main");
     }
 
     default boolean isStructureObstructed() {
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
index 68cc7693c7d..d5bfa7babc6 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
@@ -8,7 +8,7 @@ public interface IMultiblockPart {
     boolean isAttachedToMultiBlock();
 
     default void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        addToMultiBlock(controllerBase, "MAIN");
+        addToMultiBlock(controllerBase, "main");
     }
 
     void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String substructureName);
@@ -29,7 +29,7 @@ default void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
     boolean canPartShare(MultiblockControllerBase target, String substructureName);
 
     default boolean canPartShare(MultiblockControllerBase target) {
-        return canPartShare(target, "MAIN");
+        return canPartShare(target, "main");
     }
 
     default boolean canPartShare() {
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index ac55a49056a..88cffb3f273 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -144,7 +144,7 @@ public void update() {
     protected abstract IBlockPattern createStructurePattern();
 
     protected void createStructurePatterns() {
-        structures.put("MAIN", createStructurePattern());
+        structures.put("main", createStructurePattern());
     }
 
     public EnumFacing getUpwardsFacing() {
@@ -444,7 +444,7 @@ public void checkStructurePatterns() {
     }
 
     public void checkStructurePattern() {
-        checkStructurePattern("MAIN");
+        checkStructurePattern("main");
     }
 
     public void checkStructurePattern(String name) {
@@ -587,11 +587,11 @@ protected void formStructure(String name) {
     }
 
     protected void formStructure() {
-        formStructure("MAIN");
+        formStructure("main");
     }
 
     public void invalidateStructure() {
-        invalidateStructure("MAIN");
+        invalidateStructure("main");
     }
 
     public void invalidateStructure(String name) {
@@ -628,12 +628,12 @@ public IBlockPattern getSubstructure(String name) {
     }
 
     public IBlockPattern getSubstructure() {
-        return getSubstructure("MAIN");
+        return getSubstructure("main");
     }
 
     public String trySubstructure(String name) {
         if (structures.get(name) != null) return name;
-        return "MAIN";
+        return "main";
     }
 
     public Set trySubstructure(Map map) {
@@ -642,7 +642,7 @@ public Set trySubstructure(Map map) {
         for (String key : map.keySet()) {
             if (key.startsWith("substructure")) set.add(trySubstructure(map.get(key)));
         }
-        if (set.isEmpty()) set.add("MAIN");
+        if (set.isEmpty()) set.add("main");
         return set;
     }
 
@@ -650,7 +650,7 @@ public Set trySubstructure(Map map) {
     public void onRemoval() {
         super.onRemoval();
         if (!getWorld().isRemote) {
-            invalidateStructure("MAIN");
+            invalidateStructure("main");
         }
     }
 
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index 0adad4f9755..376f804d3ab 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -126,7 +126,7 @@ public static void renderControllerInList(MultiblockControllerBase src, Map
     public void addDisplayText(List textList) {
         super.addDisplayText(textList);
         textList.add(new TextComponentString("Multi aisle repeats: " +
-                ((BasicAisleStrategy) ((BlockPattern) structures.get("MAIN")).getAisleStrategy())
+                ((BasicAisleStrategy) ((BlockPattern) structures.get("main")).getAisleStrategy())
                         .getMultiAisleRepeats(1)));
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index 89ef8257355..d21bba850b2 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -115,7 +115,7 @@ protected void formStructure(String name) {
 
         if (parts.isEmpty()) {
             // only empty batteries found in the structure
-            invalidateStructure("MAIN");
+            invalidateStructure("main");
             return;
         }
         if (this.energyBank == null) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
index f6180243331..f8aeb9af427 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
@@ -76,7 +76,7 @@ protected void formStructure(String name) {
 
         // should never happen, but would rather do this than have an obscure NPE
         if (computationProvider == null || objectHolder == null) {
-            invalidateStructure("MAIN");
+            invalidateStructure("main");
         }
     }
 
@@ -85,7 +85,7 @@ protected void formStructure(String name) {
     public void checkStructurePattern() {
         super.checkStructurePattern();
         if (isStructureFormed() && objectHolder.getFrontFacing() != getFrontFacing().getOpposite()) {
-            invalidateStructure("MAIN");
+            invalidateStructure("main");
         }
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index 973b45c351f..f38718c6c46 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -522,7 +522,7 @@ public void renderMetaTileEntity(double x, double y, double z, float partialTick
                         TileEntity tileEntity = getWorld().getTileEntity(pos);
                         if (tileEntity instanceof IGregTechTileEntity && ((IGregTechTileEntity) tileEntity)
                                 .getMetaTileEntity() instanceof MetaTileEntityMonitorScreen screen) {
-                            screen.addToMultiBlock(this, "MAIN");
+                            screen.addToMultiBlock(this, "main");
                             int sx = screen.getX(), sy = screen.getY();
                             if (sx < 0 || sx >= width || sy < 0 || sy >= height) {
                                 parts.clear();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
index 7f916dc3edf..05bbb66c466 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
@@ -282,7 +282,7 @@ public void onRemoval() {
 
         List controllers = getControllers();
         for (int i = 0; i < controllers.size(); i++) {
-            controllers.get(controllers.size() - 1).invalidateStructure("MAIN");
+            controllers.get(controllers.size() - 1).invalidateStructure("main");
         }
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index eda1a7a194f..89e7a017e3b 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -134,7 +134,7 @@ public void invalidateStructure(String name) {
     protected void formStructure(String name) {
         super.formStructure(name);
         // this doesn't iterate over any(), so doesn't count the borders
-        forEachFormed("MAIN", (info, pos) -> {
+        forEachFormed("main", (info, pos) -> {
             BlockPos immutable = pos.immutable();
 
             if (info.getBlockState().getBlock().isWood(getWorld(), immutable)) {

From f0798f1ae6818ee0620b5f6869f85f4f1cad56d8 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Thu, 26 Dec 2024 18:58:16 -0800
Subject: [PATCH 60/64] constant + oops

---
 .../api/capability/IMultiblockController.java   |  4 +++-
 .../multiblock/IMultiblockPart.java             |  4 ++--
 .../multiblock/MultiblockControllerBase.java    | 17 +++++++++--------
 .../handler/MultiblockPreviewRenderer.java      |  2 +-
 .../java/gregtech/common/EventHandlers.java     |  2 +-
 .../MetaTileEntityActiveTransformer.java        |  2 +-
 .../MetaTileEntityLargeChemicalReactor.java     |  2 +-
 .../electric/MetaTileEntityPowerSubstation.java |  2 +-
 .../electric/MetaTileEntityResearchStation.java |  4 ++--
 .../MetaTileEntityCentralMonitor.java           |  2 +-
 .../MetaTileEntityMultiblockPart.java           |  2 +-
 .../MetaTileEntityCharcoalPileIgniter.java      |  2 +-
 12 files changed, 24 insertions(+), 21 deletions(-)

diff --git a/src/main/java/gregtech/api/capability/IMultiblockController.java b/src/main/java/gregtech/api/capability/IMultiblockController.java
index a32ee04d804..56b49fef760 100644
--- a/src/main/java/gregtech/api/capability/IMultiblockController.java
+++ b/src/main/java/gregtech/api/capability/IMultiblockController.java
@@ -1,11 +1,13 @@
 package gregtech.api.capability;
 
+import gregtech.api.metatileentity.multiblock.MultiblockControllerBase;
+
 public interface IMultiblockController {
 
     boolean isStructureFormed(String name);
 
     default boolean isStructureFormed() {
-        return isStructureFormed("main");
+        return isStructureFormed(MultiblockControllerBase.DEFAULT_STRUCTURE);
     }
 
     default boolean isStructureObstructed() {
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
index d5bfa7babc6..d4050228ab6 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java
@@ -8,7 +8,7 @@ public interface IMultiblockPart {
     boolean isAttachedToMultiBlock();
 
     default void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
-        addToMultiBlock(controllerBase, "main");
+        addToMultiBlock(controllerBase, MultiblockControllerBase.DEFAULT_STRUCTURE);
     }
 
     void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String substructureName);
@@ -29,7 +29,7 @@ default void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) {
     boolean canPartShare(MultiblockControllerBase target, String substructureName);
 
     default boolean canPartShare(MultiblockControllerBase target) {
-        return canPartShare(target, "main");
+        return canPartShare(target, MultiblockControllerBase.DEFAULT_STRUCTURE);
     }
 
     default boolean canPartShare() {
diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 88cffb3f273..2e1d65cda9b 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -90,6 +90,7 @@
 import static gregtech.api.capability.GregtechDataCodes.*;
 
 public abstract class MultiblockControllerBase extends MetaTileEntity implements IMultiblockController {
+    public static final String DEFAULT_STRUCTURE = "main";
 
     protected final Comparator partComparator = Comparator.comparingLong(part -> {
         MetaTileEntity mte = (MetaTileEntity) part;
@@ -144,7 +145,7 @@ public void update() {
     protected abstract IBlockPattern createStructurePattern();
 
     protected void createStructurePatterns() {
-        structures.put("main", createStructurePattern());
+        structures.put(DEFAULT_STRUCTURE, createStructurePattern());
     }
 
     public EnumFacing getUpwardsFacing() {
@@ -444,7 +445,7 @@ public void checkStructurePatterns() {
     }
 
     public void checkStructurePattern() {
-        checkStructurePattern("main");
+        checkStructurePattern(DEFAULT_STRUCTURE);
     }
 
     public void checkStructurePattern(String name) {
@@ -587,11 +588,11 @@ protected void formStructure(String name) {
     }
 
     protected void formStructure() {
-        formStructure("main");
+        formStructure(DEFAULT_STRUCTURE);
     }
 
     public void invalidateStructure() {
-        invalidateStructure("main");
+        invalidateStructure(DEFAULT_STRUCTURE);
     }
 
     public void invalidateStructure(String name) {
@@ -628,12 +629,12 @@ public IBlockPattern getSubstructure(String name) {
     }
 
     public IBlockPattern getSubstructure() {
-        return getSubstructure("main");
+        return getSubstructure(DEFAULT_STRUCTURE);
     }
 
     public String trySubstructure(String name) {
         if (structures.get(name) != null) return name;
-        return "main";
+        return DEFAULT_STRUCTURE;
     }
 
     public Set trySubstructure(Map map) {
@@ -642,7 +643,7 @@ public Set trySubstructure(Map map) {
         for (String key : map.keySet()) {
             if (key.startsWith("substructure")) set.add(trySubstructure(map.get(key)));
         }
-        if (set.isEmpty()) set.add("main");
+        if (set.isEmpty()) set.add(DEFAULT_STRUCTURE);
         return set;
     }
 
@@ -650,7 +651,7 @@ public Set trySubstructure(Map map) {
     public void onRemoval() {
         super.onRemoval();
         if (!getWorld().isRemote) {
-            invalidateStructure("main");
+            invalidateStructure(DEFAULT_STRUCTURE);
         }
     }
 
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index 376f804d3ab..abcdac4169e 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -126,7 +126,7 @@ public static void renderControllerInList(MultiblockControllerBase src, Map
     public void addDisplayText(List textList) {
         super.addDisplayText(textList);
         textList.add(new TextComponentString("Multi aisle repeats: " +
-                ((BasicAisleStrategy) ((BlockPattern) structures.get("main")).getAisleStrategy())
+                ((BasicAisleStrategy) ((BlockPattern) structures.get(DEFAULT_STRUCTURE)).getAisleStrategy())
                         .getMultiAisleRepeats(1)));
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
index d21bba850b2..d477dcd0036 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java
@@ -115,7 +115,7 @@ protected void formStructure(String name) {
 
         if (parts.isEmpty()) {
             // only empty batteries found in the structure
-            invalidateStructure("main");
+            invalidateStructure();
             return;
         }
         if (this.energyBank == null) {
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
index f8aeb9af427..a0d8da0dbbb 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityResearchStation.java
@@ -76,7 +76,7 @@ protected void formStructure(String name) {
 
         // should never happen, but would rather do this than have an obscure NPE
         if (computationProvider == null || objectHolder == null) {
-            invalidateStructure("main");
+            invalidateStructure();
         }
     }
 
@@ -85,7 +85,7 @@ protected void formStructure(String name) {
     public void checkStructurePattern() {
         super.checkStructurePattern();
         if (isStructureFormed() && objectHolder.getFrontFacing() != getFrontFacing().getOpposite()) {
-            invalidateStructure("main");
+            invalidateStructure();
         }
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
index f38718c6c46..21dff65e0a0 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/centralmonitor/MetaTileEntityCentralMonitor.java
@@ -522,7 +522,7 @@ public void renderMetaTileEntity(double x, double y, double z, float partialTick
                         TileEntity tileEntity = getWorld().getTileEntity(pos);
                         if (tileEntity instanceof IGregTechTileEntity && ((IGregTechTileEntity) tileEntity)
                                 .getMetaTileEntity() instanceof MetaTileEntityMonitorScreen screen) {
-                            screen.addToMultiBlock(this, "main");
+                            screen.addToMultiBlock(this);
                             int sx = screen.getX(), sy = screen.getY();
                             if (sx < 0 || sx >= width || sy < 0 || sy >= height) {
                                 parts.clear();
diff --git a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
index 05bbb66c466..3639c9ed525 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockPart.java
@@ -282,7 +282,7 @@ public void onRemoval() {
 
         List controllers = getControllers();
         for (int i = 0; i < controllers.size(); i++) {
-            controllers.get(controllers.size() - 1).invalidateStructure("main");
+            controllers.get(i).invalidateStructure();
         }
     }
 
diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
index 89e7a017e3b..8b5b06a7772 100644
--- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
+++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java
@@ -134,7 +134,7 @@ public void invalidateStructure(String name) {
     protected void formStructure(String name) {
         super.formStructure(name);
         // this doesn't iterate over any(), so doesn't count the borders
-        forEachFormed("main", (info, pos) -> {
+        forEachFormed(DEFAULT_STRUCTURE, (info, pos) -> {
             BlockPos immutable = pos.immutable();
 
             if (info.getBlockState().getBlock().isWood(getWorld(), immutable)) {

From a58923f1f8e24d792a928094e83e936132d98acd Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Thu, 26 Dec 2024 18:58:39 -0800
Subject: [PATCH 61/64] how it feels to spotless

---
 .../metatileentity/multiblock/MultiblockControllerBase.java    | 1 +
 .../client/renderer/handler/MultiblockPreviewRenderer.java     | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 2e1d65cda9b..70f672dc184 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -90,6 +90,7 @@
 import static gregtech.api.capability.GregtechDataCodes.*;
 
 public abstract class MultiblockControllerBase extends MetaTileEntity implements IMultiblockController {
+
     public static final String DEFAULT_STRUCTURE = "main";
 
     protected final Comparator partComparator = Comparator.comparingLong(part -> {
diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
index abcdac4169e..24e11bac47f 100644
--- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
+++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java
@@ -126,7 +126,8 @@ public static void renderControllerInList(MultiblockControllerBase src, Map
Date: Thu, 26 Dec 2024 19:02:36 -0800
Subject: [PATCH 62/64] the great unboxing

---
 .../metatileentity/multiblock/MultiblockControllerBase.java  | 5 +++--
 src/main/java/gregtech/api/util/RelativeDirection.java       | 4 ++--
 .../multi/electric/MetaTileEntityAssemblyLine.java           | 4 ++--
 .../multi/electric/MetaTileEntityDistillationTower.java      | 4 ++--
 4 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index 70f672dc184..da22135fa2a 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -86,6 +86,7 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
+import java.util.function.ToIntFunction;
 
 import static gregtech.api.capability.GregtechDataCodes.*;
 
@@ -95,7 +96,7 @@ public abstract class MultiblockControllerBase extends MetaTileEntity implements
 
     protected final Comparator partComparator = Comparator.comparingLong(part -> {
         MetaTileEntity mte = (MetaTileEntity) part;
-        return ((long) multiblockPartSorter().apply(mte.getPos()) << 32) | mte.getPos().hashCode();
+        return ((long) multiblockPartSorter().applyAsInt(mte.getPos()) << 32) | mte.getPos().hashCode();
     });
 
     private final Map, List> multiblockAbilities = new HashMap<>();
@@ -435,7 +436,7 @@ public boolean shouldShowInJei() {
      * Used if MultiblockPart Abilities need to be sorted a certain way, like
      * Distillation Tower and Assembly Line.
      */
-    protected Function multiblockPartSorter() {
+    protected ToIntFunction multiblockPartSorter() {
         return BlockPos::hashCode;
     }
 
diff --git a/src/main/java/gregtech/api/util/RelativeDirection.java b/src/main/java/gregtech/api/util/RelativeDirection.java
index 0b8020bd7cd..ca12b25f2ec 100644
--- a/src/main/java/gregtech/api/util/RelativeDirection.java
+++ b/src/main/java/gregtech/api/util/RelativeDirection.java
@@ -6,7 +6,7 @@
 import net.minecraft.util.math.BlockPos;
 
 import java.util.function.BinaryOperator;
-import java.util.function.Function;
+import java.util.function.ToIntFunction;
 
 /**
  * Relative direction when facing horizontally
@@ -58,7 +58,7 @@ public EnumFacing getRelativeFacing(EnumFacing frontFacing, EnumFacing upwardsFa
                 getRelativeFacing(frontFacing, upwardsFacing);
     }
 
-    public Function getSorter(EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped) {
+    public ToIntFunction getSorter(EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped) {
         // get the direction to go in for the part sorter
         EnumFacing sorterDirection = getRelativeFacing(frontFacing, upwardsFacing, isFlipped);
 
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
index 0bb09b2620f..aac31f963c2 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java
@@ -52,7 +52,7 @@
 import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
-import java.util.function.Function;
+import java.util.function.ToIntFunction;
 
 import static gregtech.api.util.RelativeDirection.*;
 
@@ -141,7 +141,7 @@ protected static TraceabilityPredicate dataHatchPredicate() {
     }
 
     @Override
-    protected Function multiblockPartSorter() {
+    protected ToIntFunction multiblockPartSorter() {
         // player's right when looking at the controller, but the controller's left
         return RelativeDirection.LEFT.getSorter(getFrontFacing(), getUpwardsFacing(), isFlipped());
     }
diff --git a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
index f9005cde40c..9206cb34a2f 100644
--- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
+++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityDistillationTower.java
@@ -36,7 +36,7 @@
 import org.jetbrains.annotations.NotNull;
 
 import java.util.List;
-import java.util.function.Function;
+import java.util.function.ToIntFunction;
 
 import static gregtech.api.util.RelativeDirection.*;
 
@@ -70,7 +70,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) {
      * a properly overriden {@link DistillationTowerLogicHandler#determineOrderedFluidOutputs()}
      */
     @Override
-    protected Function multiblockPartSorter() {
+    protected ToIntFunction multiblockPartSorter() {
         return RelativeDirection.UP.getSorter(getFrontFacing(), getUpwardsFacing(), isFlipped());
     }
 

From 14ae70ef489ad319dae4ebe1353476ce4553137a Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Fri, 27 Dec 2024 16:33:36 -0800
Subject: [PATCH 63/64] caught

---
 .../metatileentity/multiblock/MultiblockControllerBase.java   | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index da22135fa2a..c865e61fcb4 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -733,10 +733,6 @@ public  T getCapability(Capability capability, EnumFacing side) {
         return null;
     }
 
-    public boolean isStructureFormed() {
-        return isStructureFormed();
-    }
-
     public boolean isStructureFormed(String name) {
         return getWorld() != null && getSubstructure(name) != null &&
                 getSubstructure(name).getPatternState().isFormed();

From c8ba33f75cd271d1cac0247a8a9893f49cdf78f9 Mon Sep 17 00:00:00 2001
From: vrejhead <113960896+vrejhead@users.noreply.github.com>
Date: Wed, 1 Jan 2025 14:45:23 -0800
Subject: [PATCH 64/64] stuff

---
 .../multiblock/MultiblockControllerBase.java  | 37 ++-----------------
 .../gregtech/api/pattern/GreggyBlockPos.java  |  7 ++--
 .../api/pattern/MultiblockShapeInfo.java      |  1 +
 .../gregtech/api/pattern/PatternError.java    |  4 ++
 .../api/pattern/TraceabilityPredicate.java    | 16 ++------
 .../pattern/pattern/BasicAisleStrategy.java   |  2 -
 .../api/pattern/pattern/BlockPattern.java     |  6 +--
 .../behaviors/MultiblockBuilderBehavior.java  |  2 -
 8 files changed, 16 insertions(+), 59 deletions(-)

diff --git a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
index c865e61fcb4..a58f037d1f0 100644
--- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
+++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java
@@ -56,7 +56,7 @@
 import codechicken.lib.render.pipeline.IVertexOperation;
 import codechicken.lib.vec.Matrix4;
 import codechicken.lib.vec.Rotation;
-import com.google.common.collect.AbstractIterator;
+import com.google.common.collect.Iterators;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
@@ -834,19 +834,7 @@ public List getMatchingShapes() {
      */
     @NotNull
     public Iterator> getPreviewBuilds() {
-        return new AbstractIterator<>() {
-
-            private boolean used;
-
-            @Override
-            protected Map computeNext() {
-                if (!used) {
-                    used = true;
-                    return Collections.emptyMap();
-                }
-                return endOfData();
-            }
-        };
+        return Iterators.singletonIterator(Collections.emptyMap());
     }
 
     /**
@@ -882,10 +870,8 @@ public void autoBuild(EntityPlayer player, Map map,
 
         BiPredicate place = (l, info) -> {
             BlockPos pos = BlockPos.fromLong(l);
-
             // don't stop build if its air
             if (!getWorld().isAirBlock(pos)) return true;
-
             if (info.getTileEntity() instanceof MetaTileEntityHolder holder) {
                 ItemStack removed = hasAndRemoveItem(player, holder.getMetaTileEntity().getStackForm());
                 if (removed.isEmpty()) return false;
@@ -896,7 +882,6 @@ public void autoBuild(EntityPlayer player, Map map,
                 newHolder.getMetaTileEntity().setFrontFacing(holder.getMetaTileEntity().getFrontFacing());
                 if (removed.hasTagCompound())
                     newHolder.getMetaTileEntity().initFromItemStackData(removed.getTagCompound());
-
                 if (predicates.containsKey(pos.offset(newHolder.getMetaTileEntity().getFrontFacing()).toLong())) {
                     EnumFacing valid = null;
                     for (EnumFacing facing : EnumFacing.HORIZONTALS) {
@@ -914,7 +899,6 @@ public void autoBuild(EntityPlayer player, Map map,
                         }
                     }
                 }
-
                 getWorld().setBlockState(pos, holder.getMetaTileEntity().getBlock().getDefaultState());
                 getWorld().setTileEntity(pos, newHolder);
             } else {
@@ -922,18 +906,15 @@ public void autoBuild(EntityPlayer player, Map map,
                     getWorld().setBlockState(pos, info.getBlockState());
                 else return false;
             }
-
             return true;
         };
 
         for (Long2ObjectMap.Entry entry : predicates.long2ObjectEntrySet()) {
             TraceabilityPredicate pred = entry.getValue();
             if (simpleIndex.getInt(pred) >= pred.simple.size()) continue;
-
             int pointer = simpleIndex.getInt(pred);
             TraceabilityPredicate.SimplePredicate simple = pred.simple.get(pointer);
             int count = globalCache.getInt(simple);
-
             try {
                 while ((simple.previewCount == -1 || count == simple.previewCount) &&
                         (simple.minGlobalCount == -1 || count == simple.minGlobalCount)) {
@@ -946,28 +927,19 @@ public void autoBuild(EntityPlayer player, Map map,
             } catch (IndexOutOfBoundsException e) {
                 continue;
             }
-
             globalCache.put(simple, globalCache.getInt(simple) + 1);
-
             if (simple.candidates == null) continue;
-
             TraceabilityPredicate.SimplePredicate finalSimple = simple;
             cache.computeIfAbsent(simple, k -> finalSimple.candidates.apply(map)[0]);
-
             if (!place.test(entry.getLongKey(), cache.get(simple))) return;
-
             entry.setValue(null);
         }
-
         simpleIndex.clear();
-
         for (Long2ObjectMap.Entry entry : predicates.long2ObjectEntrySet()) {
             TraceabilityPredicate pred = entry.getValue();
             if (pred == null || simpleIndex.getInt(pred) >= pred.simple.size()) continue;
-
             TraceabilityPredicate.SimplePredicate simple = pred.simple.get(simpleIndex.getInt(pred));
             int count = globalCache.getInt(simple);
-
             while (count == simple.previewCount || count == simple.maxGlobalCount) {
                 // if the current predicate is used, move until the next free one
                 int newIndex = simpleIndex.put(pred, simpleIndex.getInt(pred) + 1) + 1;
@@ -980,19 +952,16 @@ public void autoBuild(EntityPlayer player, Map map,
                 count = globalCache.getInt(simple);
             }
             globalCache.put(simple, globalCache.getInt(simple) + 1);
-
             if (simple.candidates == null) continue;
-
             TraceabilityPredicate.SimplePredicate finalSimple = simple;
             cache.computeIfAbsent(simple, k -> finalSimple.candidates.apply(map)[0]);
-
             if (!place.test(entry.getLongKey(), cache.get(simple))) return;
         }
     }
 
     /**
      * Called right before the autobuild code starts, modify the map like if you want it to be "height"
-     * instead of "multi.1.0"
+     * instead of "multi.1.0". The passed in map may be immutable and may be the same one passed in for multiple builds.
      */
     protected void modifyAutoBuild(Map map) {}
 
diff --git a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
index a3214fe0bf2..f310c97c940 100644
--- a/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
+++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java
@@ -2,7 +2,6 @@
 
 import net.minecraft.util.EnumFacing;
 import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.math.MathHelper;
 
 import com.google.common.collect.AbstractIterator;
 import org.jetbrains.annotations.NotNull;
@@ -17,7 +16,7 @@
  */
 public class GreggyBlockPos {
 
-    public static final int NUM_X_BITS = 1 + MathHelper.log2(MathHelper.smallestEncompassingPowerOfTwo(30000000));
+    public static final int NUM_X_BITS = 1 + 32 - Integer.numberOfLeadingZeros(30_000_000 - 1);
     public static final int NUM_Z_BITS = NUM_X_BITS, NUM_Y_BITS = 64 - 2 * NUM_X_BITS;
     public static final int Y_SHIFT = NUM_Z_BITS;
     public static final int X_SHIFT = Y_SHIFT + NUM_Y_BITS;
@@ -156,7 +155,6 @@ public GreggyBlockPos offset(EnumFacing facing) {
 
     /**
      * Serializes this pos to long, this should be identical to {@link BlockPos}.
-     * But the blockpos impl is so bad, who let them cook???
      * 
      * @return Long rep
      */
@@ -284,7 +282,8 @@ public GreggyBlockPos copy() {
 
     @Override
     public int hashCode() {
-        return Arrays.hashCode(pos);
+        // should be identical to blockpos
+        return (pos[1] + pos[2] * 31) * 31 + pos[0];
     }
 
     @Override
diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
index fe3b1f4e797..65f25603ebd 100644
--- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
+++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java
@@ -32,6 +32,7 @@
 import java.util.Map;
 import java.util.function.Supplier;
 
+@Deprecated(forRemoval = true) // this shall be removed soontm, this class has no use atm
 public class MultiblockShapeInfo {
 
     /**
diff --git a/src/main/java/gregtech/api/pattern/PatternError.java b/src/main/java/gregtech/api/pattern/PatternError.java
index b5ee500ac4c..b81b06ff42a 100644
--- a/src/main/java/gregtech/api/pattern/PatternError.java
+++ b/src/main/java/gregtech/api/pattern/PatternError.java
@@ -30,6 +30,10 @@ public PatternError(BlockPos pos, TraceabilityPredicate failingPredicate) {
         this(pos, failingPredicate.getCandidates());
     }
 
+    public PatternError(BlockPos pos, TraceabilityPredicate.SimplePredicate failingPredicate) {
+        this(pos, Collections.singletonList(failingPredicate.getCandidates()));
+    }
+
     @Nullable
     public BlockPos getPos() {
         return pos;
diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
index 3c390bbc77c..ac38f388530 100644
--- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
+++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java
@@ -102,11 +102,9 @@ public TraceabilityPredicate addTooltips(String... tips) {
      * @return A list containing lists which group together candidates
      */
     public List> getCandidates() {
-        List> candidates = new ArrayList<>();
-        for (TraceabilityPredicate.SimplePredicate pred : simple) {
-            candidates.add(pred.getCandidates());
-        }
-        return candidates;
+        return simple.stream()
+                .map(SimplePredicate::getCandidates)
+                .collect(Collectors.toList());
     }
 
     /**
@@ -284,23 +282,17 @@ public PatternError testLimited(BlockWorldState worldState,
         public PatternError testGlobal(BlockWorldState worldState, Object2IntMap global,
                                        Object2IntMap layer) {
             PatternError result = predicate.apply(worldState);
-
             if (!global.containsKey(this)) global.put(this, 0);
             if ((minGlobalCount == -1 && maxGlobalCount == -1) || result != null || layer == null) return result;
-
             int count = layer.put(this, layer.getInt(this) + 1) + 1 + global.getInt(this);
             if (maxGlobalCount == -1 || count <= maxGlobalCount) return null;
-
             return new SinglePredicateError(this, 0);
         }
 
         public PatternError testLayer(BlockWorldState worldState, Object2IntMap cache) {
             PatternError result = predicate.apply(worldState);
-
             if ((minLayerCount == -1 && maxLayerCount == -1) || result != null) return result;
-
             if (maxLayerCount == -1 || cache.getInt(this) <= maxLayerCount) return null;
-
             return new SinglePredicateError(this, 2);
         }
 
@@ -318,7 +310,7 @@ public static class SinglePredicateError extends PatternError {
         public final int type, number;
 
         public SinglePredicateError(SimplePredicate failingPredicate, int type) {
-            super(null, Collections.singletonList(failingPredicate.getCandidates()));
+            super(null, failingPredicate);
             this.type = type;
 
             int number = -1;
diff --git a/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
index 1b8c7624705..c044d17c80b 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java
@@ -71,7 +71,6 @@ protected int checkRepeatAisle(int index, int offset, boolean flip) {
         return aisles.get(index).actualRepeats = aisle.maxRepeats;
     }
 
-    // todo more lang support(yay!)
     @Override
     public int @NotNull [] getDefaultAisles(Map map) {
         IntList list = new IntArrayList();
@@ -135,7 +134,6 @@ protected void multiAisleError() {
         throw new IllegalStateException("Illegal multiAisles, check logs above.");
     }
 
-    // resisting the urge to make this a generic type return to allow for inheritors,,,,,,
     public BasicAisleStrategy multiAisle(int min, int max, int from, int to) {
         Preconditions.checkArgument(max >= min, "max: %s is less than min: %s", max, min);
         Preconditions.checkArgument(from >= 0, "from argument is negative: %s", from);
diff --git a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
index 351d435b3c0..c5972cf5725 100644
--- a/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
+++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java
@@ -85,15 +85,12 @@ public BlockPattern(@NotNull PatternAisle @NotNull [] aisles,
      * @param center The center char to look for
      */
     private void legacyStartOffset(char center) {
-        // don't do anything if center char isn't specified, this allows
-        // MultiblockControllerBase#validateStructurePatterns to do its thing while not logging an error here
+        // don't do anything if center char isn't specified
         if (center == 0) return;
-        // could also use aisles.length but this is cooler
         for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) {
             int[] result = aisles[aisleI].firstInstanceOf(center);
             if (result != null) {
                 // structure starts at aisle 0, string 0, char 0, think about it
-                // so relative to the controller we need to offset by this to get to the start
                 moveOffset(directions[0], -aisleI);
                 moveOffset(directions[1], -result[0]);
                 moveOffset(directions[2], -result[1]);
@@ -211,7 +208,6 @@ public boolean checkPatternAt(World world, BlockPos centerPos, EnumFacing frontF
      */
     public boolean checkAisle(GreggyBlockPos controllerPos, EnumFacing frontFacing, EnumFacing upFacing, int aisleIndex,
                               int aisleOffset, boolean flip) {
-        // todo use a temporary cache to not double count sometimes
         // absolute facings from the relative facings
         EnumFacing absoluteAisle = directions[0].getRelativeFacing(frontFacing, upFacing, flip);
         EnumFacing absoluteString = directions[1].getRelativeFacing(frontFacing, upFacing, flip);
diff --git a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
index 3ec73cbb82c..30c62f0f7dc 100644
--- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
+++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java
@@ -10,7 +10,6 @@
 import gregtech.api.mui.factory.MetaItemGuiFactory;
 import gregtech.api.pattern.PatternError;
 import gregtech.api.util.GTUtility;
-import gregtech.client.renderer.handler.BlockPosHighlightRenderer;
 
 import net.minecraft.client.resources.I18n;
 import net.minecraft.entity.player.EntityPlayer;
@@ -193,7 +192,6 @@ public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPo
                     player.sendMessage(
                             new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header"));
                     player.sendMessage(new TextComponentString(error.getErrorInfo()));
-                    if (error.getPos() != null) BlockPosHighlightRenderer.renderBlockBoxHighLight(error.getPos(), 5000);
                     return EnumActionResult.SUCCESS;
                 }
             }