diff --git a/src/main/java/gregtech/api/capability/GregtechDataCodes.java b/src/main/java/gregtech/api/capability/GregtechDataCodes.java index 6054957ac0a..95ce9744f36 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(); @@ -87,7 +88,8 @@ public static int assignId() { public static final int IS_FRONT_FACE_FREE = assignId(); public static final int MAINTENANCE_MULTIPLIER = assignId(); public static final int UPDATE_UPWARDS_FACING = assignId(); - public static final int UPDATE_FLIP = assignId(); + // todo replacement for this + public static final int UPDATE_MATRIX = assignId(); // Item Bus Item Stack Auto Collapsing public static final int TOGGLE_COLLAPSE_ITEMS = assignId(); 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/capability/IMultiblockController.java b/src/main/java/gregtech/api/capability/IMultiblockController.java index 34a3272420b..56b49fef760 100644 --- a/src/main/java/gregtech/api/capability/IMultiblockController.java +++ b/src/main/java/gregtech/api/capability/IMultiblockController.java @@ -1,8 +1,14 @@ package gregtech.api.capability; +import gregtech.api.metatileentity.multiblock.MultiblockControllerBase; + public interface IMultiblockController { - boolean isStructureFormed(); + boolean isStructureFormed(String name); + + default boolean isStructureFormed() { + return isStructureFormed(MultiblockControllerBase.DEFAULT_STRUCTURE); + } default boolean isStructureObstructed() { return false; diff --git a/src/main/java/gregtech/api/capability/impl/DistillationTowerLogicHandler.java b/src/main/java/gregtech/api/capability/impl/DistillationTowerLogicHandler.java index e674db66763..5c1a1fdb4af 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; @@ -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/IMultiblockPart.java b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java index 520a36dcebb..d4050228ab6 100644 --- a/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java +++ b/src/main/java/gregtech/api/metatileentity/multiblock/IMultiblockPart.java @@ -1,12 +1,36 @@ package gregtech.api.metatileentity.multiblock; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + public interface IMultiblockPart { boolean isAttachedToMultiBlock(); - void addToMultiBlock(MultiblockControllerBase controllerBase); + default void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase) { + addToMultiBlock(controllerBase, MultiblockControllerBase.DEFAULT_STRUCTURE); + } + + void addToMultiBlock(@NotNull MultiblockControllerBase controllerBase, @NotNull String substructureName); + + void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase); - void removeFromMultiBlock(MultiblockControllerBase controllerBase); + /** + * Gets how many multiblocks are currently using the part. + */ + int getWallshareCount(); + + /** + * 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); + + default boolean canPartShare(MultiblockControllerBase target) { + return canPartShare(target, MultiblockControllerBase.DEFAULT_STRUCTURE); + } 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 ff3848b1557..a69b58a2e85 100644 --- a/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java +++ b/src/main/java/gregtech/api/metatileentity/multiblock/MultiblockControllerBase.java @@ -5,20 +5,25 @@ 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; -import gregtech.api.pattern.BlockPattern; import gregtech.api.pattern.BlockWorldState; +import gregtech.api.pattern.GreggyBlockPos; +import gregtech.api.pattern.MatrixPair; import gregtech.api.pattern.MultiblockShapeInfo; -import gregtech.api.pattern.PatternMatchContext; +import gregtech.api.pattern.PatternError; +import gregtech.api.pattern.PatternStringError; import gregtech.api.pattern.TraceabilityPredicate; +import gregtech.api.pattern.pattern.IBlockPattern; +import gregtech.api.pattern.pattern.PatternState; import gregtech.api.pipenet.tile.IPipeTile; import gregtech.api.unification.material.Material; import gregtech.api.util.BlockInfo; import gregtech.api.util.GTLog; import gregtech.api.util.GTUtility; -import gregtech.api.util.RelativeDirection; +import gregtech.api.util.GregFakePlayer; import gregtech.api.util.world.DummyWorld; import gregtech.client.renderer.ICubeRenderer; import gregtech.client.renderer.handler.MultiblockPreviewRenderer; @@ -40,6 +45,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; @@ -51,11 +57,16 @@ import codechicken.lib.render.pipeline.IVertexOperation; import codechicken.lib.vec.Matrix4; import codechicken.lib.vec.Rotation; +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; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.tuple.Pair; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.lwjgl.util.vector.Matrix4f; import java.util.ArrayList; import java.util.Arrays; @@ -63,29 +74,38 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; +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.Stack; -import java.util.function.BiFunction; +import java.util.TreeSet; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; 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.*; public abstract class MultiblockControllerBase extends MetaTileEntity implements IMultiblockController { - @Nullable - public BlockPattern structurePattern; + public static final String DEFAULT_STRUCTURE = "main"; + + protected final Comparator partComparator = Comparator.comparingLong(part -> { + MetaTileEntity mte = (MetaTileEntity) part; + return ((long) multiblockPartSorter().applyAsInt(mte.getPos()) << 32) | mte.getPos().hashCode(); + }); private final Map, List> multiblockAbilities = new HashMap<>(); - private final List multiblockParts = new ArrayList<>(); - private boolean structureFormed; - protected EnumFacing upwardsFacing = EnumFacing.NORTH; - protected boolean isFlipped; + private final NavigableSet multiblockParts = new TreeSet<>(partComparator); + + protected EnumFacing upwardsFacing = EnumFacing.UP; + protected final Map structures = new HashMap<>(); + protected final MatrixPair mat = new MatrixPair(); public MultiblockControllerBase(ResourceLocation metaTileEntityId) { super(metaTileEntityId); @@ -98,7 +118,7 @@ public void onPlacement(EntityLivingBase placer) { } public void reinitializeStructurePattern() { - this.structurePattern = createStructurePattern(); + createStructurePatterns(); } @Override @@ -106,7 +126,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 @@ -125,7 +145,11 @@ public void update() { * @return structure pattern of this multiblock */ @NotNull - protected abstract BlockPattern createStructurePattern(); + protected abstract IBlockPattern createStructurePattern(); + + protected void createStructurePatterns() { + structures.put(DEFAULT_STRUCTURE, createStructurePattern()); + } public EnumFacing getUpwardsFacing() { return upwardsFacing; @@ -133,7 +157,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; } @@ -143,39 +167,37 @@ public void setUpwardsFacing(EnumFacing upwardsFacing) { notifyBlockUpdate(); markDirty(); writeCustomData(UPDATE_UPWARDS_FACING, buf -> buf.writeByte(upwardsFacing.getIndex())); - if (structurePattern != null) { - structurePattern.clearCache(); - checkStructurePattern(); + for (IBlockPattern pattern : structures.values()) { + pattern.clearCache(); } + checkStructurePatterns(); } } } public boolean isFlipped() { - return isFlipped; - } - - /** Should not be called outside of structure formation logic! */ - @ApiStatus.Internal - protected void setFlipped(boolean isFlipped) { - if (this.isFlipped != isFlipped) { - this.isFlipped = isFlipped; - notifyBlockUpdate(); - markDirty(); - writeCustomData(UPDATE_FLIP, buf -> buf.writeBoolean(isFlipped)); - } + return (boolean) getSubstructure().getState().storage.getOrDefault("flip", false); } @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; } /** * Override this method to change the Controller overlay - * + * n + * * @return The overlay to render on the Multiblock Controller */ @SideOnly(Side.CLIENT) @@ -189,22 +211,13 @@ public TextureAtlasSprite getFrontDefaultTexture() { return getFrontOverlay().getParticleSprite(); } - public static TraceabilityPredicate tilePredicate(@NotNull BiFunction predicate, - @Nullable Supplier candidates) { - return new TraceabilityPredicate(blockWorldState -> { - TileEntity tileEntity = blockWorldState.getTileEntity(); - if (!(tileEntity instanceof IGregTechTileEntity)) - return false; + public static TraceabilityPredicate tilePredicate(@NotNull BiPredicate predicate, + @Nullable Function, BlockInfo[]> candidates) { + return new TraceabilityPredicate(worldState -> { + TileEntity tileEntity = worldState.getTileEntity(); + if (!(tileEntity instanceof IGregTechTileEntity)) return PatternError.PLACEHOLDER; MetaTileEntity metaTileEntity = ((IGregTechTileEntity) tileEntity).getMetaTileEntity(); - if (predicate.apply(blockWorldState, metaTileEntity)) { - if (metaTileEntity instanceof IMultiblockPart) { - Set partsFound = blockWorldState.getMatchContext().getOrCreate("MultiblockParts", - HashSet::new); - partsFound.add((IMultiblockPart) metaTileEntity); - } - return true; - } - return false; + return predicate.test(worldState, metaTileEntity) ? null : PatternError.PLACEHOLDER; }, candidates); } @@ -212,40 +225,78 @@ 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 < + 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 Supplier getCandidates(MetaTileEntity... metaTileEntities) { - return () -> Arrays.stream(metaTileEntities).filter(Objects::nonNull).map(tile -> { - // TODO + 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); } - private static Supplier getCandidates(IBlockState... allowedStates) { - return () -> Arrays.stream(allowedStates).map(state -> new BlockInfo(state, null)).toArray(BlockInfo[]::new); + // generic hell + @SafeVarargs + 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); } - public static TraceabilityPredicate abilities(MultiblockAbility... allowedAbilities) { + 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 Arrays.stream(allowedStates).map(BlockInfo::new).toArray(BlockInfo[]::new); + }; + } + + 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 new TraceabilityPredicate(blockWorldState -> { - IBlockState state = blockWorldState.getBlockState(); - if (state.getBlock() instanceof VariantActiveBlock) { - blockWorldState.getMatchContext().getOrPut("VABlock", new LinkedList<>()).add(blockWorldState.getPos()); - } - return ArrayUtils.contains(allowedStates, state); - }, getCandidates(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(key, allowedStates)); } /** @@ -254,20 +305,25 @@ 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(); - if (!(tileEntity instanceof IPipeTile)) { - return false; + .or(new TraceabilityPredicate(worldState -> { + TileEntity tileEntity = worldState.getTileEntity(); + if (!(tileEntity instanceof IPipeTilepipeTile)) { + return PatternError.PLACEHOLDER; } - IPipeTile pipeTile = (IPipeTile) tileEntity; - return ArrayUtils.contains(frameMaterials, pipeTile.getFrameMaterial()); + return ArrayUtils.contains(frameMaterials, pipeTile.getFrameMaterial()) ? null : + PatternError.PLACEHOLDER; })); } public static TraceabilityPredicate blocks(Block... block) { + return blocks(null, block); + } + + public static TraceabilityPredicate blocks(String key, Block... block) { return new TraceabilityPredicate( - blockWorldState -> ArrayUtils.contains(block, blockWorldState.getBlockState().getBlock()), - getCandidates(Arrays.stream(block).map(Block::getDefaultState).toArray(IBlockState[]::new))); + worldState -> ArrayUtils.contains(block, worldState.getBlockState().getBlock()) ? null : + PatternError.PLACEHOLDER, + getCandidates(key, Arrays.stream(block).map(Block::getDefaultState).toArray(IBlockState[]::new))); } public static TraceabilityPredicate air() { @@ -282,6 +338,38 @@ 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(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())} + * + * @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}. + */ + public static V allSameType(Object2ObjectMap info, IBlockPattern pattern, String error) { + V type = null; + 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 { + pattern.getState() + .setError(new PatternStringError(BlockPos.fromLong(entry.getLongKey()), error)); + return null; + } + } + } + } + + return type; + } + public TraceabilityPredicate selfPredicate() { return metaTileEntities(this).setCenter(); } @@ -298,16 +386,23 @@ public void renderMetaTileEntity(CCRenderState renderState, Matrix4 translation, } 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 (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; } - 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); } } @@ -329,68 +424,250 @@ 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; } + public void checkStructurePatterns() { + for (String name : structures.keySet()) { + checkStructurePattern(name); + } + } + public void checkStructurePattern() { - if (structurePattern == null) return; - PatternMatchContext context = structurePattern.checkPatternFastAt(getWorld(), getPos(), - getFrontFacing().getOpposite(), getUpwardsFacing(), allowsFlip()); - if (context != null && !structureFormed) { - Set rawPartsSet = context.getOrCreate("MultiblockParts", HashSet::new); - ArrayList parts = new ArrayList<>(rawPartsSet); - for (IMultiblockPart part : parts) { - if (part.isAttachedToMultiBlock()) { - if (!part.canPartShare()) { - return; + checkStructurePattern(DEFAULT_STRUCTURE); + } + + public void checkStructurePattern(String name) { + IBlockPattern pattern = getSubstructure(name); + if (!pattern.getState().shouldUpdate || getWorld() == null) return; + + long time = System.nanoTime(); + PatternState result = pattern.cachedPattern(getWorld()); + if (result == null) { + pattern.getState().setState(checkUncachedPattern(pattern) ? + PatternState.EnumCheckState.VALID_UNCACHED : PatternState.EnumCheckState.INVALID); + result = pattern.getState(); + } + GTLog.logger.info( + "structure check for " + getClass().getSimpleName() + " took " + (System.nanoTime() - time) + " nanos"); + + if (result.getState().isValid()) { + if (result.formed) { + // fast rebuild parts + if (result.getState() == PatternState.EnumCheckState.VALID_UNCACHED) { + forEachMultiblockPart(name, part -> { + if (multiblockParts.contains(part)) return true; + + 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 + addedParts.add((IMultiblockAbilityPart) abilityPart); + } + return true; + }); + + // another bandaid fix + addedParts.sort(partComparator); + + for (IMultiblockAbilityPart part : addedParts) { + registerMultiblockAbility(part); } + + formStructure(name); } + return; } - this.setFlipped(context.neededFlip()); - 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); + + boolean[] valid = new boolean[1]; + valid[0] = true; + + forEachMultiblockPart(name, part -> { + if (part.isAttachedToMultiBlock() && !part.canPartShare(this, name)) { + valid[0] = false; + return false; + } + return true; + }); + + // since the structure isn't formed, don't invalidate, instead just don't form it + if (!valid[0]) return; + + forEachMultiblockPart(name, part -> { + // parts *should* not have this controller added + multiblockParts.add(part); + part.addToMultiBlock(this, name); + return true; + }); + + // maybe bandaid fix + for (IMultiblockPart part : multiblockParts) { + if (name.equals(part.getSubstructureName()) && part instanceof IMultiblockAbilityPartabilityPart) { + // noinspection unchecked + registerMultiblockAbility((IMultiblockAbilityPart) abilityPart); } } - this.multiblockParts.addAll(parts); - this.multiblockAbilities.putAll(abilities); - parts.forEach(part -> part.addToMultiBlock(this)); - this.structureFormed = true; - writeCustomData(STRUCTURE_FORMED, buf -> buf.writeBoolean(true)); - formStructure(context); - } else if (context == null && structureFormed) { - 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()); + + formStructure(name); + } else { + if (result.formed) { + invalidateStructure(name); + } + } + } + + /** + * Checks to see if an invalid pattern is valid, though calling + * {@link IBlockPattern#checkPatternAt(World, Matrix4f)} + * with different matricies. + */ + protected boolean checkUncachedPattern(IBlockPattern pattern) { + mat.identity(); + + defaultTranslate(); + defaultRotate(); + if (pattern.checkPatternAt(getWorld(), mat.mat)) { + pattern.getState().storage.put("flip", false); + return true; + } + + mat.identity(); + defaultTranslate(); + defaultReflect(); + defaultRotate(); + pattern.getState().storage.put("flip", true); + return pattern.checkPatternAt(getWorld(), mat.mat); + } + + /** + * 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 iterates + * 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)) continue; + MetaTileEntity mte = gtte.getMetaTileEntity(); + if (mte instanceof IMultiblockPart part) { + if (!action.test(part)) return; } } } - protected void formStructure(PatternMatchContext context) {} + 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 registerMultiblockAbility(IMultiblockAbilityPart part) { + List abilityList = multiblockAbilities.computeIfAbsent(part.getAbility(), k -> new ArrayList<>()); + part.registerAbilities(abilityList); + } + + protected void forEachFormed(String name, BiConsumer action) { + Long2ObjectMap cache = getSubstructure(name).getCache(); + GreggyBlockPos pos = new GreggyBlockPos(); + for (Long2ObjectMap.Entry entry : cache.long2ObjectEntrySet()) { + action.accept(entry.getValue(), pos.fromLong(entry.getLongKey())); + } + } + + protected void formStructure(String name) { + getSubstructure(name).getState().formed = true; + writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString(name).writeBoolean(true)); + } + + protected void formStructure() { + formStructure(DEFAULT_STRUCTURE); + } public void invalidateStructure() { - this.multiblockParts.forEach(part -> part.removeFromMultiBlock(this)); - this.multiblockAbilities.clear(); - this.multiblockParts.clear(); - this.structureFormed = false; - this.setFlipped(false); - writeCustomData(STRUCTURE_FORMED, buf -> buf.writeBoolean(false)); + invalidateStructure(DEFAULT_STRUCTURE); + } + + public void invalidateStructure(String name) { + if (!getSubstructure(name).getState().formed) return; + List dummyList = new ArrayList<>(); + + multiblockParts.removeIf(part -> { + if (name.equals(part.getSubstructureName())) { + if (part instanceof IMultiblockAbilityPart) { + // noinspection unchecked + IMultiblockAbilityPart ability = (IMultiblockAbilityPart) part; + dummyList.clear(); + ability.registerAbilities(dummyList); + multiblockAbilities.get(ability.getAbility()).removeIf(dummyList::contains); + } + part.removeFromMultiBlock(this); + return true; + } + return false; + }); + + getSubstructure(name).getState().formed = false; + writeCustomData(STRUCTURE_FORMED, buf -> buf.writeString(name).writeBoolean(false)); + } + + protected void invalidStructureCaches() { + for (IBlockPattern pattern : structures.values()) { + pattern.clearCache(); + } + } + + public IBlockPattern getSubstructure(String name) { + return structures.get(name); + } + + public IBlockPattern getSubstructure() { + return getSubstructure(DEFAULT_STRUCTURE); + } + + public String trySubstructure(String name) { + if (structures.get(name) != null) return name; + return DEFAULT_STRUCTURE; + } + + 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(DEFAULT_STRUCTURE); + return set; } @Override public void onRemoval() { super.onRemoval(); - if (!getWorld().isRemote && structureFormed) { - invalidateStructure(); + if (!getWorld().isRemote) { + invalidateStructure(DEFAULT_STRUCTURE); } } @@ -400,8 +677,8 @@ public List getAbilities(MultiblockAbility ability) { return Collections.unmodifiableList(rawList); } - public List getMultiblockParts() { - return Collections.unmodifiableList(multiblockParts); + public NavigableSet getMultiblockParts() { + return Collections.unmodifiableNavigableSet(multiblockParts); } @Override @@ -409,9 +686,17 @@ public void readFromNBT(NBTTagCompound data) { super.readFromNBT(data); if (data.hasKey("UpwardsFacing")) { this.upwardsFacing = EnumFacing.VALUES[data.getByte("UpwardsFacing")]; - } - if (data.hasKey("IsFlipped")) { - this.isFlipped = data.getBoolean("IsFlipped"); + // 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(); } @@ -419,40 +704,38 @@ public void readFromNBT(NBTTagCompound data) { @Override public NBTTagCompound writeToNBT(NBTTagCompound data) { super.writeToNBT(data); - data.setByte("UpwardsFacing", (byte) upwardsFacing.getIndex()); - data.setBoolean("IsFlipped", isFlipped); + data.setByte("UpFacing", (byte) upwardsFacing.getIndex()); return data; } @Override public void writeInitialSyncData(PacketBuffer buf) { super.writeInitialSyncData(buf); - buf.writeBoolean(structureFormed); buf.writeByte(upwardsFacing.getIndex()); - buf.writeBoolean(isFlipped); } @Override public void receiveInitialSyncData(PacketBuffer buf) { super.receiveInitialSyncData(buf); - this.structureFormed = buf.readBoolean(); this.upwardsFacing = EnumFacing.VALUES[buf.readByte()]; - this.isFlipped = buf.readBoolean(); } @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) { + String name = buf.readString(Short.MAX_VALUE); + getSubstructure(name).getState().formed = buf.readBoolean(); + + if (!isStructureFormed()) { + GregTechAPI.soundManager.stopTileSound(getPos()); + } + } else if (dataId == UPDATE_MATRIX) { + MatrixPair.deserialize(mat.mat, buf); + Matrix4f.invert(mat.mat, mat.inv); } } @@ -467,8 +750,9 @@ public T getCapability(Capability capability, EnumFacing side) { return null; } - public boolean isStructureFormed() { - return structureFormed; + public boolean isStructureFormed(String name) { + return getWorld() != null && getSubstructure(name) != null && + getSubstructure(name).getState().formed; } @Override @@ -478,18 +762,18 @@ public void setFrontFacing(EnumFacing frontFacing) { // Set the upwards facing in a way that makes it "look like" the upwards facing wasn't changed if (allowsExtendedFacing()) { - EnumFacing newUpwardsFacing = RelativeDirection.simulateAxisRotation(frontFacing, oldFrontFacing, + EnumFacing newUpwardsFacing = GTUtility.simulateAxisRotation(frontFacing, oldFrontFacing, getUpwardsFacing()); 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(); + checkStructurePatterns(); } } @@ -516,7 +800,7 @@ public boolean onRightClick(EntityPlayer playerIn, EnumHand hand, EnumFacing fac if (this.getWorld().isRemote && !this.isStructureFormed() && playerIn.isSneaking() && playerIn.getHeldItem(hand).isEmpty()) { - MultiblockPreviewRenderer.renderMultiBlockPreview(this, 60000); + MultiblockPreviewRenderer.renderMultiBlockPreview(this, playerIn, 60000); return true; } return false; @@ -527,7 +811,12 @@ 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 rot = upwardsFacing.rotateAround(getFrontFacing().getAxis()); + if (frontFacing.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE ^ playerIn.isSneaking()) { + rot = rot.getOpposite(); + } + + setUpwardsFacing(rot); } return true; } @@ -551,33 +840,171 @@ public boolean allowsFlip() { } public List getMatchingShapes() { - if (this.structurePattern == null) { - this.reinitializeStructurePattern(); - if (this.structurePattern == null) { - return Collections.emptyList(); + return Collections.emptyList(); + } + + /** + * 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. + */ + @NotNull + public Iterator> getPreviewBuilds() { + return Iterators.singletonIterator(Collections.emptyMap()); + } + + /** + * 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, String substructure) { + if (getWorld().isRemote) throw new IllegalArgumentException("client side call wuh"); + + IBlockPattern structure = getSubstructure(trySubstructure(substructure)); + + mat.identity(); + defaultTranslate(); + if ("true".equals(map.get("flip"))) defaultReflect(); + defaultRotate(); + + Long2ObjectMap predicates = structure.getDefaultShape(mat.mat, map); + if (predicates == null) return; + + autoBuild(player, map, predicates); + } + + /** + * 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, + 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 + 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 + if (!getWorld().isAirBlock(pos)) return true; + if (info.getTileEntity() instanceof MetaTileEntityHolder holder) { + ItemStack removed = hasAndRemoveItem(player, holder.getMetaTileEntity().getStackForm()); + if (removed.isEmpty()) return false; + + 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()); + 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); + } else { + if (!hasAndRemoveItem(player, GTUtility.toItem(info.getBlockState())).isEmpty()) + 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)) { + // 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; + 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; + 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.apply(map)[0]); + if (!place.test(entry.getLongKey(), cache.get(simple))) return; } - int[][] aisleRepetitions = this.structurePattern.aisleRepetitions; - 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(); + /** + * Called right before the autobuild code starts, modify the map like if you want it to be "height" + * 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) {} + + /** + * @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 ItemStack hasAndRemoveItem(EntityPlayer player, ItemStack stack) { + if (stack.isEmpty()) return ItemStack.EMPTY; + if (player.isCreative() || player instanceof GregFakePlayer) return stack.copy(); + + for (ItemStack ztack : player.inventory.mainInventory) { + if (!ztack.isEmpty() && ztack.isItemEqual(stack)) { + ztack.setCount(ztack.getCount() - 1); + return ztack.copy(); } } - return pages; + + return ItemStack.EMPTY; } @SideOnly(Side.CLIENT) @@ -592,8 +1019,7 @@ public int getDefaultPaintingColor() { } public void explodeMultiblock(float explosionPower) { - List parts = new ArrayList<>(getMultiblockParts()); - for (IMultiblockPart part : parts) { + for (IMultiblockPart part : multiblockParts) { part.removeFromMultiBlock(this); ((MetaTileEntity) part).doExplosion(explosionPower); } @@ -607,4 +1033,37 @@ public void explodeMultiblock(float explosionPower) { public boolean isMultiblockPartWeatherResistant(@NotNull IMultiblockPart part) { return false; } + + public void defaultTranslate() { + mat.translate(getPos().getX(), getPos().getY(), getPos().getZ()); + } + + public void defaultRotate() { + EnumFacing start; + EnumFacing goal; + if (frontFacing == EnumFacing.UP) { + start = EnumFacing.SOUTH; + goal = upwardsFacing; + } else if (frontFacing == EnumFacing.DOWN) { + start = EnumFacing.NORTH; + goal = upwardsFacing; + } else { + start = EnumFacing.NORTH; + goal = frontFacing; + } + + for (; start != goal; start = start.rotateYCCW()) mat.rotate((float) Math.PI / 2, 0, 1, 0); + + if (frontFacing.getAxis() == EnumFacing.Axis.Y) { + mat.rotate((float) Math.PI / 2, frontFacing.getYOffset(), 0, 0); + } else { + for (EnumFacing up = EnumFacing.UP; up != upwardsFacing; up = up.rotateAround(frontFacing.getAxis())) + mat.rotate((float) Math.PI / 2, 0, 0, 1); + } + } + + public void defaultReflect() { + EnumFacing side = GTUtility.cross(frontFacing, upwardsFacing); + mat.reflect(side.getXOffset(), side.getYOffset(), side.getZOffset(), 0); + } } 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/BlockPattern.java b/src/main/java/gregtech/api/pattern/BlockPattern.java deleted file mode 100644 index 8479368a3db..00000000000 --- a/src/main/java/gregtech/api/pattern/BlockPattern.java +++ /dev/null @@ -1,630 +0,0 @@ -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 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.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import org.apache.commons.lang3.ArrayUtils; -import org.jetbrains.annotations.NotNull; - -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -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 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; - - if (this.thumbLength > 0) { - this.palmLength = predicatesIn[0][0].length; - } else { - this.palmLength = 0; - } - } else { - this.thumbLength = 0; - this.palmLength = 0; - } - - initializeCenterOffsets(); - } - - 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; - } - } - } - } - if (centerOffset == null) { - throw new IllegalArgumentException("Didn't find center predicate"); - } - } - - public PatternError getError() { - return worldState.error; - } - - public PatternMatchContext checkPatternFastAt(World world, BlockPos centerPos, EnumFacing frontFacing, - EnumFacing upwardsFacing, boolean allowsFlip) { - if (!cache.isEmpty()) { - boolean pass = true; - for (Map.Entry entry : cache.entrySet()) { - BlockPos pos = BlockPos.fromLong(entry.getKey()); - 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 worldState.hasError() ? null : matchContext; - } - - // First try normal pattern, and if it fails, try flipped (if allowed). - PatternMatchContext pmc = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, false); - if (allowsFlip) { - if (pmc != null) { - return pmc; - } - pmc = checkPatternAt(world, centerPos, frontFacing, upwardsFacing, true); - } - if (pmc == null) clearCache(); // we don't want a random cache of a partially formed multi - return pmc; - } - - public void clearCache() { - cache.clear(); - } - - private PatternMatchContext 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 = RelativeDirection.setActualRelativeOffset(x, y, z, frontFacing, upwardsFacing, - isFlipped, structureDir) - .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; - } - - // 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; - } - } - - worldState.setError(null); - matchContext.setNeededFlip(isFlipped); - return matchContext; - } - - 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 = RelativeDirection.setActualRelativeOffset(x, y, z, facing, - controllerBase.getUpwardsFacing(), - controllerBase.isFlipped(), structureDir) - .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++; - } - } - 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 = RelativeDirection.setActualRelativeOffset(z, y, x, EnumFacing.NORTH, - EnumFacing.UP, false, structureDir); - // 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; - } -} diff --git a/src/main/java/gregtech/api/pattern/BlockWorldState.java b/src/main/java/gregtech/api/pattern/BlockWorldState.java index 74dfa1c4526..2f4fb431c08 100644 --- a/src/main/java/gregtech/api/pattern/BlockWorldState.java +++ b/src/main/java/gregtech/api/pattern/BlockWorldState.java @@ -2,15 +2,14 @@ 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. + */ public class BlockWorldState { protected World world; @@ -18,41 +17,27 @@ public class BlockWorldState { protected IBlockState state; 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) { + 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; - this.matchContext = matchContext; - this.globalCount = globalCount; - this.layerCount = layerCount; - this.predicate = predicate; - this.error = null; - } - - public boolean hasError() { - return error != null; } - public void setError(PatternError error) { - this.error = error; - if (error != null) { - error.setWorldState(this); - } + public void setPos(GreggyBlockPos pos) { + this.pos = pos.immutable(); + this.state = null; + this.tileEntity = null; + this.tileEntityInitialized = false; } - public PatternMatchContext getMatchContext() { - return matchContext; + public void setPos(BlockPos pos) { + this.pos = pos.toImmutable(); + this.state = null; + this.tileEntity = null; + this.tileEntityInitialized = false; } public IBlockState getBlockState() { @@ -77,17 +62,11 @@ 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; } + + public void setWorld(World world) { + this.world = world; + } } diff --git a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java deleted file mode 100644 index 7b2e272f991..00000000000 --- a/src/main/java/gregtech/api/pattern/FactoryBlockPattern.java +++ /dev/null @@ -1,172 +0,0 @@ -package gregtech.api.pattern; - -import gregtech.api.util.RelativeDirection; - -import com.google.common.base.Joiner; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; - -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.*; - -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; - private final RelativeDirection[] structureDir = new RelativeDirection[3]; - - private FactoryBlockPattern(RelativeDirection charDir, RelativeDirection stringDir, RelativeDirection aisleDir) { - structureDir[0] = charDir; - structureDir[1] = stringDir; - structureDir[2] = aisleDir; - int flags = 0; - for (int i = 0; i < this.structureDir.length; 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!"); - this.symbolMap.put(' ', TraceabilityPredicate.ANY); - } - - /** - * Adds a repeatable aisle to this pattern. - */ - 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(); - } - - 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() + ")"); - } - - for (char c0 : s.toCharArray()) { - if (!this.symbolMap.containsKey(c0)) { - this.symbolMap.put(c0, null); - } - } - } - - 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; - } - } else { - throw new IllegalArgumentException("Empty pattern for aisle"); - } - } - - /** - * 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 - */ - 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 }); - return this; - } - - /** - * Set last aisle repeatable - */ - public FactoryBlockPattern setRepeatable(int repeatCount) { - return setRepeatable(repeatCount, repeatCount); - } - - public static FactoryBlockPattern start() { - return new FactoryBlockPattern(RIGHT, UP, BACK); - } - - public static FactoryBlockPattern start(RelativeDirection charDir, RelativeDirection stringDir, - RelativeDirection aisleDir) { - return new FactoryBlockPattern(charDir, stringDir, aisleDir); - } - - public FactoryBlockPattern where(char symbol, TraceabilityPredicate blockMatcher) { - this.symbolMap.put(symbol, new TraceabilityPredicate(blockMatcher).sort()); - 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)); - } - - 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; - } - - private void checkMissingPredicates() { - List list = new ArrayList<>(); - - for (Entry entry : this.symbolMap.entrySet()) { - if (entry.getValue() == null) { - list.add(entry.getKey()); - } - } - - if (!list.isEmpty()) { - throw new IllegalStateException("Predicates for character(s) " + COMMA_JOIN.join(list) + " are missing"); - } - } -} 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..f310c97c940 --- /dev/null +++ b/src/main/java/gregtech/api/pattern/GreggyBlockPos.java @@ -0,0 +1,431 @@ +package gregtech.api.pattern; + +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; + +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, + * finding the one method that returns a new instance will be left as an exercise to the reader. + */ +public class GreggyBlockPos { + + 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; + 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); + } + + 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() }; + } + + /** + * 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 + * + * @param axis The axis to set + * @param value The value of said coordinate + */ + public GreggyBlockPos set(EnumFacing.Axis axis, int value) { + return set(axis.ordinal(), value); + } + + /** + * 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) { + return set(axis.ordinal(), other.pos[axis.ordinal()]); + } + + /** + * 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 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 + * + * @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 GreggyBlockPos setAxisRelative(EnumFacing.Axis a1, EnumFacing.Axis a2, int p1, int p2, int p3) { + set(a1, p1); + set(a2, p2); + pos[3 - a1.ordinal() - a2.ordinal()] = p3; + return this; + } + + /** + * Offsets in the given {@link EnumFacing} amount times {@link BlockPos#offset(EnumFacing)} + */ + 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'sposition 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; + } + + /** + * BlockPos verison 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 + */ + public GreggyBlockPos offset(EnumFacing facing) { + return offset(facing, 1); + } + + /** + * Serializes this pos to long, this should be identical to {@link BlockPos}. + * + * @return Long rep + */ + public long toLong() { + 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; + } + + /** + * 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; + } + + /** + * 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. + */ + public boolean origin() { + return equals(BlockPos.ORIGIN); + } + + /** + * @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()]; + } + + /** + * 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. + */ + public int[] getAll() { + return Arrays.copyOf(pos, 3); + } + + public GreggyBlockPos copy() { + return new GreggyBlockPos().from(this); + } + + @Override + public int hashCode() { + // should be identical to blockpos + return (pos[1] + pos[2] * 31) * 31 + pos[0]; + } + + @Override + 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 == 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; + } + + /** + * 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). + */ + public static > void validateFacingsArray(T[] facings) { + if (facings.length != 3) throw new IllegalArgumentException("Facings must be array of length 3!"); + + int x = 0; + for (int i = 0; i < 3; i++) { + x |= 1 << (facings[i].ordinal() / 2); + } + + if (x != 7) 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()); + int mult = facings[i].getAxisDirection().getOffset(); + + start.set(facings[i].getAxis(), Math.min(a * mult, b * mult) * mult); + } + + return start; + } + + /** + * 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) { + validateFacingsArray(facings); + + // same code as startPos but it has the length thing + 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()); + int mult = facings[i].getAxisDirection().getOffset(); + + start.set(facings[i].getAxis(), Math.min(a * mult, b * mult) * mult); + + 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]); + } + }; + } + }; + } + + /** + * BlockPos version of {@link GreggyBlockPos#get(EnumFacing.Axis)}. + */ + 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/MatrixPair.java b/src/main/java/gregtech/api/pattern/MatrixPair.java new file mode 100644 index 00000000000..d2bacd1e238 --- /dev/null +++ b/src/main/java/gregtech/api/pattern/MatrixPair.java @@ -0,0 +1,211 @@ +package gregtech.api.pattern; + +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; + +import org.lwjgl.util.vector.Matrix4f; + +public class MatrixPair { + public final Matrix4f mat = new Matrix4f(), inv = new Matrix4f(); + public final GreggyBlockPos dummy = new GreggyBlockPos(); + + public MatrixPair translate(float x, float y, float z) { + translate(x, y, z, mat, mat); + translate(-x, -y, -z, inv, inv); + return this; + } + + /** + * Counterclockwise when looking negative direction from vector into origin. + */ + public MatrixPair rotate(float a, float x, float y, float z) { + rotate(a, x, y, z, mat, mat); + rotate(-a, x, y, z, inv, inv); + return this; + } + + /** + * Reflect across ax + by + cz + d = 0 + */ + public MatrixPair reflect(float a, float b, float c, float d) { + reflect(a, b, c, d, mat, mat); + reflect(a, b, c, d, inv, inv); + return this; + } + + public MatrixPair identity() { + mat.setIdentity(); + inv.setIdentity(); + return this; + } + + public GreggyBlockPos apply(GreggyBlockPos o) { + return apply(mat, o); + } + + public GreggyBlockPos unapply(GreggyBlockPos o) { + return apply(inv, o); + } + + public GreggyBlockPos apply(BlockPos o) { + return apply(mat, dummy.from(o)); + } + + public GreggyBlockPos unapply(BlockPos o) { + return apply(inv, dummy.from(o)); + } + + public static GreggyBlockPos apply(Matrix4f src, GreggyBlockPos dest) { + float x = dest.x() * src.m00 + dest.y() * src.m10 + dest.z() * src.m20 + src.m30; + float y = dest.x() * src.m01 + dest.y() * src.m11 + dest.z() * src.m21 + src.m31; + float z = dest.x() * src.m02 + dest.y() * src.m12 + dest.z() * src.m22 + src.m32; + dest.x(MathHelper.floor(x + 0.5f)); + dest.y(MathHelper.floor(y + 0.5f)); + dest.z(MathHelper.floor(z + 0.5f)); + return dest; + } + + public static void serialize(Matrix4f src, PacketBuffer buf) { + buf.writeFloat(src.m00) + .writeFloat(src.m01) + .writeFloat(src.m02) + .writeFloat(src.m03) + .writeFloat(src.m10) + .writeFloat(src.m11) + .writeFloat(src.m12) + .writeFloat(src.m13) + .writeFloat(src.m20) + .writeFloat(src.m21) + .writeFloat(src.m22) + .writeFloat(src.m23) + .writeFloat(src.m30) + .writeFloat(src.m31) + .writeFloat(src.m32) + .writeFloat(src.m33); + } + + public static void deserialize(Matrix4f dest, PacketBuffer buf) { + dest.m00 = buf.readFloat(); + dest.m01 = buf.readFloat(); + dest.m02 = buf.readFloat(); + dest.m03 = buf.readFloat(); + dest.m10 = buf.readFloat(); + dest.m11 = buf.readFloat(); + dest.m12 = buf.readFloat(); + dest.m13 = buf.readFloat(); + dest.m20 = buf.readFloat(); + dest.m21 = buf.readFloat(); + dest.m22 = buf.readFloat(); + dest.m23 = buf.readFloat(); + dest.m30 = buf.readFloat(); + dest.m31 = buf.readFloat(); + dest.m32 = buf.readFloat(); + dest.m33 = buf.readFloat(); + } + + // all below stolen from JOML, shoutout to MIT + public static Matrix4f reflect(float a, float b, float c, float d, Matrix4f src, Matrix4f dest) { + float da = a + a, db = b + b, dc = c + c, dd = d + d; + float rm00 = 1.0f - da * a; + float rm01 = -da * b; + float rm02 = -da * c; + float rm10 = -db * a; + float rm11 = 1.0f - db * b; + float rm12 = -db * c; + float rm20 = -dc * a; + float rm21 = -dc * b; + float rm22 = 1.0f - dc * c; + float rm30 = -dd * a; + float rm31 = -dd * b; + float rm32 = -dd * c; + // matrix multiplication + dest.m30 = src.m00 * rm30 + src.m10 * rm31 + src.m20 * rm32 + src.m30; + dest.m31 = src.m01 * rm30 + src.m11 * rm31 + src.m21 * rm32 + src.m31; + dest.m32 = src.m02 * rm30 + src.m12 * rm31 + src.m22 * rm32 + src.m32; + dest.m33 = src.m03 * rm30 + src.m13 * rm31 + src.m23 * rm32 + src.m33; + float nm00 = src.m00 * rm00 + src.m10 * rm01 + src.m20 * rm02; + float nm01 = src.m01 * rm00 + src.m11 * rm01 + src.m21 * rm02; + float nm02 = src.m02 * rm00 + src.m12 * rm01 + src.m22 * rm02; + float nm03 = src.m03 * rm00 + src.m13 * rm01 + src.m23 * rm02; + float nm10 = src.m00 * rm10 + src.m10 * rm11 + src.m20 * rm12; + float nm11 = src.m01 * rm10 + src.m11 * rm11 + src.m21 * rm12; + float nm12 = src.m02 * rm10 + src.m12 * rm11 + src.m22 * rm12; + float nm13 = src.m03 * rm10 + src.m13 * rm11 + src.m23 * rm12; + dest.m20 = src.m00 * rm20 + src.m10 * rm21 + src.m20 * rm22; + dest.m21 = src.m01 * rm20 + src.m11 * rm21 + src.m21 * rm22; + dest.m22 = src.m02 * rm20 + src.m12 * rm21 + src.m22 * rm22; + dest.m23 = src.m03 * rm20 + src.m13 * rm21 + src.m23 * rm22; + dest.m00 = nm00; + dest.m01 = nm01; + dest.m02 = nm02; + dest.m03 = nm03; + dest.m10 = nm10; + dest.m11 = nm11; + dest.m12 = nm12; + dest.m13 = nm13; + return dest; + } + + public static Matrix4f reflect(float nx, float ny, float nz, float px, float py, float pz, Matrix4f src, + Matrix4f dest) { + float invLength = 1 / MathHelper.sqrt(nx * nx + ny * ny + nz * nz); + float nnx = nx * invLength; + float nny = ny * invLength; + float nnz = nz * invLength; + /* See: http://mathworld.wolfram.com/Plane.html */ + return reflect(nnx, nny, nnz, -nnx * px - nny * py - nnz * pz, src, dest); + } + + public static Matrix4f rotate(float ang, float x, float y, float z, Matrix4f src, Matrix4f dest) { + float s = MathHelper.sin(ang); + float c = MathHelper.cos(ang); + float C = 1.0f - c; + float xx = x * x, xy = x * y, xz = x * z; + float yy = y * y, yz = y * z; + float zz = z * z; + float rm00 = xx * C + c; + float rm01 = xy * C + z * s; + float rm02 = xz * C - y * s; + float rm10 = xy * C - z * s; + float rm11 = yy * C + c; + float rm12 = yz * C + x * s; + float rm20 = xz * C + y * s; + float rm21 = yz * C - x * s; + float rm22 = zz * C + c; + float nm00 = src.m00 * rm00 + src.m10 * rm01 + src.m20 * rm02; + float nm01 = src.m01 * rm00 + src.m11 * rm01 + src.m21 * rm02; + float nm02 = src.m02 * rm00 + src.m12 * rm01 + src.m22 * rm02; + float nm03 = src.m03 * rm00 + src.m13 * rm01 + src.m23 * rm02; + float nm10 = src.m00 * rm10 + src.m10 * rm11 + src.m20 * rm12; + float nm11 = src.m01 * rm10 + src.m11 * rm11 + src.m21 * rm12; + float nm12 = src.m02 * rm10 + src.m12 * rm11 + src.m22 * rm12; + float nm13 = src.m03 * rm10 + src.m13 * rm11 + src.m23 * rm12; + dest.m20 = src.m00 * rm20 + src.m10 * rm21 + src.m20 * rm22; + dest.m21 = src.m01 * rm20 + src.m11 * rm21 + src.m21 * rm22; + dest.m22 = src.m02 * rm20 + src.m12 * rm21 + src.m22 * rm22; + dest.m23 = src.m03 * rm20 + src.m13 * rm21 + src.m23 * rm22; + dest.m00 = nm00; + dest.m01 = nm01; + dest.m02 = nm02; + dest.m03 = nm03; + dest.m10 = nm10; + dest.m11 = nm11; + dest.m12 = nm12; + dest.m13 = nm13; + dest.m30 = src.m30; + dest.m31 = src.m31; + dest.m32 = src.m32; + dest.m33 = src.m33; + return dest; + } + + public static Matrix4f translate(float x, float y, float z, Matrix4f src, Matrix4f dest) { + dest.load(src); + dest.m30 = src.m00 * x + src.m10 * y + src.m20 * z + src.m30; + dest.m31 = src.m01 * x + src.m11 * y + src.m21 * z + src.m31; + dest.m32 = src.m02 * x + src.m12 * y + src.m22 * z + src.m32; + dest.m33 = src.m03 * x + src.m13 * y + src.m23 * z + src.m33; + return dest; + } +} diff --git a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java index f82b3dc8319..133b2f09ee9 100644 --- a/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java +++ b/src/main/java/gregtech/api/pattern/MultiblockShapeInfo.java @@ -2,90 +2,287 @@ 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 net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Blocks; 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 org.jetbrains.annotations.NotNull; +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 java.util.ArrayList; -import java.util.HashMap; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.Supplier; -import static gregtech.api.util.RelativeDirection.*; - +@Deprecated // this shall be removed soontm, this class has no use atm public class MultiblockShapeInfo { - /** {@code [x][y][z]} */ - private final BlockInfo[][][] blocks; + /** + * Default front facing for jei preview(and what patterns are most likely made for). + */ + public static final EnumFacing DEFAULT_FRONT = EnumFacing.SOUTH; - public MultiblockShapeInfo(BlockInfo[][][] blocks) { - this.blocks = blocks; - } + /** + * Default up facing for jei preview(and what patterns are most likely made for). + */ + public static final EnumFacing DEFAULT_UP = EnumFacing.UP; /** - * @return the blocks in an array of format: {@code [x][y][z]} + * 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 BlockInfo[][][] getBlocks() { - return blocks; + public static final Map FACING_MAP; + protected final PatternAisle[] aisles; + protected final Char2ObjectMap symbols; + protected final Char2ObjectMap dotMap; + 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 static Builder builder() { - return builder(RIGHT, DOWN, BACK); + 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); } - 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]); + /** + * 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. + */ + // 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, 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); + EnumFacing absoluteChar = directions[2].getRelativeFacing(frontFacing, upFacing, false); + + int aisleCount = aisles.length; + int stringCount = aisles[0].getStringCount(); + int charCount = aisles[0].getCharCount(); + + GreggyBlockPos pos = new GreggyBlockPos(); + + 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.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()); + mteHolder.getMetaTileEntity().onPlacement(); + + // 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 (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(); + } + mteBase.setUpwardsFacing(newUpFacing); + } + + map.put(pos.immutable(), + new BlockInfo(mteHolder.getMetaTileEntity().getBlock().getDefaultState(), mteHolder)); + } else { + map.put(pos.immutable(), symbols.get(c)); + } + } + } + } + + // 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()) { + 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 controller; } - public static class Builder { + /** + * 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; + } + } - private final RelativeDirection[] structureDir = new RelativeDirection[3]; + 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); + } + } + } - private List shape = new ArrayList<>(); - private Map symbolMap = new HashMap<>(); + // maybe throw instead? + return new GreggyBlockPos(0, 0, 0); + } - /** - * 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; + /** + * 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); } } - if (flags != 0x7) throw new IllegalArgumentException("The directions must be on different axes!"); + } + + return map; + } + + 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 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); + } + + public static Builder builder() { + // 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 { + + 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) { + directions[0] = aisleDir; + directions[1] = stringDir; + directions[2] = charDir; + GreggyBlockPos.validateFacingsArray(directions); } public Builder aisle(String... data) { - this.shape.add(data); + this.shape.add(new PatternAisle(1, data)); return this; } 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; } @@ -119,53 +316,38 @@ 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; - } - } + /** + * 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; } - return blockInfos; + + // todo make lang a Function, String> to account for builder map + dotMap.put(symbol, new Dot(dot, lang)); + return this; } public Builder shallowCopy() { - Builder builder = new Builder(this.structureDir); + Builder builder = new Builder(directions[0], directions[1], directions[2]); builder.shape = new ArrayList<>(this.shape); - builder.symbolMap = new HashMap<>(this.symbolMap); + builder.symbolMap = new Char2ObjectOpenHashMap<>(this.symbolMap); + builder.dotMap = new Char2ObjectOpenHashMap<>(this.dotMap); return builder; } public MultiblockShapeInfo build() { - return new MultiblockShapeInfo(bakeArray()); + 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/OriginOffset.java b/src/main/java/gregtech/api/pattern/OriginOffset.java new file mode 100644 index 00000000000..a140590e9d4 --- /dev/null +++ b/src/main/java/gregtech/api/pattern/OriginOffset.java @@ -0,0 +1,33 @@ +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 { + + 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) { + for (int i = 0; i < 3; i++) { + pos.offset(RelativeDirection.VALUES[2 * i].getRelativeFacing(frontFacing, upFacing), offset[i]); + } + } +} diff --git a/src/main/java/gregtech/api/pattern/PatternError.java b/src/main/java/gregtech/api/pattern/PatternError.java index 62c014fa426..b81b06ff42a 100644 --- a/src/main/java/gregtech/api/pattern/PatternError.java +++ b/src/main/java/gregtech/api/pattern/PatternError.java @@ -3,44 +3,48 @@ 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 java.util.ArrayList; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; import java.util.List; public class PatternError { - protected BlockWorldState worldState; + /** + * 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; + + public PatternError(BlockPos pos, List> candidates) { + this.pos = pos; + this.candidates = candidates; + } - public void setWorldState(BlockWorldState worldState) { - this.worldState = worldState; + public PatternError(BlockPos pos, TraceabilityPredicate failingPredicate) { + this(pos, failingPredicate.getCandidates()); } - public World getWorld() { - return worldState.getWorld(); + public PatternError(BlockPos pos, TraceabilityPredicate.SimplePredicate failingPredicate) { + this(pos, Collections.singletonList(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 +53,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/PatternMatchContext.java b/src/main/java/gregtech/api/pattern/PatternMatchContext.java deleted file mode 100644 index f0526f43186..00000000000 --- a/src/main/java/gregtech/api/pattern/PatternMatchContext.java +++ /dev/null @@ -1,76 +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) { - 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/PatternStringError.java b/src/main/java/gregtech/api/pattern/PatternStringError.java index fc62bfecb27..84e4b2b74de 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) { + public PatternStringError(BlockPos pos, String translateKey) { + super(pos, Collections.emptyList()); this.translateKey = translateKey; } diff --git a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java index da9483998be..ac38f388530 100644 --- a/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java +++ b/src/main/java/gregtech/api/pattern/TraceabilityPredicate.java @@ -1,57 +1,47 @@ 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.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 java.util.*; -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 TraceabilityPredicate ANY = new TraceabilityPredicate((state) -> true); + public static final TraceabilityPredicate ANY = new TraceabilityPredicate(worldState -> null); // Allow the air block. - public static TraceabilityPredicate AIR = new TraceabilityPredicate( - blockWorldState -> blockWorldState.getBlockState().getBlock().isAir(blockWorldState.getBlockState(), - blockWorldState.getWorld(), blockWorldState.getPos())); + 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())); // Allow all heating coils, and require them to have the same type. - public static Supplier HEATING_COILS = () -> new TraceabilityPredicate(blockWorldState -> { - IBlockState blockState = blockWorldState.getBlockState(); - if (GregTechAPI.HEATING_COILS.containsKey(blockState)) { - IHeatingCoilBlockStats stats = GregTechAPI.HEATING_COILS.get(blockState); - Object currentCoil = blockWorldState.getMatchContext().getOrPut("CoilType", stats); - if (!currentCoil.equals(stats)) { - blockWorldState.setError(new PatternStringError("gregtech.multiblock.pattern.error.coils")); - return false; - } - blockWorldState.getMatchContext().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"); - - public final List common = new ArrayList<>(); - public final List limited = new ArrayList<>(); + public static Supplier HEATING_COILS = () -> new TraceabilityPredicate( + worldState -> GregTechAPI.HEATING_COILS.containsKey(worldState.getBlockState()) ? null : + PatternError.PLACEHOLDER, + map -> GregTechAPI.HEATING_COILS.entrySet().stream() + .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"); + + public final List simple = new ArrayList<>(); protected boolean isCenter; protected boolean hasAir = false; protected boolean isSingle = true; @@ -59,29 +49,21 @@ public class TraceabilityPredicate { public TraceabilityPredicate() {} public TraceabilityPredicate(TraceabilityPredicate predicate) { - common.addAll(predicate.common); - limited.addAll(predicate.limited); + simple.addAll(predicate.simple); isCenter = predicate.isCenter; hasAir = predicate.hasAir; isSingle = predicate.isSingle; } - public TraceabilityPredicate(Predicate predicate, Supplier candidates) { - common.add(new SimplePredicate(predicate, candidates)); + public TraceabilityPredicate(Function predicate, + Function, BlockInfo[]> candidates) { + simple.add(new SimplePredicate(predicate, candidates)); } - public TraceabilityPredicate(Predicate predicate) { + public TraceabilityPredicate(Function predicate) { this(predicate, null); } - public boolean isHasAir() { - return hasAir; - } - - 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. @@ -91,9 +73,8 @@ public TraceabilityPredicate setCenter() { return this; } - public TraceabilityPredicate sort() { - limited.sort(Comparator.comparingInt(a -> ((a.minLayerCount + 1) * 100 + a.minGlobalCount))); - return this; + public boolean isCenter() { + return isCenter; } /** @@ -104,14 +85,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 -> { - if (predicate.candidates == null) return; - if (predicate.toolTips == null) { - predicate.toolTips = new ArrayList<>(); - } - predicate.toolTips.addAll(tooltips); - }); - limited.forEach(predicate -> { + simple.forEach(predicate -> { if (predicate.candidates == null) return; if (predicate.toolTips == null) { predicate.toolTips = new ArrayList<>(); @@ -122,6 +96,17 @@ 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() { + return simple.stream() + .map(SimplePredicate::getCandidates) + .collect(Collectors.toList()); + } + /** * Note: This method does not translate dynamically!! Parameters can not be updated once set. */ @@ -136,11 +121,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; - } + simple.forEach(p -> p.minGlobalCount = min); return this; } @@ -152,11 +133,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; - } + simple.forEach(p -> p.maxGlobalCount = max); return this; } @@ -168,11 +145,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; - } + simple.forEach(p -> p.minLayerCount = min); return this; } @@ -184,11 +157,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; - } + simple.forEach(p -> p.maxLayerCount = max); return this; } @@ -209,19 +178,22 @@ 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); + simple.forEach(p -> p.previewCount = count); return this; } - public boolean test(BlockWorldState blockWorldState) { - boolean flag = false; - for (SimplePredicate predicate : limited) { - if (predicate.testLimited(blockWorldState)) { - flag = true; - } + public PatternError test(BlockWorldState worldState, + Object2IntMap globalCache, + Object2IntMap layerCache) { + PatternError lastError = null; + for (SimplePredicate pred : simple) { + PatternError error = pred.testLimited(worldState, globalCache, layerCache); + if (error == null) return null; + + lastError = error; } - return flag || common.stream().anyMatch(predicate -> predicate.test(blockWorldState)); + return lastError == PatternError.PLACEHOLDER ? new PatternError(worldState.getPos(), getCandidates()) : + lastError; } public TraceabilityPredicate or(TraceabilityPredicate other) { @@ -233,8 +205,7 @@ 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.limited.addAll(other.limited); + newPredicate.simple.addAll(other.simple); return newPredicate; } return this; @@ -242,9 +213,8 @@ public TraceabilityPredicate or(TraceabilityPredicate other) { public static class SimplePredicate { - public final Supplier candidates; - - public final Predicate predicate; + public final Function, BlockInfo[]> candidates; + public final Function predicate; @SideOnly(Side.CLIENT) private List toolTips; @@ -256,7 +226,8 @@ public static class SimplePredicate { public int previewCount = -1; - public SimplePredicate(Predicate predicate, Supplier candidates) { + public SimplePredicate(Function predicate, + Function, BlockInfo[]> candidates) { this.predicate = predicate; this.candidates = candidates; } @@ -296,75 +267,64 @@ public List getToolTips(TraceabilityPredicate predicates) { return result; } - public boolean test(BlockWorldState blockWorldState) { - return predicate.test(blockWorldState); + public PatternError testRaw(BlockWorldState worldState) { + return predicate.apply(worldState); } - public boolean testLimited(BlockWorldState blockWorldState) { - return testGlobal(blockWorldState) && testLayer(blockWorldState); + public PatternError testLimited(BlockWorldState worldState, + Object2IntMap globalCache, + Object2IntMap layerCache) { + PatternError error = testGlobal(worldState, globalCache, layerCache); + if (error != null) return error; + return testLayer(worldState, layerCache); } - public boolean testGlobal(BlockWorldState blockWorldState) { - 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); - if (maxGlobalCount == -1 || count <= maxGlobalCount) return base; - blockWorldState.setError(new SinglePredicateError(this, 0)); - return false; + 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 boolean testLayer(BlockWorldState blockWorldState) { - if (minLayerCount == -1 && maxLayerCount == -1) return true; - Integer count = blockWorldState.layerCount.get(this); - boolean base = predicate.test(blockWorldState); - count = (count == null ? 0 : count) + (base ? 1 : 0); - blockWorldState.layerCount.put(this, count); - if (maxLayerCount == -1 || count <= maxLayerCount) return base; - blockWorldState.setError(new SinglePredicateError(this, 2)); - return false; + 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); } public List getCandidates() { - return candidates == null ? Collections.emptyList() : Arrays.stream(this.candidates.get()) - .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()); } } 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, failingPredicate); 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/api/pattern/pattern/AisleStrategy.java b/src/main/java/gregtech/api/pattern/pattern/AisleStrategy.java new file mode 100644 index 00000000000..d0fd3408f39 --- /dev/null +++ b/src/main/java/gregtech/api/pattern/pattern/AisleStrategy.java @@ -0,0 +1,64 @@ +package gregtech.api.pattern.pattern; + +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 org.lwjgl.util.vector.Matrix4f; + +import java.util.List; +import java.util.Map; + +/** + * A strategy to how aisles should be checked in patterns. + */ +public abstract class AisleStrategy { + + protected final int[] dimensions = new int[3]; + protected final EnumFacing[] directions = new EnumFacing[3]; + + protected BlockPattern pattern; + protected Matrix4f transform; + + /** + * Checks the aisles + * + * @return Whether the pattern is formed after this. + */ + public abstract boolean check(); + + /** + * 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, String)} + * @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. + */ + protected void start(Matrix4f transform) { + this.transform = transform; + } + + /** + * No more aisles will be added. Check preconditions and throw exceptions here. + */ + protected void finish(int[] dimensions, EnumFacing[] 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) { + // todo remove + GTLog.logger.info("Checked aisle {} with offset {}", index, offset); + return pattern.checkAisle(transform, index, offset); + } +} 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..4bca0b4e8bf --- /dev/null +++ b/src/main/java/gregtech/api/pattern/pattern/BasicAisleStrategy.java @@ -0,0 +1,145 @@ +package gregtech.api.pattern.pattern; + +import gregtech.api.util.GTLog; +import gregtech.api.util.GTUtility; +import gregtech.api.util.RelativeDirection; + +import net.minecraft.util.EnumFacing; +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; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * The default aisle strategy, supporting repeatable (multi) aisles. Multi aisles may lead to cache issues though. + */ +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() { + int offset = 0; + for (int[] multiAisle : multiAisles) { + int result = checkMultiAisle(multiAisle, offset); + if (result == -1) return false; + offset += result; + } + return true; + } + + public int getMultiAisleRepeats(int index) { + return multiAisles.get(index)[4]; + } + + protected int checkMultiAisle(int[] multi, int offset) { + int aisleOffset = 0; + int temp = 0; + for (int i = 1; i <= multi[1]; i++) { + for (int j = multi[2]; j < multi[3]; j++) { + int result = checkRepeatAisle(j, offset + temp); + if (result == -1) { + if (i <= multi[0]) return -1; + multi[4] = i - 1; + return aisleOffset; + } + temp += result; + } + aisleOffset = temp; + } + + multi[4] = multi[1]; + return aisleOffset; + } + + protected int checkRepeatAisle(int index, int offset) { + PatternAisle aisle = aisles.get(index); + for (int i = 1; i <= aisle.maxRepeats; i++) { + boolean result = checkAisle(index, offset + i - 1); + if (!result) { + if (i <= aisle.minRepeats) return -1; + + return aisles.get(index).actualRepeats = i - 1; + } + } + + return aisles.get(index).actualRepeats = aisle.maxRepeats; + } + + @Override + public int @NotNull [] getDefaultAisles(Map map) { + IntList list = new IntArrayList(); + 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); + } + } + } + return list.toIntArray(); + } + + @Override + protected void finish(int[] dimensions, EnumFacing[] directions, List aisles) { + super.finish(dimensions, directions, aisles); + + // 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()); + 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); + // set default multi aisle if not specified + 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])); + } + + protected void multiAisleError() { + GTLog.logger.error( + "multiAisles in the pattern, formatted as [ minRepeats, maxRepeats, startInclusive, endExclusive, actualRepeats ] "); + for (int[] arr : multiAisles) { + GTLog.logger.error(Arrays.toString(arr)); + } + throw new IllegalStateException("Illegal multiAisles, check logs above."); + } + + 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 new file mode 100644 index 00000000000..cd41c506b1c --- /dev/null +++ b/src/main/java/gregtech/api/pattern/pattern/BlockPattern.java @@ -0,0 +1,255 @@ +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.MatrixPair; +import gregtech.api.pattern.OriginOffset; +import gregtech.api.pattern.PatternError; +import gregtech.api.pattern.TraceabilityPredicate; +import gregtech.api.util.BlockInfo; +import gregtech.api.util.GTUtility; +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 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.Long2ObjectRBTreeMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMap; +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 org.lwjgl.util.vector.Matrix4f; + +import java.util.*; + +public class BlockPattern implements IBlockPattern { + + /** + * In the form of [ aisleDir, stringDir, charDir ] + */ + protected final EnumFacing[] directions; + + /** + * In the form of [ num aisles, num string per aisle, num char per string ] + */ + protected final int[] dimensions; + protected final PatternAisle[] aisles; + protected final AisleStrategy aisleStrategy; + protected final Char2ObjectMap predicates; + protected final BlockWorldState worldState; + protected final Object2IntMap globalCount = new Object2IntOpenHashMap<>(); + protected final Object2IntMap layerCount = new Object2IntOpenHashMap<>(); + protected final PatternState state = new PatternState(); + protected final Long2ObjectMap cache = new Long2ObjectOpenHashMap<>(); + protected final GreggyBlockPos startPos = new GreggyBlockPos(); + + // how many not nulls to keep someone from not passing in null? + public BlockPattern(@NotNull PatternAisle @NotNull [] aisles, + @NotNull AisleStrategy aisleStrategy, + int @NotNull [] dimensions, + @NotNull EnumFacing @NotNull [] directions, + @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; + + if (offset == null) { + legacyStartOffset(centerChar); + } else { + offset.apply(this.startPos, EnumFacing.NORTH, EnumFacing.UP); + } + + this.worldState = new BlockWorldState(); + } + + /** + * For legacy compat only, + * + * @param center The center char to look for + */ + private void legacyStartOffset(char center) { + if (center == 0) return; + for (int aisleI = 0; aisleI < dimensions[0]; aisleI++) { + int[] result = aisles[aisleI].firstInstanceOf(center); + if (result != null) { + moveOffset(directions[0], -aisleI); + moveOffset(directions[1], -result[0]); + moveOffset(directions[2], -result[1]); + return; + } + } + + throw new IllegalStateException("Failed to find center char: '" + center + "'"); + } + + @Nullable + @Override + public PatternState cachedPattern(World world) { + PatternState result = validateCache(world); + if (result != null) return result; + + clearCache(); + state.setError(null); + return null; + } + + @Override + public Long2ObjectMap getCache() { + return cache; + } + + @Override + public boolean checkPatternAt(World world, Matrix4f transform) { + this.globalCount.clear(); + this.layerCount.clear(); + cache.clear(); + + worldState.setWorld(world); + + aisleStrategy.pattern = this; + aisleStrategy.start(transform); + if (!aisleStrategy.check()) { + clearCache(); + return false; + } + + // global minimum checks + for (Object2IntMap.Entry entry : globalCount.object2IntEntrySet()) { + if (entry.getIntValue() < entry.getKey().minGlobalCount) { + state.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 1)); + clearCache(); + return false; + } + } + + state.setError(null); + return true; + } + + /** + * Checks a specific aisle for validity + * + * @param transform Transformation matrix + * @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 + * @return True if the check passed + */ + public boolean checkAisle(Matrix4f transform, int aisleIndex, int aisleOffset) { + // where the aisle would start in world + GreggyBlockPos pos = startPos.copy().offset(directions[0], aisleOffset); + GreggyBlockPos transformed = new GreggyBlockPos(); + PatternAisle aisle = aisles[aisleIndex]; + + layerCount.clear(); + + for (int stringI = 0; stringI < dimensions[1]; stringI++) { + for (int charI = 0; charI < dimensions[2]; charI++) { + MatrixPair.apply(transform, transformed.from(pos)); + worldState.setPos(transformed); + TraceabilityPredicate predicate = predicates.get(aisle.charAt(stringI, charI)); + + if (predicate != TraceabilityPredicate.ANY) { + TileEntity te = worldState.getTileEntity(); + cache.put(transformed.toLong(), new BlockInfo(worldState.getBlockState(), + !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null)); + } + + // GTLog.logger.info("Checked pos at " + charPos + " with flip " + flip); + + PatternError result = predicate.test(worldState, globalCount, layerCount); + if (result != null) { + state.setError(result); + return false; + } + + pos.offset(directions[2]); + } + + pos.offset(directions[2].getOpposite(), dimensions[2]); + pos.offset(directions[1]); + } + + // layer minimum checks + // todo fix all minimum checks fr + 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; + } + + public int getRepetitionCount(int aisleI) { + return aisles[aisleI].actualRepeats; + } + + @Override + public Long2ObjectSortedMap getDefaultShape(Matrix4f transform, + @NotNull Map keyMap) { + Long2ObjectSortedMap map = new Long2ObjectRBTreeMap<>(); + + GreggyBlockPos pos = startPos.copy(); + GreggyBlockPos transformed = new GreggyBlockPos(); + + 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(MatrixPair.apply(transform, transformed.from(pos)).toLong(), + predicates.get(aisles[order[i]].charAt(j, k))); + pos.offset(directions[2]); + } + pos.offset(directions[1]); + pos.offset(directions[2].getOpposite(), dimensions[2]); + } + + pos.from(startPos); + pos.offset(directions[0], i + 1); + } + + return map; + } + + @Override + public PatternState getState() { + return state; + } + + /** + * Probably shouldn't mutate this. + */ + public AisleStrategy getAisleStrategy() { + return aisleStrategy; + } + + @Override + public void moveOffset(RelativeDirection dir, int amount) { + startPos.offset(DEFAULT_FACINGS[dir.ordinal()], amount); + } + + public void moveOffset(EnumFacing dir, int amount) { + startPos.offset(dir, amount); + } +} 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..ba9bcd3f428 --- /dev/null +++ b/src/main/java/gregtech/api/pattern/pattern/ExpandablePattern.java @@ -0,0 +1,188 @@ +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.MatrixPair; +import gregtech.api.pattern.PatternError; +import gregtech.api.pattern.TraceabilityPredicate; +import gregtech.api.util.BlockInfo; +import gregtech.api.util.GTUtility; +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 net.minecraft.world.World; + +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.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.util.vector.Matrix4f; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +public class ExpandablePattern implements IBlockPattern { + + protected final Supplier boundSupplier; + protected final BiFunction predicateFunction; + protected final GreggyBlockPos offset = new GreggyBlockPos(); + + /** + * In the form of [ aisleDir, stringDir, charDir ] + */ + protected final EnumFacing[] directions; + protected final BlockWorldState worldState; + 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 boundSupplier Supplier for 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 Supplier boundSupplier, + @NotNull BiFunction predicateFunction, + @NotNull EnumFacing[] directions) { + this.boundSupplier = boundSupplier; + this.predicateFunction = predicateFunction; + this.directions = directions; + + this.worldState = new BlockWorldState(); + } + + @Nullable + @Override + public PatternState cachedPattern(World world) { + PatternState result = validateCache(world); + if (result != null) return result; + + clearCache(); + state.setError(null); + return null; + } + + @Override + public boolean checkPatternAt(World world, Matrix4f transform) { + int[] bounds = boundSupplier.get(); + + globalCount.clear(); + + // 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++) { + negativeCorner.set(i, -bounds[directions[i].ordinal() ^ 1]); + positiveCorner.set(i, bounds[directions[i].ordinal()]); + } + + worldState.setWorld(world); + GreggyBlockPos transformed = new GreggyBlockPos(); + + // 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(directions[0], arr[0]).offset(directions[1], arr[1]).offset(directions[2], arr[2]); + + MatrixPair.apply(transform, transformed.from(pos)); + worldState.setPos(transformed); + + if (predicate != TraceabilityPredicate.ANY) { + TileEntity te = worldState.getTileEntity(); + cache.put(transformed.toLong(), new BlockInfo(worldState.getBlockState(), + !(te instanceof IGregTechTileEntity gtTe) || gtTe.isValid() ? te : null)); + } + + PatternError result = predicate.test(worldState, globalCount, null); + if (result != null) { + state.setError(result); + clearCache(); + return false; + } + } + + for (Object2IntMap.Entry entry : globalCount.object2IntEntrySet()) { + if (entry.getIntValue() < entry.getKey().minGlobalCount) { + state.setError(new TraceabilityPredicate.SinglePredicateError(entry.getKey(), 1)); + clearCache(); + return false; + } + } + return true; + } + + @Override + public Long2ObjectSortedMap getDefaultShape(Matrix4f transform, + @NotNull Map keyMap) { + int[] bounds = boundSupplier.get(); + Long2ObjectSortedMap predicates = new Long2ObjectRBTreeMap<>(); + + GreggyBlockPos negativeCorner = new GreggyBlockPos(); + GreggyBlockPos positiveCorner = new GreggyBlockPos(); + + for (int i = 0; i < 3; i++) { + negativeCorner.set(i, -bounds[directions[i].ordinal() ^ 1]); + positiveCorner.set(i, bounds[directions[i].ordinal()]); + } + + GreggyBlockPos translation = new GreggyBlockPos(); + + 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(directions[0], arr[0]).offset(directions[1], arr[1]).offset(directions[2], arr[2]); + + if (predicate != TraceabilityPredicate.ANY && predicate != TraceabilityPredicate.AIR) { + predicates.put(MatrixPair.apply(transform, translation.from(pos)).toLong(), predicate); + } + } + + return predicates; + } + + @Override + public PatternState getState() { + return state; + } + + @Override + public Long2ObjectMap getCache() { + return this.cache; + } + + @Override + public void clearCache() { + cache.clear(); + } + + @Override + public void moveOffset(RelativeDirection dir, int amount) { + offset.offset(DEFAULT_FACINGS[dir.ordinal()], amount); + } +} diff --git a/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java new file mode 100644 index 00000000000..3a2dab3c962 --- /dev/null +++ b/src/main/java/gregtech/api/pattern/pattern/FactoryBlockPattern.java @@ -0,0 +1,214 @@ +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; + +import com.google.common.base.Joiner; +import it.unimi.dsi.fastutil.chars.Char2ObjectMap; +import it.unimi.dsi.fastutil.chars.Char2ObjectOpenHashMap; + +import net.minecraft.util.EnumFacing; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +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#startOffset(OriginOffset)} + * , or automatically detected(for legacy compat only, you should use + * {@link FactoryBlockPattern#startOffset(OriginOffset)} 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 { + + protected static final Joiner COMMA_JOIN = Joiner.on(","); + + /** + * [ num aisles, num string per aisle, num char per string ] + */ + private final int[] dimensions = { -1, -1, -1 }; + private char centerChar; + private AisleStrategy aisleStrategy; + + private final List aisles = new ArrayList<>(); + + /** + * Map going from chars to the predicates + */ + private final Char2ObjectMap symbolMap = new Char2ObjectOpenHashMap<>(); + + /** + * In the form of [ aisleDir, stringDir, charDir ] + */ + private final EnumFacing[] directions = new EnumFacing[3]; + + /** + * @see FactoryBlockPattern#start(RelativeDirection, RelativeDirection, RelativeDirection) + */ + private FactoryBlockPattern(RelativeDirection aisleDir, RelativeDirection stringDir, RelativeDirection charDir) { + directions[0] = aisleDir.getRelativeFacing(EnumFacing.NORTH, EnumFacing.UP); + directions[1] = stringDir.getRelativeFacing(EnumFacing.NORTH, EnumFacing.UP); + directions[2] = charDir.getRelativeFacing(EnumFacing.NORTH, EnumFacing.UP); + GreggyBlockPos.validateFacingsArray(directions); + this.symbolMap.put(' ', TraceabilityPredicate.ANY); + } + + /** + * Adds a repeatable aisle to this pattern. + * + * @param minRepeats The min repeats, inclusive + * @param maxRepeats The max repeats, inclusive + * @param aisle The aisle to add + */ + 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); + } + } + } + + if (minRepeats > maxRepeats) + throw new IllegalArgumentException("Lower bound of repeat counting must smaller than upper bound!"); + + PatternAisle aile = new PatternAisle(aisle); + aile.minRepeats = minRepeats; + aile.maxRepeats = maxRepeats; + aisles.add(aile); + 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); + } + + /** + * Sets a part of the start offset in the given direction. + */ + public FactoryBlockPattern startOffset(OriginOffset offset) { + this.offset = offset; + 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(BACK, UP, RIGHT); + } + + /** + * 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 aisleDir, RelativeDirection stringDir, + RelativeDirection charDir) { + return new FactoryBlockPattern(aisleDir, stringDir, charDir); + } + + /** + * 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, blockMatcher); + if (blockMatcher.isCenter()) centerChar = symbol; + return this; + } + + public FactoryBlockPattern aisleStrategy(AisleStrategy strategy) { + this.aisleStrategy = strategy; + return this; + } + + public BlockPattern build() { + checkMissingPredicates(); + this.dimensions[0] = aisles.size(); + 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); + } + + private void checkMissingPredicates() { + List list = new ArrayList<>(); + + for (Char2ObjectMap.Entry entry : this.symbolMap.char2ObjectEntrySet()) { + if (entry.getValue() == null) { + list.add(entry.getCharKey()); + } + } + + if (!list.isEmpty()) { + 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/FactoryExpandablePattern.java b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java new file mode 100644 index 00000000000..9f8c7d6aa7e --- /dev/null +++ b/src/main/java/gregtech/api/pattern/pattern/FactoryExpandablePattern.java @@ -0,0 +1,78 @@ +package gregtech.api.pattern.pattern; + +import gregtech.api.pattern.GreggyBlockPos; +import gregtech.api.pattern.TraceabilityPredicate; +import gregtech.api.util.RelativeDirection; + +import net.minecraft.util.EnumFacing; + +import java.util.function.BiFunction; +import java.util.function.Supplier; + +public class FactoryExpandablePattern { + + protected Supplier boundsSupplier; + protected BiFunction predicateFunction; + protected final EnumFacing[] directions = new EnumFacing[3]; + + private FactoryExpandablePattern(RelativeDirection aisleDir, RelativeDirection stringDir, + RelativeDirection charDir) { + directions[0] = aisleDir.getRelativeFacing(EnumFacing.NORTH, EnumFacing.UP); + directions[1] = stringDir.getRelativeFacing(EnumFacing.NORTH, EnumFacing.UP); + directions[2] = charDir.getRelativeFacing(EnumFacing.NORTH, EnumFacing.UP); + GreggyBlockPos.validateFacingsArray(directions); + } + + /** + * 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 EnumFacing enum. Remember that the canonical multiblock has front + * facing NORTH and up facing UP. + */ + public FactoryExpandablePattern boundsFunction(Supplier supplier) { + this.boundsSupplier = supplier; + 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; + } + + public ExpandablePattern build() { + if (boundsSupplier == 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(boundsSupplier, predicateFunction, directions); + } +} 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..e15553058b8 --- /dev/null +++ b/src/main/java/gregtech/api/pattern/pattern/IBlockPattern.java @@ -0,0 +1,109 @@ +package gregtech.api.pattern.pattern; + +import gregtech.api.pattern.GreggyBlockPos; +import gregtech.api.pattern.TraceabilityPredicate; +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 net.minecraft.world.World; + +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 org.lwjgl.util.vector.Matrix4f; + +import java.util.Map; + +public interface IBlockPattern { + + // a[i] is RelativeDirection.values()[i].getRelativeFacing(NORTH, UP) + EnumFacing[] DEFAULT_FACINGS = { + EnumFacing.UP, + EnumFacing.DOWN, + EnumFacing.WEST, + EnumFacing.EAST, + EnumFacing.NORTH, + EnumFacing.SOUTH + }; + + /** + * @return The internal state of the pattern if it is valid, or null; + */ + @Nullable + PatternState cachedPattern(World world); + + /** + * @return True if the check passed, in which case the context is mutated for returning from checkPatternFastAt(...) + */ + boolean checkPatternAt(World world, Matrix4f transform); + + /** + * Gets the default shape, if the multiblock does not specify one. Return null to represent the default shape does + * not exist. + * + * @param transform Matrix transforming coordinates(specified by the specific impl) into world coordinates. + * @param keyMap The map from multiblock builder for autobuild. + * @return Expected that each invocation returns a new map instance. + */ + Long2ObjectSortedMap getDefaultShape(Matrix4f transform, + @NotNull Map keyMap); + + PatternState getState(); + + default void clearCache() { + getCache().clear(); + } + + Long2ObjectMap getCache(); + + void moveOffset(RelativeDirection dir, int amount); + + default void moveOffset(RelativeDirection dir) { + moveOffset(dir, 1); + } + + /** + * Common code for validating a structure's cache. + * @return The state iff cache is valid. + */ + default PatternState validateCache(World world) { + if (!getCache().isEmpty()) { + boolean pass = true; + GreggyBlockPos gregPos = new GreggyBlockPos(); + for (Long2ObjectMap.Entry entry : getCache().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) { + if (getState().hasError()) { + getState().setState(PatternState.EnumCheckState.INVALID); + } else { + getState().setState(PatternState.EnumCheckState.VALID_CACHED); + } + + return getState(); + } + } + return null; + } +} diff --git a/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java b/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java new file mode 100644 index 00000000000..635cd942102 --- /dev/null +++ b/src/main/java/gregtech/api/pattern/pattern/PatternAisle.java @@ -0,0 +1,92 @@ +package gregtech.api.pattern.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 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; + this.pattern = pattern; + } + + public PatternAisle(int repeats, String[] pattern) { + this(repeats, repeats, pattern); + } + + 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; + } + + 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++) { + int pos = pattern[strI].indexOf(c); + if (pos != -1) return new int[] { strI, pos }; + } + 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); + } + + public int getStringCount() { + return pattern.length; + } + + 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/pattern/PatternState.java b/src/main/java/gregtech/api/pattern/pattern/PatternState.java new file mode 100644 index 00000000000..fdfedcb9db3 --- /dev/null +++ b/src/main/java/gregtech/api/pattern/pattern/PatternState.java @@ -0,0 +1,66 @@ +package gregtech.api.pattern.pattern; + +import gregtech.api.pattern.MatrixPair; +import gregtech.api.pattern.PatternError; + +import org.jetbrains.annotations.ApiStatus; +import org.lwjgl.util.vector.Matrix4f; + +import java.util.HashMap; +import java.util.Map; + +public class PatternState { + + public boolean formed = false; + public boolean shouldUpdate = true; + public PatternError error; + public Map storage = new HashMap<>(); + protected EnumCheckState state; + + public void setError(PatternError error) { + this.error = error; + } + + public boolean hasError() { + return error != null; + } + + public PatternError getError() { + return error; + } + + @ApiStatus.Internal + public void setState(EnumCheckState state) { + this.state = state; + } + + public EnumCheckState getState() { + return state; + } + + /** + * 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 structure is invalid, regardless of cache. + */ + INVALID; + + public boolean isValid() { + return ordinal() < 2; + } + } +} diff --git a/src/main/java/gregtech/api/util/BlockInfo.java b/src/main/java/gregtech/api/util/BlockInfo.java index 2f2340db46d..9b3a4bd730b 100644 --- a/src/main/java/gregtech/api/util/BlockInfo.java +++ b/src/main/java/gregtech/api/util/BlockInfo.java @@ -1,11 +1,13 @@ 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 net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; import com.google.common.base.Preconditions; @@ -20,7 +22,6 @@ public class BlockInfo { private final IBlockState blockState; private final TileEntity tileEntity; - private final Object info; public BlockInfo(Block block) { this(block.getDefaultState()); @@ -31,17 +32,24 @@ public BlockInfo(IBlockState blockState) { } public BlockInfo(IBlockState blockState, TileEntity tileEntity) { - this(blockState, tileEntity, null); - } - - public BlockInfo(IBlockState blockState, TileEntity tileEntity, Object info) { + // 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; Preconditions.checkArgument(tileEntity == null || blockState.getBlock().hasTileEntity(blockState), "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; } @@ -49,15 +57,4 @@ public IBlockState getBlockState() { public TileEntity getTileEntity() { return tileEntity; } - - public Object getInfo() { - return info; - } - - 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/GTUtility.java b/src/main/java/gregtech/api/util/GTUtility.java index 39b4a6a0d65..37bff32b920 100644 --- a/src/main/java/gregtech/api/util/GTUtility.java +++ b/src/main/java/gregtech/api/util/GTUtility.java @@ -16,10 +16,12 @@ import gregtech.api.metatileentity.WorkableTieredMetaTileEntity; import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; import gregtech.api.metatileentity.registry.MTERegistry; +import gregtech.api.pattern.GreggyBlockPos; import gregtech.api.recipes.RecipeMap; import gregtech.api.unification.OreDictUnifier; import gregtech.api.unification.ore.OrePrefix; +import net.minecraft.block.Block; import net.minecraft.block.BlockRedstoneWire; import net.minecraft.block.BlockSnow; import net.minecraft.block.material.MapColor; @@ -38,6 +40,9 @@ import net.minecraft.util.ResourceLocation; import net.minecraft.util.SoundCategory; 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.TextComponentTranslation; import net.minecraft.world.IBlockAccess; import net.minecraft.world.World; @@ -61,6 +66,7 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.lwjgl.util.vector.Matrix4f; import java.util.AbstractList; import java.util.ArrayList; @@ -653,6 +659,46 @@ 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 igtte) { + stack = igtte.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 + NonNullList list = NonNullList.create(); + state.getBlock().getDrops(list, world, pos, state, 0); + if (!list.isEmpty()) { + return list.get(0); + } + } else return stack; + + return ItemStack.EMPTY; + } + public static boolean isOre(ItemStack item) { OrePrefix orePrefix = OreDictUnifier.getPrefix(item); return orePrefix != null && orePrefix.name().startsWith("ore"); @@ -911,6 +957,28 @@ public double getAsDouble() { return Pair.of(supplier1, supplier2); } + /** + * Return a cross b or null if result is zero vector. + */ + 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()); + } + + /** + * Return new upward facing. + */ + public static EnumFacing simulateAxisRotation(EnumFacing newFrontFacing, EnumFacing oldFrontFacing, + EnumFacing upwardsFacing) { + EnumFacing cross = GTUtility.cross(newFrontFacing, oldFrontFacing); + if (cross == null) return upwardsFacing; + if (cross.getAxis() == upwardsFacing.getAxis()) return upwardsFacing; + if (upwardsFacing == newFrontFacing) return oldFrontFacing.getOpposite(); + return oldFrontFacing; + } + /** * Safely cast a Long to an Int without overflow. * @@ -920,4 +988,19 @@ public double getAsDouble() { 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 def if the string is not an int. + */ + public static int parseInt(String s, int def) { + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + return def; + } + } + + public static int parseInt(String s) { + return parseInt(s, -1); + } } diff --git a/src/main/java/gregtech/api/util/RelativeDirection.java b/src/main/java/gregtech/api/util/RelativeDirection.java index 41c67e91f09..7dd022413fb 100644 --- a/src/main/java/gregtech/api/util/RelativeDirection.java +++ b/src/main/java/gregtech/api/util/RelativeDirection.java @@ -1,282 +1,46 @@ package gregtech.api.util; +import gregtech.api.pattern.GreggyBlockPos; + 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.Function; +import java.util.function.BinaryOperator; +import java.util.function.ToIntFunction; /** * Relative direction when facing horizontally */ public enum RelativeDirection { - UP(f -> EnumFacing.UP), - DOWN(f -> EnumFacing.DOWN), - LEFT(EnumFacing::rotateYCCW), - RIGHT(EnumFacing::rotateY), - FRONT(Function.identity()), - BACK(EnumFacing::getOpposite); - - final Function actualFacing; - - RelativeDirection(Function actualFacing) { - this.actualFacing = actualFacing; - } - - public EnumFacing getActualFacing(EnumFacing facing) { - return actualFacing.apply(facing); - } - - public EnumFacing apply(EnumFacing facing) { - return actualFacing.apply(facing); - } + 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()); - public Vec3i applyVec3i(EnumFacing facing) { - return apply(facing).getDirectionVec(); - } - - 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(); - }; - } - - public Function getSorter(EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped) { - // get the direction to go in for the part sorter - EnumFacing sorterDirection = getRelativeFacing(frontFacing, upwardsFacing, isFlipped); - - // Determined by EnumFacing Axis + AxisDirection - return switch (sorterDirection) { - case UP -> BlockPos::getY; - case DOWN -> pos -> -pos.getY(); - case EAST -> BlockPos::getX; - case WEST -> pos -> -pos.getX(); - case NORTH -> pos -> -pos.getZ(); - case SOUTH -> BlockPos::getZ; - }; - } + final BinaryOperator facingFunction; /** - * Simulates rotating the controller around an axis to get to a new front facing. - * - * @return Returns the new upwards facing. + * do not mutate this unless you want your house to explode */ - public static EnumFacing simulateAxisRotation(EnumFacing newFrontFacing, EnumFacing oldFrontFacing, - EnumFacing upwardsFacing) { - if (newFrontFacing == oldFrontFacing) return upwardsFacing; - - EnumFacing.Axis newAxis = newFrontFacing.getAxis(); - EnumFacing.Axis oldAxis = oldFrontFacing.getAxis(); + public static final RelativeDirection[] VALUES = values(); - 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(); - } + RelativeDirection(BinaryOperator facingFunction) { + this.facingFunction = facingFunction; } - /** - * Offset a BlockPos relatively in any direction by any amount. Pass negative values to offset down, right or - * backwards. - */ - 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; + 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!"); } - - 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); + return facingFunction.apply(frontFacing, upFacing); } - /** - * 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]); + public EnumFacing getRelativeFacing(EnumFacing frontFacing, EnumFacing upwardsFacing, boolean isFlipped) { + return (isFlipped && (this == LEFT || this == RIGHT)) ? + getRelativeFacing(frontFacing, upwardsFacing).getOpposite() : + getRelativeFacing(frontFacing, upwardsFacing); } } 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..a6e74acd64e --- /dev/null +++ b/src/main/java/gregtech/client/renderer/handler/AABBHighlightRenderer.java @@ -0,0 +1,89 @@ +package gregtech.client.renderer.handler; + +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 com.github.bsideup.jabel.Desugar; +import org.lwjgl.Sys; +import org.lwjgl.opengl.GL11; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.function.BooleanSupplier; + +@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) { + @Override + public int hashCode() { + return System.identityHashCode(this); + } + } +} diff --git a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java index d3531a43d66..08cd3e0e423 100644 --- a/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java +++ b/src/main/java/gregtech/client/renderer/handler/MultiblockPreviewRenderer.java @@ -1,23 +1,29 @@ 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.MultiblockShapeInfo; -import gregtech.api.util.BlockInfo; +import gregtech.api.pattern.GreggyBlockPos; +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; -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; @@ -30,10 +36,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 { @@ -73,20 +80,23 @@ public static void renderWorldLastEvent(RenderWorldLastEvent event) { } } - public static void renderMultiBlockPreview(MultiblockControllerBase controller, long durTimeMillis) { - if (!controller.getPos().equals(mbpPos)) { - layer = 0; - } else { - if (mbpEndTime - System.currentTimeMillis() < 200) return; - layer++; + public static void renderMultiBlockPreview(MultiblockControllerBase src, EntityPlayer player, + long durTimeMillis) { + if (mbpPos != null) { + resetMultiblockRender(); + return; } 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.getMatchingShapes(); - if (!shapes.isEmpty()) renderControllerInList(controller, shapes.get(0), layer); + Iterator> iter = src.getPreviewBuilds(); + if (iter.hasNext()) { + renderControllerInList(src, iter.next(), layer); + // todo add dots again + // shapes.get(0).sendDotMessage(player); + } GlStateManager.glEndList(); } @@ -99,86 +109,46 @@ public static void resetMultiblockRender() { } } - public static void renderControllerInList(MultiblockControllerBase controllerBase, MultiblockShapeInfo shapeInfo, + public static void renderControllerInList(MultiblockControllerBase src, Map keyMap, int layer) { - BlockPos mbpPos = controllerBase.getPos(); - EnumFacing frontFacing, previewFacing; - previewFacing = controllerBase.getFrontFacing(); - BlockPos controllerPos = BlockPos.ORIGIN; - MultiblockControllerBase mte = null; - BlockInfo[][][] blocks = shapeInfo.getBlocks(); - 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); + ImmediateWorldSceneRenderer worldSceneRenderer = new ImmediateWorldSceneRenderer(world); + worldSceneRenderer.setClearColor(ConfigHolder.client.multiblockPreviewColor); + + MetaTileEntityHolder holder = new MetaTileEntityHolder(); + 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); - EnumFacing facing = controllerBase.getFrontFacing(); - EnumFacing upwardsFacing = controllerBase.getUpwardsFacing(); + ((MultiblockControllerBase) holder.getMetaTileEntity()).autoBuild(new GregFakePlayer(world), keyMap, + MultiblockControllerBase.DEFAULT_STRUCTURE); + ((MultiblockControllerBase) holder.getMetaTileEntity()).checkStructurePattern(); - frontFacing = facing.getYOffset() == 0 ? facing : - facing.getYOffset() < 0 ? upwardsFacing : upwardsFacing.getOpposite(); - Rotation rotatePreviewBy = Rotation - .values()[(4 + frontFacing.getHorizontalIndex() - previewFacing.getHorizontalIndex()) % 4]; + 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(); - 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(); - } BlockRenderLayer oldLayer = MinecraftForgeClient.getRenderLayer(); - TargetBlockAccess targetBA = new TargetBlockAccess(world, BlockPos.ORIGIN); - for (BlockPos pos : blockMap.keySet()) { + + GreggyBlockPos greg = new GreggyBlockPos(); + GreggyBlockPos offset = new GreggyBlockPos(SOURCE); + GreggyBlockPos temp = new GreggyBlockPos(); + + for (BlockPos pos : world.renderedBlocks) { targetBA.setPos(pos); + greg.from(src.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); @@ -194,8 +164,6 @@ public static void renderControllerInList(MultiblockControllerBase controllerBas GlStateManager.popMatrix(); } ForgeHooksClient.setRenderLayer(oldLayer); - - GlStateManager.popMatrix(); } @SideOnly(Side.CLIENT) 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/client/utils/TrackedDummyWorld.java b/src/main/java/gregtech/client/utils/TrackedDummyWorld.java index 939233d0b1e..085024ae125 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,29 +47,17 @@ 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)) - return null; + if (renderFilter != null && !renderFilter.test(pos)) return null; return proxyWorld != null ? proxyWorld.getTileEntity(pos) : super.getTileEntity(pos); } @NotNull @Override public IBlockState getBlockState(@NotNull BlockPos pos) { - if (renderFilter != null && !renderFilter.test(pos)) - return Blocks.AIR.getDefaultState(); // return air if not rendering this block + // return air if not rendering this block + if (renderFilter != null && !renderFilter.test(pos)) return Blocks.AIR.getDefaultState(); return proxyWorld != null ? proxyWorld.getBlockState(pos) : super.getBlockState(pos); } @@ -83,6 +69,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 c1148f5453b..c5470f7eb6b 100644 --- a/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java +++ b/src/main/java/gregtech/common/items/behaviors/MultiblockBuilderBehavior.java @@ -1,9 +1,13 @@ package gregtech.common.items.behaviors; +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; @@ -11,7 +15,9 @@ 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; @@ -22,11 +28,139 @@ 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.PanelSyncManager; +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 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; -public class MultiblockBuilderBehavior implements IItemBehaviour { +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 Map getMap(ItemStack target) { + if (!target.hasTagCompound()) return Collections.emptyMap(); + + NBTTagCompound tag = target.getTagCompound().getCompoundTag("MultiblockBuilder"); + Map result = new HashMap<>(); + + 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, 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, PanelSyncManager 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) + .value(keyValues[s]) + .background(GTGuiTextures.DISPLAY)) + .child(new TextFieldWidget() + .left(4 * 18).width(4 * 18) + .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, @@ -35,23 +169,25 @@ 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; + 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()) { - multiblock.structurePattern.autoBuild(player, multiblock); + 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()) { - PatternError error = multiblock.structurePattern.getError(); + if (!multiblock.isStructureFormed(structure)) { + PatternError error = multiblock.getSubstructure(structure).getState().getError(); if (error != null) { player.sendMessage( new TextComponentTranslation("gregtech.multiblock.pattern.error_message_header")); @@ -75,4 +211,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/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java index 7c3da2f0166..e291bce4980 100644 --- a/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java +++ b/src/main/java/gregtech/common/items/behaviors/monitorplugin/AdvancedMonitorPluginBehavior.java @@ -9,7 +9,6 @@ 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; @@ -222,7 +221,7 @@ 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) { + if (worldSceneRenderer == null && validPos != null && !validPos.isEmpty()) { createWorldScene(); } if (this.connect && worldSceneRenderer != null && @@ -247,15 +246,12 @@ public void update() { } } } else { // check multi-block valid - if (holder != null && holder.getMetaTileEntity() instanceof MultiblockControllerBase) { - MultiblockControllerBase entity = (MultiblockControllerBase) holder.getMetaTileEntity(); + if (holder != null && holder.getMetaTileEntity() instanceof MultiblockControllerBase entity) { 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) + if (entity.getSubstructure().getState().getState().isValid()) { + validPos = entity.getSubstructure().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/items/behaviors/monitorplugin/FakeGuiPluginBehavior.java b/src/main/java/gregtech/common/items/behaviors/monitorplugin/FakeGuiPluginBehavior.java index 3328ee49a26..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; @@ -66,34 +62,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/MetaTileEntityCokeOven.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOven.java index 781ae14b7e3..168b2f50bc1 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; @@ -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/MetaTileEntityCokeOvenHatch.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityCokeOvenHatch.java index acb83429697..c0e207ea306 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) { - 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()); } @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/MetaTileEntityLargeBoiler.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java index 6921ca44d5f..c512cb16c26 100644 --- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java +++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityLargeBoiler.java @@ -14,9 +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.PatternMatchContext; +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; @@ -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(); @@ -187,11 +186,11 @@ public boolean isActive() { } @Override - protected BlockPattern createStructurePattern() { + 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) @@ -247,6 +246,11 @@ public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) { return boilerType.casingRenderer; } + @Override + public ICubeRenderer getInactiveTexture(IMultiblockPart part) { + return isFireboxPart(part) ? boilerType.fireboxIdleRenderer : boilerType.casingRenderer; + } + @Override public boolean hasMufflerMechanics() { return true; diff --git a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityMultiblockTank.java index eeabc2cd762..719dac5bd54 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; @@ -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 90078eaf604..05aa41f2b00 100644 --- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java +++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveBlastFurnace.java @@ -11,9 +11,10 @@ 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.PatternError; 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.util.GTUtility; import gregtech.client.particle.VanillaParticleEffects; @@ -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); @@ -63,9 +64,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 0017c750d94..3f26d10fa59 100644 --- a/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java +++ b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityPrimitiveWaterPump.java @@ -6,9 +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.PatternMatchContext; +import gregtech.api.pattern.pattern.BlockPattern; +import gregtech.api.pattern.pattern.FactoryBlockPattern; import gregtech.api.unification.material.Materials; import gregtech.api.util.LocalizationUtils; import gregtech.client.renderer.ICubeRenderer; @@ -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(); } @@ -131,11 +130,11 @@ private void resetTileAbilities() { } @Override - protected BlockPattern createStructurePattern() { + 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/MetaTileEntityTankValve.java b/src/main/java/gregtech/common/metatileentities/multi/MetaTileEntityTankValve.java index 13f9c9657f3..b7eafff84f0 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) { - 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 } @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/electric/MetaTileEntityActiveTransformer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityActiveTransformer.java index d22e681fad3..3c5e677d03a 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,9 @@ 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.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; @@ -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)); @@ -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); @@ -116,9 +115,9 @@ public void invalidateStructure() { @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 69b648b41cb..a19e78b13e3 100644 --- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java +++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityAssemblyLine.java @@ -3,14 +3,16 @@ import gregtech.api.GTValues; import gregtech.api.capability.GregtechDataCodes; import gregtech.api.capability.IDataAccessHatch; +import gregtech.api.gui.Widget; 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.RecipeMapMultiblockController; -import gregtech.api.pattern.BlockPattern; -import gregtech.api.pattern.FactoryBlockPattern; import gregtech.api.pattern.TraceabilityPredicate; +import gregtech.api.pattern.pattern.BlockPattern; +import gregtech.api.pattern.pattern.FactoryBlockPattern; +import gregtech.api.pattern.pattern.IBlockPattern; import gregtech.api.recipes.Recipe; import gregtech.api.recipes.RecipeMaps; import gregtech.api.recipes.ingredients.GTRecipeInput; @@ -30,14 +32,20 @@ import gregtech.common.metatileentities.multi.multiblockpart.MetaTileEntityMultiFluidHatch; import gregtech.core.sound.GTSoundEvents; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; + import net.minecraft.block.state.IBlockState; import net.minecraft.client.resources.I18n; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.network.PacketBuffer; import net.minecraft.util.EnumFacing; 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; import net.minecraft.world.World; import net.minecraftforge.fluids.IFluidTank; import net.minecraftforge.fml.relauncher.Side; @@ -50,10 +58,14 @@ import codechicken.lib.vec.Vector3; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.lwjgl.util.vector.Matrix4f; +import java.lang.reflect.Field; import java.util.List; -import java.util.function.Function; +import java.util.Map; +import java.util.function.ToIntFunction; +import static gregtech.api.gui.widgets.AdvancedTextWidget.withButton; import static gregtech.api.util.RelativeDirection.*; public class MetaTileEntityAssemblyLine extends RecipeMapMultiblockController { @@ -66,7 +78,6 @@ public class MetaTileEntityAssemblyLine extends RecipeMapMultiblockController { private GTLaserBeamParticle[][] beamParticles; private int beamCount; private int beamTime; - public MetaTileEntityAssemblyLine(ResourceLocation metaTileEntityId) { super(metaTileEntityId, RecipeMaps.ASSEMBLY_LINE_RECIPES); } @@ -79,10 +90,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("FIF", "RTR", "GAS", " Y ") + .aisleRepeatable(3, 16, "FIF", "RTR", "GAD", " Y ") + .aisle("FOF", "RTR", "GAD", " Y ") .where('S', selfPredicate()) .where('F', states(getCasingState()) .or(autoAbilities(false, true, false, false, false, false, false)) @@ -141,9 +152,9 @@ 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()); + return pos -> -mat.unapply(pos).x(); } @SideOnly(Side.CLIENT) @@ -260,8 +271,7 @@ private void readParticles(@NotNull PacketBuffer buf) { } BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(getPos()); - EnumFacing relativeUp = RelativeDirection.UP.getRelativeFacing(getFrontFacing(), getUpwardsFacing(), - isFlipped()); + EnumFacing relativeUp = getUpwardsFacing(); EnumFacing relativeLeft = RelativeDirection.LEFT.getRelativeFacing(getFrontFacing(), getUpwardsFacing(), isFlipped()); boolean negativeUp = relativeUp.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE; 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..a677ed3e33c 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,24 +23,24 @@ 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.GreggyBlockPos; +import gregtech.api.pattern.PatternError; import gregtech.api.pattern.TraceabilityPredicate; +import gregtech.api.pattern.pattern.FactoryExpandablePattern; +import gregtech.api.pattern.pattern.IBlockPattern; import gregtech.api.util.BlockInfo; 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.handler.AABBHighlightRenderer; import gregtech.client.renderer.texture.Textures; import gregtech.client.utils.TooltipHelper; import gregtech.common.ConfigHolder; 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; @@ -50,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; @@ -59,7 +59,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; @@ -77,15 +76,20 @@ import codechicken.lib.vec.Matrix4; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.lwjgl.util.vector.Matrix4f; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.EnumMap; import java.util.HashSet; -import java.util.LinkedList; +import java.util.Iterator; import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static gregtech.api.gui.widgets.AdvancedTextWidget.withButton; public class MetaTileEntityCleanroom extends MultiblockWithDisplayBase implements ICleanroomProvider, IWorkable, IDataInfoProvider { @@ -95,21 +99,20 @@ 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; - + 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 = { MIN_DEPTH, 0, MIN_RADIUS, MIN_RADIUS, MIN_RADIUS, MIN_RADIUS }; private CleanroomType cleanroomType = null; private int cleanAmount; private IEnergyContainer energyContainer; private ICleanroomFilter cleanroomFilter; + private boolean renderingAABB; private final CleanroomLogic cleanroomLogic; private final Collection cleanroomReceivers = new HashSet<>(); + private AABBHighlightRenderer.AABBRender aabb; public MetaTileEntityCleanroom(ResourceLocation metaTileEntityId) { super(metaTileEntityId); @@ -121,7 +124,7 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) { return new MetaTileEntityCleanroom(metaTileEntityId); } - protected void initializeAbilities() { + private void initializeAbilities() { this.energyContainer = new EnergyContainerList(getAbilities(MultiblockAbility.INPUT_ENERGY)); } @@ -130,23 +133,49 @@ private void resetTileAbilities() { } @Override - protected void formStructure(PatternMatchContext context) { - super.formStructure(context); + protected void formStructure(String name) { + super.formStructure(name); initializeAbilities(); - this.cleanroomFilter = context.get("FilterType"); - this.cleanroomType = cleanroomFilter.getCleanroomType(); + + renderingAABB = false; + writeCustomData(GregtechDataCodes.RENDER_UPDATE, buf -> buf.writeBoolean(false)); + + ICleanroomFilter type = allSameType(GregTechAPI.CLEANROOM_FILTERS, getSubstructure(), + "gregtech.multiblock.pattern.error.filters"); + if (type == null) { + invalidateStructure(name); + return; + } + + this.cleanroomFilter = type; + this.cleanroomType = type.getCleanroomType(); + + forEachFormed(name, (info, pos) -> { + 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 - this.cleanroomLogic.setMaxProgress(Math.max(100, - ((lDist + rDist + 1) * (bDist + fDist + 1) * hDist) - ((lDist + rDist + 1) * (bDist + fDist + 1)))); + 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()); } @Override - public void invalidateStructure() { - super.invalidateStructure(); + public void invalidateStructure(String name) { + super.invalidateStructure(name); resetTileAbilities(); this.cleanroomLogic.invalidate(); this.cleanAmount = MIN_CLEAN_AMOUNT; @@ -167,9 +196,6 @@ protected void updateFormedValid() { @Override public void checkStructurePattern() { - if (!this.isStructureFormed()) { - reinitializeStructurePattern(); - } super.checkStructurePattern(); } @@ -183,230 +209,76 @@ 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"); - } - } + protected IBlockPattern createStructurePattern() { + TraceabilityPredicate wallPredicate = states("cleanroomGlass", getCasingState(), getGlassState()); + TraceabilityPredicate energyPredicate = autoAbilities().or(abilities(MultiblockAbility.INPUT_ENERGY) + .setMinGlobalLimited(1).setMaxGlobalLimited(3)); - // 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(); - } + 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(() -> 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() == 0; + boolean botAisle = c.x() == -b[0]; + + 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[4] || c.y() == b[5]) intersects++; + // char dir is front, so its bounds[4] and bounds[5] + if (c.z() == b[2] || c.z() == -b[3]) 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; + } - TraceabilityPredicate wallPredicate = states(getCasingState(), getGlassState()); - TraceabilityPredicate basePredicate = autoAbilities().or(abilities(MultiblockAbility.INPUT_ENERGY) - .setMinGlobalLimited(1).setMaxGlobalLimited(3)); + // 1 intersect means it is a face + if (intersects == 1) { + if (topAisle) return filterPredicate; + return facePredicate; + } - // 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()) + // intersects == 0, so its not a face + return innerPredicate; + }) .build(); } @NotNull protected TraceabilityPredicate filterPredicate() { - return new TraceabilityPredicate(blockWorldState -> { - 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", - cleanroomFilter); - if (!currentFilter.getCleanroomType().equals(cleanroomFilter.getCleanroomType())) { - blockWorldState.setError(new PatternStringError("gregtech.multiblock.pattern.error.filters")); - return false; - } - blockWorldState.getMatchContext().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 -> GregTechAPI.CLEANROOM_FILTERS.containsKey(worldState.getBlockState()) ? null : + PatternError.PLACEHOLDER, + map -> GregTechAPI.CLEANROOM_FILTERS.entrySet().stream() + .filter(e -> e.getValue().getCleanroomType() != null) + .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)) + .addTooltips("gregtech.multiblock.pattern.error.filters"); } @SideOnly(Side.CLIENT) @@ -429,37 +301,30 @@ protected IBlockState getGlassState() { @NotNull protected static TraceabilityPredicate doorPredicate() { return new TraceabilityPredicate( - blockWorldState -> blockWorldState.getBlockState().getBlock() instanceof BlockDoor); + worldState -> worldState.getBlockState().getBlock() instanceof BlockDoor ? null : + PatternError.PLACEHOLDER); } @NotNull protected TraceabilityPredicate innerPredicate() { - return new TraceabilityPredicate(blockWorldState -> { + return new TraceabilityPredicate(worldState -> { // all non-MetaTileEntities are allowed inside by default - TileEntity tileEntity = blockWorldState.getTileEntity(); - if (!(tileEntity instanceof IGregTechTileEntity)) return true; - - MetaTileEntity metaTileEntity = ((IGregTechTileEntity) tileEntity).getMetaTileEntity(); + TileEntity te = worldState.getTileEntity(); + if (!(te instanceof IGregTechTileEntity)) return null; - // always ban other cleanrooms, can cause problems otherwise - if (metaTileEntity instanceof ICleanroomProvider) - return false; + MetaTileEntity mte = ((IGregTechTileEntity) te).getMetaTileEntity(); - if (isMachineBanned(metaTileEntity)) - return false; - - // the machine does not need a cleanroom, so do nothing more - if (!(metaTileEntity instanceof ICleanroomReceiver cleanroomReceiver)) return true; - - // give the machine this cleanroom if it doesn't have this one - if (cleanroomReceiver.getCleanroom() != this) { - cleanroomReceiver.setCleanroom(this); - cleanroomReceivers.add(cleanroomReceiver); - } - return true; + return isMachineBanned(mte) || mte instanceof ICleanroomProvider ? PatternError.PLACEHOLDER : null; }); } + @Override + protected boolean checkUncachedPattern(IBlockPattern pattern) { + mat.identity(); + defaultTranslate(); + return pattern.checkPatternAt(getWorld(), mat.mat); + } + @Override public SoundEvent getBreakdownSound() { return GTSoundEvents.BREAKDOWN_MECHANICAL; @@ -514,11 +379,77 @@ protected void addDisplayText(List textList) { "gregtech.multiblock.cleanroom.low_tier", energyNeeded)); } }) + .addCustom(tl -> { + if (isStructureFormed()) return; + + 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 TextComponentTranslation("gregtech.multiblock.render." + !renderingAABB), + "render:" + renderingAABB)); + }) .addEnergyUsageExactLine(isClean() ? 4 : GTValues.VA[getEnergyTier()]) .addWorkingStatusLine() .addProgressLine(getProgressPercent() / 100.0); } + protected ITextComponent getWithButton(EnumFacing facing) { + String name = facing.name(); + + ITextComponent button = new TextComponentTranslation("gregtech.direction." + facing.getName().toLowerCase( + Locale.ROOT)).appendText(": " + bounds[facing.ordinal()]); + button.appendText(" "); + button.appendSibling(withButton(new TextComponentString("[-]"), name + ":-")); + button.appendText(" "); + button.appendSibling(withButton(new TextComponentString("[+]"), name + ":+")); + return button; + } + + @Override + protected void handleDisplayClick(String componentData, Widget.ClickData clickData) { + super.handleDisplayClick(componentData, clickData); + + 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 "DOWN" -> bounds[0] = MathHelper.clamp(bounds[0] + getFactor(data[1]), MIN_DEPTH, MAX_DEPTH); + case "NORTH" -> bounds[2] = MathHelper.clamp(bounds[2] + getFactor(data[1]), MIN_RADIUS, MAX_RADIUS); + case "SOUTH" -> bounds[3] = MathHelper.clamp(bounds[3] + getFactor(data[1]), MIN_RADIUS, MAX_RADIUS); + case "WEST" -> bounds[4] = MathHelper.clamp(bounds[4] + getFactor(data[1]), MIN_RADIUS, MAX_RADIUS); + case "EAST" -> bounds[5] = MathHelper.clamp(bounds[5] + getFactor(data[1]), MIN_RADIUS, MAX_RADIUS); + default -> { + return; + } + } + + writeCustomData(GregtechDataCodes.UPDATE_STRUCTURE_SIZE, buf -> buf.writeVarIntArray(bounds)); + + getSubstructure().clearCache(); + } + + 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(), false) @@ -558,6 +489,41 @@ 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); @@ -645,6 +611,8 @@ public long getEnergyInputPerSecond() { } public boolean drainEnergy(boolean simulate) { + if (energyContainer == null) return false; + long energyToDrain = isClean() ? 4 : GTValues.VA[getEnergyTier()]; long resultEnergy = energyContainer.getEnergyStored() - energyToDrain; @@ -669,53 +637,47 @@ public T getCapability(Capability capability, EnumFacing side) { 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(); + 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); } } @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); + data.setIntArray("bounds", bounds); return this.cleanroomLogic.writeToNBT(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); + 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"); + } } @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 +685,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); } @@ -739,42 +696,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.where('F', entry.getKey()).build())); - - return shapeInfo; - } - @Override protected boolean shouldShowVoidingModeButton() { 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..fe3795d0ea7 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,9 +8,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.PatternMatchContext; +import gregtech.api.pattern.pattern.BlockPattern; +import gregtech.api.pattern.pattern.FactoryBlockPattern; import gregtech.api.recipes.RecipeMaps; import gregtech.api.recipes.logic.OCResult; import gregtech.api.recipes.properties.RecipePropertyStorage; @@ -52,11 +52,11 @@ 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") .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()) @@ -122,19 +122,20 @@ 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), + "gregtech.multiblock.pattern.error.coils"); + 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 5b52829f001..d61a74f1af3 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,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.PatternMatchContext; +import gregtech.api.pattern.pattern.BlockPattern; +import gregtech.api.pattern.pattern.FactoryBlockPattern; import gregtech.api.util.TextFormattingUtil; import gregtech.client.renderer.ICubeRenderer; import gregtech.client.renderer.texture.Textures; @@ -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; } @@ -158,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 2c7eea8410a..14bdc67f86c 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,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.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.GTTransferUtils; @@ -37,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.*; @@ -71,8 +70,9 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) { * a properly overriden {@link DistillationTowerLogicHandler#determineOrderedFluidOutputs()} */ @Override - protected Function multiblockPartSorter() { - return RelativeDirection.UP.getSorter(getFrontFacing(), getUpwardsFacing(), isFlipped()); + protected ToIntFunction multiblockPartSorter() { + // technically doesn't matter since extended facing is false you can just sort by y + return pos -> mat.unapply(pos).y(); } /** @@ -103,24 +103,25 @@ 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); + protected void formStructure(String name) { + super.formStructure(name); + // todo validate layer stuff + if (this.handler == null) return; + handler.determineLayerCount((BlockPattern) getSubstructure()); handler.determineOrderedFluidOutputs(); } @Override - public void invalidateStructure() { - super.invalidateStructure(); + public void invalidateStructure(String name) { + super.invalidateStructure(name); if (this.handler != null) handler.invalidate(); } @Override protected @NotNull BlockPattern createStructurePattern() { - return FactoryBlockPattern.start(RIGHT, FRONT, UP) - .aisle("YSY", "YYY", "YYY") - .aisle("XXX", "X#X", "XXX").setRepeatable(1, 11) + return FactoryBlockPattern.start(UP, FRONT, RIGHT) + .aisle("YYY", "YYY", "YSY") + .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/MetaTileEntityElectricBlastFurnace.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityElectricBlastFurnace.java index 89c277d7c8d..53faada3037 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,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.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; @@ -23,18 +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.BlockWireCoil.CoilType; 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; @@ -48,9 +41,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Comparator; +import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Map; import static gregtech.api.util.RelativeDirection.*; @@ -93,13 +87,14 @@ 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), + "gregtech.multiblock.pattern.error.coils"); + 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 +102,8 @@ protected void formStructure(PatternMatchContext context) { } @Override - public void invalidateStructure() { - super.invalidateStructure(); + public void invalidateStructure(String name) { + super.invalidateStructure(name); this.blastFurnaceTemperature = 0; } @@ -118,11 +113,11 @@ 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") .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))) @@ -132,6 +127,16 @@ protected BlockPattern createStructurePattern() { .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); } @@ -178,30 +183,6 @@ public SoundEvent getBreakdownSound() { return GTSoundEvents.BREAKDOWN_ELECTRICAL; } - @Override - public List getMatchingShapes() { - ArrayList shapeInfo = new ArrayList<>(); - MultiblockShapeInfo.Builder builder = MultiblockShapeInfo.builder(RIGHT, DOWN, FRONT) - .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); - GregTechAPI.HEATING_COILS.entrySet().stream() - .sorted(Comparator.comparingInt(entry -> entry.getValue().getTier())) - .forEach(entry -> shapeInfo.add(builder.where('C', entry.getKey()).build())); - return shapeInfo; - } - @NotNull @Override public List getDataInfo() { 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..41dd80f7ed3 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,9 @@ 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.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; @@ -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(); } @@ -110,11 +109,11 @@ 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#") .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 b9a918639f3..8933f28b7c4 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,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.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; @@ -60,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; @@ -79,10 +76,12 @@ 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; +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; @@ -126,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###") @@ -140,15 +139,14 @@ 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('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))) @@ -159,48 +157,12 @@ protected BlockPattern createStructurePattern() { .build(); } + @NotNull @Override - public List getMatchingShapes() { - List shapeInfos = new ArrayList<>(); - - MultiblockShapeInfo.Builder baseBuilder = MultiblockShapeInfo.builder(RIGHT, DOWN, FRONT) - .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) @@ -249,16 +211,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 @@ -691,10 +653,8 @@ public void renderBloomEffect(@NotNull BufferBuilder buffer, @NotNull EffectRend float r = (float) (color >> 16 & 255) / 255.0F; float g = (float) (color >> 8 & 255) / 255.0F; float b = (float) (color & 255) / 255.0F; - EnumFacing relativeBack = RelativeDirection.BACK.getRelativeFacing(getFrontFacing(), getUpwardsFacing(), - isFlipped()); - EnumFacing.Axis axis = RelativeDirection.UP.getRelativeFacing(getFrontFacing(), getUpwardsFacing(), isFlipped()) - .getAxis(); + EnumFacing relativeBack = getFrontFacing().getOpposite(); + EnumFacing.Axis axis = getUpwardsFacing().getAxis(); buffer.begin(GL11.GL_QUAD_STRIP, DefaultVertexFormats.POSITION_COLOR); RenderBufferHelper.renderRing(buffer, @@ -716,8 +676,7 @@ public boolean shouldRenderBloomEffect(@NotNull EffectRenderContext context) { public AxisAlignedBB getRenderBoundingBox() { EnumFacing relativeRight = RelativeDirection.RIGHT.getRelativeFacing(getFrontFacing(), getUpwardsFacing(), isFlipped()); - EnumFacing relativeBack = RelativeDirection.BACK.getRelativeFacing(getFrontFacing(), getUpwardsFacing(), - isFlipped()); + EnumFacing relativeBack = getFrontFacing().getOpposite(); return new AxisAlignedBB( this.getPos().offset(relativeBack).offset(relativeRight, 6), 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..53dd1ce8bd0 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,9 @@ 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.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; @@ -71,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) { @@ -97,16 +93,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(); } @@ -199,15 +195,17 @@ 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())) - .where('X', abilities(MultiblockAbility.HPCA_COMPONENT)) + .where('X', + abilities(() -> GTUtility.cross(frontFacing, upwardsFacing), + MultiblockAbility.HPCA_COMPONENT)) .where('C', states(getCasingState()).setMinGlobalLimited(5) .or(maintenancePredicate()) .or(abilities(MultiblockAbility.INPUT_ENERGY).setMinGlobalLimited(1)) @@ -231,7 +229,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") @@ -913,16 +911,15 @@ public TextureArea getComponentTexture(int index) { public void tryGatherClientComponents(World world, BlockPos pos, EnumFacing frontFacing, EnumFacing upwardsFacing, boolean flip) { - EnumFacing relativeUp = RelativeDirection.UP.getRelativeFacing(frontFacing, upwardsFacing, flip); if (components.isEmpty()) { BlockPos testPos = pos .offset(frontFacing.getOpposite(), 3) - .offset(relativeUp, 3); + .offset(upwardsFacing, 3); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { - BlockPos tempPos = testPos.offset(frontFacing, j).offset(relativeUp.getOpposite(), i); + BlockPos tempPos = testPos.offset(frontFacing, j).offset(upwardsFacing.getOpposite(), i); TileEntity te = world.getTileEntity(tempPos); if (te instanceof IHPCAComponentHatch hatch) { components.add(hatch); 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..c9e9a26a18e 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; @@ -33,11 +33,11 @@ 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") .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 a15161260ee..b4dfea874a8 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,11 @@ 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.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; import gregtech.client.renderer.ICubeRenderer; import gregtech.client.renderer.texture.Textures; @@ -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; @@ -52,13 +56,29 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) { } @Override - protected BlockPattern createStructurePattern() { + protected @NotNull BlockPattern createStructurePattern() { TraceabilityPredicate casing = states(getCasingState()).setMinGlobalLimited(10); TraceabilityPredicate abilities = autoAbilities(); + + // todo remove + 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", "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())) @@ -71,7 +91,7 @@ protected BlockPattern createStructurePattern() { @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', @@ -140,6 +160,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(DEFAULT_STRUCTURE)).getAisleStrategy()) + .getMultiAisleRepeats(1))); + } + @SideOnly(Side.CLIENT) @NotNull @Override 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..f5fb25a8d6b 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,9 @@ 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.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; @@ -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(); } @@ -185,11 +184,11 @@ 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#") .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 71fca7ba8d4..35ee902b448 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,9 +10,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.PatternMatchContext; +import gregtech.api.pattern.pattern.BlockPattern; +import gregtech.api.pattern.pattern.FactoryBlockPattern; import gregtech.api.recipes.RecipeBuilder; import gregtech.api.recipes.RecipeMaps; import gregtech.api.recipes.logic.OCParams; @@ -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,21 @@ 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), + "gregtech.multiblock.pattern.error.coils"); + 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; } @@ -128,9 +127,9 @@ public void invalidateStructure() { @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 fc49827d51b..cd28a1dbeac 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,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.PatternMatchContext; +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; @@ -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(); } @@ -101,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 7814f3dccc9..d477dcd0036 100644 --- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java +++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityPowerSubstation.java @@ -11,7 +11,10 @@ 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.GTUtility; import gregtech.api.util.TextComponentUtil; import gregtech.api.util.TextFormattingUtil; import gregtech.client.renderer.ICubeRenderer; @@ -20,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; @@ -96,8 +97,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)); @@ -110,15 +111,8 @@ 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(); @@ -132,8 +126,18 @@ protected void formStructure(PatternMatchContext context) { this.passiveDrain = this.energyBank.getPassiveDrainPerTick(); } + protected List determineBatteryParts() { + List data = new ArrayList<>(); + for (BlockInfo info : getSubstructure().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. @@ -144,7 +148,7 @@ public void invalidateStructure() { averageInLastSec = 0; netOutLastSec = 0; averageOutLastSec = 0; - super.invalidateStructure(); + super.invalidateStructure(name); } @Override @@ -226,10 +230,10 @@ 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) + .aisleRepeatable(1, MAX_BATTERY_LAYERS, "GGGGG", "GBBBG", "GBBBG", "GBBBG", "GGGGG") .aisle("GGGGG", "GGGGG", "GGGGG", "GGGGG", "GGGGG") .where('S', selfPredicate()) .where('C', states(getCasingState())) @@ -244,35 +248,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.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() { @@ -284,22 +268,11 @@ protected IBlockState getGlassState() { } protected static final Supplier BATTERY_PREDICATE = () -> new TraceabilityPredicate( - blockWorldState -> { - 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 = blockWorldState.getMatchContext().get(key); - if (wrapper == null) wrapper = new BatteryMatchWrapper(battery); - blockWorldState.getMatchContext().set(key, wrapper.increment()); - } - return true; - } - return false; - }, () -> GregTechAPI.PSS_BATTERIES.entrySet().stream() + worldState -> GregTechAPI.PSS_BATTERIES.containsKey(worldState.getBlockState()) ? null : + PatternError.PLACEHOLDER, + map -> GregTechAPI.PSS_BATTERIES.entrySet().stream() + .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)) @@ -761,19 +734,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/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java index 4759bb1dec7..dedc16cca1a 100644 --- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java +++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityProcessingArray.java @@ -16,10 +16,9 @@ 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; import gregtech.api.recipes.Recipe; import gregtech.api.recipes.RecipeMap; import gregtech.api.recipes.logic.OCParams; @@ -75,8 +74,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(); } @@ -89,9 +88,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 b4715563582..cbe1fefeae6 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,9 +8,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.PatternMatchContext; +import gregtech.api.pattern.pattern.BlockPattern; +import gregtech.api.pattern.pattern.FactoryBlockPattern; import gregtech.api.recipes.RecipeMaps; import gregtech.api.recipes.logic.OCResult; import gregtech.api.recipes.properties.RecipePropertyStorage; @@ -52,12 +52,12 @@ public MetaTileEntity createMetaTileEntity(IGregTechTileEntity tileEntity) { } @Override - protected BlockPattern createStructurePattern() { + 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()) @@ -88,13 +88,15 @@ 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), + "gregtech.multiblock.pattern.error.coils"); + if (type == null) { + invalidateStructure(name); + } else { + this.coilTier = type.getTier(); + } } @Override @@ -146,8 +148,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 50923590e11..a0d8da0dbbb 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,9 @@ 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.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; @@ -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 { @@ -64,8 +61,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); @@ -98,7 +95,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 +103,7 @@ public void invalidateStructure() { objectHolder.setLocked(false); } objectHolder = null; - super.invalidateStructure(); + super.invalidateStructure(name); } @Override @@ -122,13 +119,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()) @@ -139,13 +136,13 @@ 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(); } @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/MetaTileEntityVacuumFreezer.java b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java index 41c2a1ea1f1..e22356e437d 100644 --- a/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java +++ b/src/main/java/gregtech/common/metatileentities/multi/electric/MetaTileEntityVacuumFreezer.java @@ -1,12 +1,15 @@ 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; import gregtech.api.metatileentity.multiblock.RecipeMapMultiblockController; -import gregtech.api.pattern.BlockPattern; -import gregtech.api.pattern.FactoryBlockPattern; +import gregtech.api.pattern.OriginOffset; +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; import gregtech.client.renderer.texture.Textures; import gregtech.common.blocks.BlockMetalCasing.MetalCasingType; @@ -16,13 +19,22 @@ 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; + +// todo fix this stupid multiblock back to its original form public class MetaTileEntityVacuumFreezer extends RecipeMapMultiblockController { + protected int offset = 5; + public MetaTileEntityVacuumFreezer(ResourceLocation metaTileEntityId) { super(metaTileEntityId, RecipeMaps.VACUUM_RECIPES); } @@ -33,17 +45,51 @@ 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") .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()) .build(); } + @Override + protected void createStructurePatterns() { + super.createStructurePatterns(); + structures.put("SECOND", FactoryBlockPattern.start() + .aisle("X") + .where('X', states(getCasingState())) + .startOffset(new OriginOffset().move(RelativeDirection.FRONT, 5)) + .build()); + } + + @Override + protected void addDisplayText(List textList) { + super.addDisplayText(textList); + + ITextComponent button = new TextComponentString("Second structure offset: " + offset); + 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"))); + } + + @Override + protected void handleDisplayClick(String componentData, Widget.ClickData clickData) { + super.handleDisplayClick(componentData, clickData); + int mod = componentData.equals("add") ? 1 : -1; + offset += mod; + getSubstructure("SECOND").moveOffset(RelativeDirection.FRONT, mod); + getSubstructure("SECOND").clearCache(); + } + @SideOnly(Side.CLIENT) @Override public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) { 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..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 @@ -16,9 +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.PatternMatchContext; +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; @@ -60,6 +59,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 +396,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"); @@ -405,9 +405,9 @@ protected BlockPattern createStructurePattern() { 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) + .aisleRepeatable(3, MAX_WIDTH, slice.toString()) .aisle(end.toString()) .where('S', selfPredicate()) .where('A', states(MetaBlocks.METAL_CASING.getState(BlockMetalCasing.MetalCasingType.STEEL_SOLID)) @@ -423,26 +423,18 @@ 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<>(); 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()).getRepetitionCount(1); 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; } } @@ -529,9 +521,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); int sx = screen.getX(), sy = screen.getY(); if (sx < 0 || sx >= width || sy < 0 || sy >= height) { @@ -599,22 +589,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..f8ff3c039a7 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,9 +15,11 @@ 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; +import gregtech.api.util.GTUtility; import gregtech.client.utils.RenderUtil; import gregtech.common.covers.CoverDigitalInterface; import gregtech.common.gui.widget.WidgetARGB; @@ -62,6 +64,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 +207,25 @@ 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 = GTUtility.cross(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(GTUtility.cross(controller.getUpwardsFacing(), controller.getFrontFacing())); + + return Math.abs(pos.get(udAxis) - GreggyBlockPos.getAxis(getPos(), udAxis)); } public boolean isActive() { 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..fc90de2645b 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.BlockPattern; -import gregtech.api.pattern.FactoryBlockPattern; -import gregtech.api.pattern.PatternMatchContext; +import gregtech.api.pattern.GreggyBlockPos; +import gregtech.api.pattern.pattern.BlockPattern; +import gregtech.api.pattern.pattern.FactoryBlockPattern; import gregtech.api.recipes.RecipeMaps; import gregtech.api.unification.material.Materials; import gregtech.api.util.RelativeDirection; @@ -126,12 +126,12 @@ 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("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', @@ -190,13 +190,14 @@ 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]; } private boolean checkIntakesObstructed() { + GreggyBlockPos pos = new GreggyBlockPos(0, 0, -1); for (int left = -1; left <= 1; left++) { for (int up = -1; up <= 1; up++) { if (left == 0 && up == 0) { @@ -204,9 +205,11 @@ private boolean checkIntakesObstructed() { continue; } - final BlockPos checkPos = RelativeDirection.offsetPos( - getPos(), getFrontFacing(), getUpwardsFacing(), isFlipped(), up, left, 1); - final IBlockState state = getWorld().getBlockState(checkPos); + pos.x(left); + pos.y(up); + pos.z(-1); + BlockPos checkPos = mat.apply(pos).immutable(); + IBlockState state = getWorld().getBlockState(checkPos); if (!state.getBlock().isAir(state, getWorld(), checkPos)) { return true; } 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..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 @@ -10,9 +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.PatternMatchContext; +import gregtech.api.pattern.pattern.BlockPattern; +import gregtech.api.pattern.pattern.FactoryBlockPattern; import gregtech.api.recipes.RecipeMap; import gregtech.api.util.TextComponentUtil; import gregtech.api.util.TextFormattingUtil; @@ -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(); } @@ -184,11 +183,11 @@ 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("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/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityCleaningMaintenanceHatch.java index c7e24cb6649..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(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); @@ -63,21 +62,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/MetaTileEntityDataAccessHatch.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityDataAccessHatch.java index a50acc06ba0..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(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 50435231a3b..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(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) { @@ -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..e1b49d4ddfe 100644 --- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockNotifiablePart.java +++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityMultiblockNotifiablePart.java @@ -9,6 +9,8 @@ import net.minecraft.util.ResourceLocation; import net.minecraftforge.fluids.IFluidTank; +import org.jetbrains.annotations.NotNull; + import java.util.ArrayList; import java.util.List; @@ -68,8 +70,8 @@ private List getPartHandlers() { } @Override - public void addToMultiBlock(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); @@ -78,7 +80,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..3639c9ed525 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,12 @@ 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.Collections; +import java.util.List; import static gregtech.api.capability.GregtechDataCodes.SYNC_CONTROLLER; @@ -32,7 +39,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) { @@ -64,35 +79,100 @@ 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() { - if (getWorld() != null && getWorld().isRemote) { // check this only clientside - if (controllerTile == null && controllerPos != null) { - this.controllerTile = (MultiblockControllerBase) GTUtility.getMetaTileEntity(getWorld(), controllerPos); + 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 null; } - return controllerTile; + + 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) { - 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 +200,7 @@ public void receiveInitialSyncData(PacketBuffer buf) { super.receiveInitialSyncData(buf); if (buf.readBoolean()) { this.controllerPos = buf.readBlockPos(); - this.controllerTile = null; + this.lastController = null; } } @@ -128,58 +208,140 @@ 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; } + 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("addContr oller(MultiblockControllerBase) was called on " + getClass().getName() + + " with a mismatched class(original: " + controllerClass.getName() + ", new: " + + controller.getClass().getName() + ")! Ignoring the call."); + return; } + + if (controllers.isEmpty()) controllerClass = controller.getClass(); + + this.controllers.add(controller); + + // controllers always add at end of list, so always sync + syncLastController(); + } + + private void removeController(@NotNull MultiblockControllerBase controller) { + tryInitControllers(); + + 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()) { + syncLastController(); + } + + if (controllers.isEmpty()) { + controllerClass = null; + attachedSubstructureName = null; + } + } + + 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 public void onRemoval() { super.onRemoval(); - MultiblockControllerBase controller = getController(); - if (!getWorld().isRemote && controller != null) { - controller.invalidateStructure(); + if (getWorld().isRemote) return; + + List controllers = getControllers(); + for (int i = 0; i < controllers.size(); i++) { + controllers.get(i).invalidateStructure(); } } @Override - public void addToMultiBlock(MultiblockControllerBase controllerBase) { - setController(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++; } @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) || attachedSubstructureName == null); + } + + @Override + public int getWallshareCount() { + return wallshareCount; + } + + @Override + public String getSubstructureName() { + return attachedSubstructureName; } @Override public boolean isAttachedToMultiBlock() { - return getController() != null; + return controllers != null && !controllers.isEmpty(); } @Override public int getDefaultPaintingColor() { - return !isAttachedToMultiBlock() && hatchTexture == null ? super.getDefaultPaintingColor() : 0xFFFFFF; + return getController() == null && hatchTexture == null ? super.getDefaultPaintingColor() : 0xFFFFFF; } @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..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,14 +197,14 @@ protected boolean shouldSerializeInventories() { } @Override - public void addToMultiBlock(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); } @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/MetaTileEntityRotorHolder.java b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityRotorHolder.java index 1fd863b864f..cd38d8f24de 100644 --- a/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityRotorHolder.java +++ b/src/main/java/gregtech/common/metatileentities/multi/multiblockpart/MetaTileEntityRotorHolder.java @@ -12,6 +12,8 @@ import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; import gregtech.api.metatileentity.multiblock.IMultiblockAbilityPart; import gregtech.api.metatileentity.multiblock.MultiblockAbility; +import gregtech.api.pattern.GreggyBlockPos; +import gregtech.api.util.GTUtility; import gregtech.api.util.RelativeDirection; import gregtech.client.renderer.texture.Textures; import gregtech.common.items.behaviors.AbstractMaterialPartBehavior; @@ -163,15 +165,16 @@ public boolean isFrontFaceFree() { } private boolean checkTurbineFaceFree() { - final EnumFacing front = getFrontFacing(); - // this can be anything really, as long as it is not up/down when on Y axis - final EnumFacing upwards = front.getAxis() == EnumFacing.Axis.Y ? EnumFacing.NORTH : EnumFacing.UP; - + EnumFacing side = frontFacing.getAxis() == EnumFacing.Axis.Y ? EnumFacing.NORTH : EnumFacing.UP; + EnumFacing other = GTUtility.cross(frontFacing, side); + GreggyBlockPos pos = new GreggyBlockPos(); for (int left = -1; left <= 1; left++) { for (int up = -1; up <= 1; up++) { - // flip doesn't affect anything here since we are checking a square anyway - final BlockPos checkPos = RelativeDirection.offsetPos( - getPos(), front, upwards, false, up, left, 1); + pos.from(getPos()); + pos.offset(getFrontFacing(), 1); + pos.offset(side, left); + pos.offset(other, up); + BlockPos checkPos = pos.immutable(); final IBlockState state = getWorld().getBlockState(checkPos); if (!state.getBlock().isAir(state, getWorld(), checkPos)) { return 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 b1e53f22f4a..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(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); @@ -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..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(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 26a8fcc7947..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(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 640f7869617..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(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); @@ -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..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,14 +99,14 @@ protected void flushInventory() { } @Override - public void addToMultiBlock(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(); } @Override - public void removeFromMultiBlock(MultiblockControllerBase controllerBase) { + public void removeFromMultiBlock(@NotNull MultiblockControllerBase controllerBase) { this.autoPullTest = $ -> false; if (this.autoPull) { this.getAEFluidHandler().clearConfig(); 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..5bb49cded5b 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; @@ -49,11 +49,11 @@ 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") .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 b3a3e5fa039..e582373455e 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; @@ -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) diff --git a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java index ecbd0241f1d..8bf92d0697f 100644 --- a/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java +++ b/src/main/java/gregtech/common/metatileentities/primitive/MetaTileEntityCharcoalPileIgniter.java @@ -11,17 +11,21 @@ import gregtech.api.metatileentity.interfaces.IGregTechTileEntity; import gregtech.api.metatileentity.multiblock.IMultiblockPart; import gregtech.api.metatileentity.multiblock.MultiblockControllerBase; -import gregtech.api.pattern.*; +import gregtech.api.pattern.PatternError; +import gregtech.api.pattern.TraceabilityPredicate; +import gregtech.api.pattern.pattern.FactoryExpandablePattern; +import gregtech.api.pattern.pattern.IBlockPattern; 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; 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; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Blocks; import net.minecraft.init.SoundEvents; import net.minecraft.item.ItemFireball; @@ -30,8 +34,14 @@ 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.TextComponentTranslation; import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.capabilities.Capability; @@ -41,23 +51,28 @@ 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; 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; import stanhebben.zenscript.annotations.ZenClass; import stanhebben.zenscript.annotations.ZenMethod; -import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +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 @@ -65,6 +80,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<>(); @@ -77,9 +94,7 @@ 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[] { MIN_DEPTH, 0, MIN_RADIUS, MIN_RADIUS, MIN_RADIUS, MIN_RADIUS }; private boolean isActive; private int progressTime = 0; @@ -103,182 +118,81 @@ 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); + // this doesn't iterate over any(), so doesn't count the borders + forEachFormed(DEFAULT_STRUCTURE, (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("walls", WALL_BLOCKS.toArray(new Block[0])); + TraceabilityPredicate logPredicate = logPredicate(); + + // basically cleanroom code + return FactoryExpandablePattern.start(RelativeDirection.UP, RelativeDirection.RIGHT, RelativeDirection.FRONT) + .boundsFunction(() -> bounds) + .predicateFunction((c, b) -> { + if (c.origin()) return selfPredicate(); + + int intersects = 0; + + // aisle dir is up, so its bounds[0] and bounds[1] + boolean topAisle = c.x() == 0; + boolean botAisle = c.x() == -b[0]; + + 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[4] || c.y() == b[5]) intersects++; + // char dir is front, so its bounds[4] and bounds[5] + if (c.z() == b[2] || c.z() == -b[3]) intersects++; + + if (intersects >= 2) return any(); + + 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()) + 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(); - 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()); + @Override + public Iterator> getPreviewBuilds() { + return IntStream.range(0, WALL_BLOCKS.size()) + .mapToObj(i -> Collections.singletonMap("walls", Integer.toString(i))) + .iterator(); } - private static boolean isBlockFloor(@NotNull World world, @NotNull BlockPos.MutableBlockPos pos) { - return world.getBlockState(pos.move(EnumFacing.DOWN)).getBlock() == Blocks.BRICK_BLOCK; + @NotNull + private TraceabilityPredicate logPredicate() { + return new TraceabilityPredicate( + worldState -> worldState.getBlockState().getBlock().isWood(worldState.getWorld(), + worldState.getPos()) || + worldState.getBlockState().equals(MetaBlocks.BRITTLE_CHARCOAL.getDefaultState()) ? null : + PatternError.PLACEHOLDER); } private void setActive(boolean active) { @@ -329,6 +243,28 @@ private void convertLogBlocks() { logPositions.clear(); } + @Override + public boolean onScrewdriverClick(EntityPlayer playerIn, EnumHand hand, EnumFacing facing, + CuboidRayTraceResult hitResult) { + if (!playerIn.isSneaking()) { + if (getWorld().isRemote) return true; + + bounds[facing.ordinal()] += 1; + if (bounds[facing.ordinal()] > (facing == EnumFacing.DOWN ? MAX_DEPTH : MAX_RADIUS)) { + bounds[facing.ordinal()] = (facing == EnumFacing.DOWN ? MIN_DEPTH : MIN_RADIUS); + } + + playerIn.sendMessage( + new TextComponentTranslation("gregtech.direction." + facing.name().toLowerCase(Locale.ROOT)) + .appendText(" ") + .appendSibling(new TextComponentTranslation("gregtech.machine.miner.radius", + bounds[facing.ordinal()]))); + getSubstructure().clearCache(); + return true; + } + return super.onScrewdriverClick(playerIn, hand, facing, hitResult); + } + @SideOnly(Side.CLIENT) @Override public ICubeRenderer getBaseTexture(IMultiblockPart sourcePart) { @@ -354,55 +290,28 @@ 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); - 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); + data.setIntArray("bounds", this.bounds); return 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"); + if (data.hasKey("bounds")) System.arraycopy(data.getIntArray("bounds"), 0, bounds, 0, 6); } @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); @@ -411,9 +320,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(); @@ -422,11 +328,7 @@ public void receiveInitialSyncData(PacketBuffer buf) { @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.hDist = buf.readInt(); - } else if (dataId == GregtechDataCodes.WORKABLE_ACTIVE) { + if (dataId == GregtechDataCodes.WORKABLE_ACTIVE) { this.isActive = buf.readBoolean(); scheduleRenderUpdate(); } @@ -488,7 +390,8 @@ 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()) { if (event.getSide().isClient()) { event.setCanceled(true); event.getEntityPlayer().swingArm(EnumHand.MAIN_HAND); 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 */ diff --git a/src/main/java/gregtech/core/network/packets/PacketRecoverMTE.java b/src/main/java/gregtech/core/network/packets/PacketRecoverMTE.java index fe3f1d1f898..f458f94749f 100644 --- a/src/main/java/gregtech/core/network/packets/PacketRecoverMTE.java +++ b/src/main/java/gregtech/core/network/packets/PacketRecoverMTE.java @@ -46,6 +46,8 @@ public void executeServer(NetHandlerPlayServer handler) { TileEntity te = world.getTileEntity(pos); if (te instanceof IGregTechTileEntity holder && holder.isValid()) { holder.writeCustomData(INITIALIZE_MTE, buffer -> { + // 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 b746f67a2cd..a0aa97c71d2 100644 --- a/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java +++ b/src/main/java/gregtech/integration/jei/multiblock/MultiblockInfoRecipeWrapper.java @@ -1,14 +1,18 @@ 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; 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.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; @@ -17,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; @@ -34,13 +40,10 @@ 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; import net.minecraftforge.fml.relauncher.SideOnly; @@ -49,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; @@ -61,15 +66,19 @@ import org.jetbrains.annotations.NotNull; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; +import org.lwjgl.util.vector.Matrix4f; import java.util.*; import java.util.Map.Entry; +import java.util.function.ToLongFunction; import java.util.stream.Collectors; import javax.vecmath.Vector3f; public class MultiblockInfoRecipeWrapper implements IRecipeWrapper { + public static final BlockPos SOURCE = new BlockPos(0, 128, 0); + public static final Matrix4f TRANSFORM; private static final int MAX_PARTS = 18; private static final int PARTS_HEIGHT = 36; private static final int SLOT_SIZE = 18; @@ -77,20 +86,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<>(); @@ -121,13 +116,20 @@ public MBPattern(final WorldSceneRenderer sceneRenderer, final List p private final List predicates; private TraceabilityPredicate father; + static { + Matrix4f mat = new Matrix4f(); + TRANSFORM = GTUtility.translate(SOURCE.getX(), SOURCE.getY(), SOURCE.getZ(), mat, mat); + } + @SuppressWarnings("NewExpressionSideOnly") public MultiblockInfoRecipeWrapper(@NotNull MultiblockControllerBase controller) { this.controller = controller; Set drops = new ObjectOpenCustomHashSet<>(ItemStackHashStrategy.comparingAllButCount()); - this.patterns = controller.getMatchingShapes().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, @@ -174,13 +176,13 @@ 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 { 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(); } } @@ -350,25 +352,19 @@ 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(); - worldState.update(renderer.world, rayTraceResult.getBlockPos(), new PatternMatchContext(), - new HashMap<>(), new HashMap<>(), predicates); - for (TraceabilityPredicate.SimplePredicate common : predicates.common) { - if (common.test(worldState)) { + + worldState.setWorld(renderer.world); + worldState.setPos(rayTraceResult.getBlockPos()); + + for (TraceabilityPredicate.SimplePredicate common : predicates.simple) { + if (common.testRaw(worldState) == null) { predicateTips = common.getToolTips(predicates); break; } } - if (predicateTips == null) { - for (TraceabilityPredicate.SimplePredicate limit : predicates.limited) { - if (limit.test(worldState)) { - predicateTips = limit.getToolTips(predicates); - break; - } - } - } } if (!itemStack.isEmpty()) { tooltipBlockStack = itemStack; @@ -424,10 +420,10 @@ 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.common); - predicates.addAll(predicate.limited); + predicates.addAll(predicate.simple); predicates.removeIf(p -> p.candidates == null); this.father = predicate; setItemStackGroup(); @@ -480,120 +476,40 @@ 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<>( + private MBPattern initializePattern(@NotNull MultiblockControllerBase src, @NotNull Map map, + @NotNull Set parts) { + Map partsMap = new Object2ObjectOpenCustomHashMap<>( ItemStackHashStrategy.comparingAllButCount()); - for (Entry entry : blocks.entrySet()) { - BlockPos pos = entry.getKey(); - IBlockState state = world.getBlockState(pos); - Block block = state.getBlock(); + TrackedDummyWorld world = new TrackedDummyWorld(); - ItemStack stack = ItemStack.EMPTY; + ImmediateWorldSceneRenderer worldSceneRenderer = new ImmediateWorldSceneRenderer(world); + worldSceneRenderer.setClearColor(ConfigHolder.client.multiblockPreviewColor); - // 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; - } - } - } + MetaTileEntityHolder holder = new MetaTileEntityHolder(); + holder.setMetaTileEntity(src); + holder.getMetaTileEntity().onPlacement(); - // if we got a stack, add it to the set and map - if (!stack.isEmpty()) { - parts.add(stack); + world.setBlockState(SOURCE, src.getBlock().getDefaultState()); + world.setTileEntity(SOURCE, holder); - PartInfo partInfo = partsMap.get(stack); - if (partInfo == null) { - partInfo = new PartInfo(stack, entry.getValue()); - partsMap.put(stack, partInfo); - } - partInfo.amount++; - } - } - return partsMap.values(); - } + Set structures = src.trySubstructure(map); - @SuppressWarnings("NewExpressionSideOnly") - @NotNull - private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @NotNull Set parts) { - Map blockMap = new HashMap<>(); - MultiblockControllerBase controllerBase = null; - BlockInfo[][][] blocks = shapeInfo.getBlocks(); - 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]); - } - } + Long2ObjectMap copy = new Long2ObjectOpenHashMap<>(); + for (String structure : structures) { + Long2ObjectMap predicates = ((MultiblockControllerBase) holder.getMetaTileEntity()) + .getSubstructure(structure) + .getDefaultShape(TRANSFORM, map); + copy.putAll(predicates); + ((MultiblockControllerBase) holder.getMetaTileEntity()).autoBuild(new GregFakePlayer(world), map, + predicates); } - TrackedDummyWorld world = new TrackedDummyWorld(); - ImmediateWorldSceneRenderer worldSceneRenderer = new ImmediateWorldSceneRenderer(world); - worldSceneRenderer.setClearColor(ConfigHolder.client.multiblockPreviewColor); - world.addBlocks(blockMap); - Vector3f size = world.getSize(); 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 -> { @@ -610,28 +526,51 @@ private MBPattern initializePattern(@NotNull MultiblockShapeInfo shapeInfo, @Not world.setRenderFilter( pos -> worldSceneRenderer.renderedBlocksMap.keySet().stream().anyMatch(c -> c.contains(pos))); - Map predicateMap = new HashMap<>(); - if (controllerBase != null) { - if (controllerBase.structurePattern == null) { - controllerBase.reinitializeStructurePattern(); + 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); } - if (controllerBase.structurePattern != null) { - controllerBase.structurePattern.cache.forEach((pos, blockInfo) -> predicateMap - .put(BlockPos.fromLong(pos), (TraceabilityPredicate) blockInfo.getInfo())); + } + + 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()); + + for (ItemStack part : sortedParts) { + GTLog.logger.info(GTStringUtils.prettyPrintItemStack(part) + " with prio " + comp.applyAsLong(part)); } - 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) @@ -670,4 +609,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; + } + } } diff --git a/src/main/java/gregtech/loaders/recipe/BatteryRecipes.java b/src/main/java/gregtech/loaders/recipe/BatteryRecipes.java index c23ec372712..d037aa479a6 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(); 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