diff --git a/src/main/java/com/simibubi/create/api/connectivity/ConnectivityHandler.java b/src/main/java/com/simibubi/create/api/connectivity/ConnectivityHandler.java new file mode 100644 index 000000000..b1801c729 --- /dev/null +++ b/src/main/java/com/simibubi/create/api/connectivity/ConnectivityHandler.java @@ -0,0 +1,372 @@ +package com.simibubi.create.api.connectivity; + +import com.simibubi.create.content.contraptions.fluids.tank.CreativeFluidTankTileEntity; +import com.simibubi.create.foundation.tileEntity.IMultiTileContainer; + +import com.simibubi.create.foundation.utility.Iterate; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; + +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.IFluidTank; +import net.minecraftforge.fluids.capability.CapabilityFluidHandler; + +import net.minecraftforge.fluids.capability.IFluidHandler; + +import net.minecraftforge.items.CapabilityItemHandler; + +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Set; + +public class ConnectivityHandler { + + public static void formMulti (T be) { + SearchCache cache = new SearchCache<>(); + List frontier = new ArrayList<>(); + frontier.add(be); + formMulti(be.getType(), be.getLevel(), cache, frontier); + } + + private static void formMulti (BlockEntityType type, BlockGetter level, SearchCache cache, List frontier) { + PriorityQueue> creationQueue = makeCreationQueue(); + Set visited = new HashSet<>(); + Direction.Axis mainAxis = frontier.get(0).getMainConnectionAxis(); + + // essentially, if it's a vertical multi then the search won't be restricted by Y + // alternately, a horizontal multi search shouldn't be restricted by X or Z + int minX = (mainAxis == Direction.Axis.Y ? Integer.MAX_VALUE : Integer.MIN_VALUE); + int minY = (mainAxis != Direction.Axis.Y ? Integer.MAX_VALUE : Integer.MIN_VALUE); + int minZ = (mainAxis == Direction.Axis.Y ? Integer.MAX_VALUE : Integer.MIN_VALUE); + + for (T be : frontier) { + BlockPos pos = be.getBlockPos(); + minX = Math.min(pos.getX(), minX); + minY = Math.min(pos.getY(), minY); + minZ = Math.min(pos.getZ(), minZ); + } + if (mainAxis == Direction.Axis.Y) minX -= frontier.get(0).getMaxWidth(); + if (mainAxis != Direction.Axis.Y) minY -= frontier.get(0).getMaxWidth(); + if (mainAxis == Direction.Axis.Y) minZ -= frontier.get(0).getMaxWidth(); + + while (!frontier.isEmpty()) { + T part = frontier.remove(0); + BlockPos partPos = part.getBlockPos(); + if (visited.contains(partPos)) continue; + + visited.add(partPos); + + int amount = tryToFormNewMulti(part, cache, true); + if (amount > 1) { + creationQueue.add(Pair.of(amount, part)); + } + + for (Direction.Axis axis : Iterate.axes) { + Direction dir = Direction.get(Direction.AxisDirection.NEGATIVE, axis); + BlockPos next = partPos.relative(dir); + + if (next.getX() <= minX || next.getY() <= minY || next.getZ() <= minZ) continue; + if (visited.contains(next)) continue; + T nextBe = partAt(type, level, next); + if (nextBe == null) continue; + if (nextBe.isRemoved()) continue; + frontier.add(nextBe); + } + } + visited.clear(); + + while (!creationQueue.isEmpty()) { + Pair next = creationQueue.poll(); + T toCreate = next.getValue(); + if (visited.contains(toCreate.getBlockPos())) continue; + + visited.add(toCreate.getBlockPos()); + tryToFormNewMulti(toCreate, cache, false); + } + } + + private static int tryToFormNewMulti (T be, SearchCache cache, boolean simulate) { + int bestWidth = 1; + int bestAmount = -1; + if (!be.isController()) return 0; + + int radius = be.getMaxWidth(); + for (int w = 1; w <= radius; w++) { + int amount = tryToFormNewMultiOfWidth(be, w, cache, true); + if (amount < bestAmount) continue; + bestWidth = w; + bestAmount = amount; + } + + if (!simulate) { + int beWidth = be.getWidth(); + if (beWidth == bestWidth && beWidth*beWidth * be.getHeight() == bestAmount) return bestAmount; + + splitMultiAndInvalidate(be, cache, false); + if (be.hasFluid()) be.setTankSize(0, bestAmount); + + tryToFormNewMultiOfWidth(be, bestWidth, cache, false); + + be.preventConnectivityUpdate(); + be.setWidth(bestWidth); + be.setHeight(bestAmount / bestWidth / bestWidth); + be.notifyMultiUpdated(); + } + return bestAmount; + } + + private static int tryToFormNewMultiOfWidth (T be, int width, SearchCache cache, boolean simulate) { + int amount = 0; + int height = 0; + BlockEntityType type = be.getType(); + Level level = be.getLevel(); + if (level == null) return 0; + BlockPos origin = be.getBlockPos(); + + // optional fluid handling + IFluidTank beTank = null; + FluidStack fluid = FluidStack.EMPTY; + if (be.hasFluid()) { + beTank = be.getTank(0); + fluid = beTank.getFluid(); + } + Direction.Axis axis = be.getMainConnectionAxis(); + + Search: + for (int yOffset = 0; yOffset < be.getMaxLength(axis, width); yOffset++) { + for (int xOffset = 0; xOffset < width; xOffset++) { + for (int zOffset = 0; zOffset < width; zOffset++) { + BlockPos pos = switch (axis) { + case X -> origin.offset(yOffset, xOffset, zOffset); + case Y -> origin.offset(xOffset, yOffset, zOffset); + case Z -> origin.offset(xOffset, zOffset, yOffset); + }; + Optional part = cache.getOrCache(type, level, pos); + if (part.isEmpty()) break Search; + + T controller = part.get(); + int otherWidth = controller.getWidth(); + if (otherWidth > width) break Search; + if (otherWidth == width && controller.getHeight() == be.getMaxLength(axis, width)) break Search; + + Direction.Axis conAxis = controller.getMainConnectionAxis(); + if (axis != conAxis) break Search; + + BlockPos conPos = controller.getBlockPos(); + if (!conPos.equals(origin)) { + if (axis == Direction.Axis.Y) { // vertical multi, like a FluidTank + if (conPos.getX() < origin.getX()) break Search; + if (conPos.getZ() < origin.getZ()) break Search; + if (conPos.getX() + otherWidth > origin.getX() + width) break Search; + if (conPos.getZ() + otherWidth > origin.getZ() + width) break Search; + } else { // horizontal multi, like an ItemVault + if (axis == Direction.Axis.Z && conPos.getX() < origin.getX()) break Search; + if (conPos.getY() < origin.getY()) break Search; + if (axis == Direction.Axis.X && conPos.getZ() < origin.getZ()) break Search; + if (axis == Direction.Axis.Z && conPos.getX() + otherWidth > origin.getX() + width) break Search; + if (conPos.getY() + otherWidth > origin.getY() + width) break Search; + if (axis == Direction.Axis.X && conPos.getZ() + otherWidth > origin.getZ() + width) break Search; + } + } + FluidStack otherFluid = controller.getFluid(0); + if (!fluid.isEmpty() && !otherFluid.isEmpty() && !fluid.isFluidEqual(otherFluid)) break Search; + } + } + amount += width * width; + height++; + } + + if (simulate) return amount; + + Object extraData = be.getExtraData(); + + for (int yOffset = 0; yOffset < height; yOffset++) { + for (int xOffset = 0; xOffset < width; xOffset++) { + for (int zOffset = 0; zOffset < width; zOffset++) { + BlockPos pos = switch (axis) { + case X -> origin.offset(yOffset, xOffset, zOffset); + case Y -> origin.offset(xOffset, yOffset, zOffset); + case Z -> origin.offset(xOffset, zOffset, yOffset); + }; + T part = partAt(type, level, pos); + if (part == null) continue; + if (part == be) continue; + + extraData = be.modifyExtraData(extraData); + + if (part.hasFluid()) { + IFluidTank tankAt = part.getTank(0); + FluidStack fluidAt = tankAt.getFluid(); + if (!fluidAt.isEmpty()) { + // making this generic would be a rather large mess, unfortunately + if (beTank != null && fluid.isEmpty() && beTank instanceof CreativeFluidTankTileEntity.CreativeSmartFluidTank) { + ((CreativeFluidTankTileEntity.CreativeSmartFluidTank)beTank).setContainedFluid(fluidAt); + } + } + tankAt.drain(tankAt.getCapacity(), IFluidHandler.FluidAction.EXECUTE); + } + + splitMultiAndInvalidate(part, cache, false); + part.setController(origin); + part.preventConnectivityUpdate(); + cache.put(pos, be); + part.setHeight(height); + part.notifyMultiUpdated(); + } + } + } + be.setExtraData(extraData); + be.notifyMultiUpdated(); + return amount; + } + + public static void splitMulti (T be) { + splitMultiAndInvalidate(be, null, false); + } + + // tryReconnect helps whenever only a few tanks have been removed + private static void splitMultiAndInvalidate (T be, @Nullable SearchCache cache, boolean tryReconnect) { + Level level = be.getLevel(); + if (level == null) return; + + be = be.getControllerTE(); + if (be == null) return; + + int height = be.getHeight(); + int width = be.getWidth(); + if (width == 1 && height == 1) return; + + BlockPos origin = be.getBlockPos(); + List frontier = new ArrayList<>(); + Direction.Axis axis = be.getMainConnectionAxis(); + + // fluid handling, if present + FluidStack toDistribute = FluidStack.EMPTY; + int maxCapacity = 0; + if (be.hasFluid()) { + toDistribute = be.getFluid(0); + maxCapacity = be.getTankSize(0); + if (!toDistribute.isEmpty() && !be.isRemoved()) toDistribute.shrink(maxCapacity); + be.setTankSize(0, 1); + } + + for (int yOffset = 0; yOffset < height; yOffset++) { + for (int xOffset = 0; xOffset < width; xOffset++) { + for (int zOffset = 0; zOffset < width; zOffset++) { + BlockPos pos = switch (axis) { + case X -> origin.offset(yOffset, xOffset, zOffset); + case Y -> origin.offset(xOffset, yOffset, zOffset); + case Z -> origin.offset(xOffset, zOffset, yOffset); + }; + T partAt = partAt(be.getType(), level, pos); + if (partAt == null) continue; + if (!partAt.getController().equals(origin)) continue; + + T controllerBE = partAt.getControllerTE(); + partAt.setExtraData ( (controllerBE == null ? null : controllerBE.getExtraData())); + partAt.removeController(true); + + if (!toDistribute.isEmpty() && partAt != be) { + FluidStack copy = toDistribute.copy(); + IFluidTank tank = partAt.getTank(0); + // making this generic would be a rather large mess, unfortunately + if (tank instanceof CreativeFluidTankTileEntity.CreativeSmartFluidTank creativeTank) { + if (creativeTank.isEmpty()) creativeTank.setContainedFluid(toDistribute); + } + else { + int split = Math.min(maxCapacity, toDistribute.getAmount()); + copy.setAmount(split); + toDistribute.shrink(split); + tank.fill(copy, IFluidHandler.FluidAction.EXECUTE); + } + } + if (tryReconnect) { + frontier.add(partAt); + partAt.preventConnectivityUpdate(); + } + if (cache != null) { + cache.put(pos, partAt); + } + } + } + } + if (be.hasItems()) { + be.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).invalidate(); + } + if (be.hasFluid()) { + be.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY).invalidate(); + } + if (tryReconnect) { + formMulti(be.getType(), level, cache == null ? new SearchCache<>() : cache, frontier); + } + } + + private static PriorityQueue> makeCreationQueue () { + return new PriorityQueue<>((one, two) -> two.getKey() - one.getKey()); + } + + @Nullable + public static T partAt (BlockEntityType type, BlockGetter level, BlockPos pos) { + BlockEntity be = level.getBlockEntity(pos); + if (be != null && be.getType() == type) return checked(be); + + return null; + } + + public static boolean isConnected (BlockGetter level, BlockPos pos, BlockPos other) { + T one = checked(level.getBlockEntity(pos)); + T two = checked(level.getBlockEntity(other)); + if (one == null || two == null) return false; + return one.getController().equals(two.getController()); + } + + @Nullable + @SuppressWarnings("unchecked") + private static T checked (BlockEntity be) { + if (be instanceof IMultiTileContainer) return (T)be; + return null; + } + + private static class SearchCache { + Map> controllerMap; + + public SearchCache () { controllerMap = new HashMap<>(); } + + void put (BlockPos pos, T target) { controllerMap.put(pos, Optional.of(target)); } + + void putEmpty (BlockPos pos) { controllerMap.put(pos, Optional.empty()); } + + boolean hasVisited (BlockPos pos) { return controllerMap.containsKey(pos); } + + Optional getOrCache (BlockEntityType type, BlockGetter level, BlockPos pos) { + if (hasVisited(pos)) return controllerMap.get(pos); + + T partAt = partAt(type, level, pos); + if (partAt == null) { + putEmpty(pos); + return Optional.empty(); + } + T controller = checked(level.getBlockEntity(partAt.getController())); + if (controller == null) { + putEmpty(pos); + return Optional.empty(); + } + put(pos, controller); + return Optional.of(controller); + } + } +} diff --git a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java index 1e9819474..18e989689 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java +++ b/src/main/java/com/simibubi/create/content/contraptions/components/structureMovement/BlockMovementChecks.java @@ -5,6 +5,7 @@ import java.util.List; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllTags.AllBlockTags; +import com.simibubi.create.api.connectivity.ConnectivityHandler; import com.simibubi.create.content.contraptions.components.actors.AttachedActorBlock; import com.simibubi.create.content.contraptions.components.actors.HarvesterBlock; import com.simibubi.create.content.contraptions.components.actors.PloughBlock; @@ -330,7 +331,7 @@ public class BlockMovementChecks { return direction.getAxis() != state.getValue(SailBlock.FACING) .getAxis(); if (state.getBlock() instanceof FluidTankBlock) - return FluidTankConnectivityHandler.isConnected(world, pos, pos.relative(direction)); + return ConnectivityHandler.isConnected(world, pos, pos.relative(direction)); //FluidTankConnectivityHandler.isConnected(world, pos, pos.relative(direction)); if (state.getBlock() instanceof ItemVaultBlock) return ItemVaultConnectivityHandler.isConnected(world, pos, pos.relative(direction)); if (AllBlocks.STICKER.has(state) && state.getValue(StickerBlock.EXTENDED)) { @@ -349,7 +350,7 @@ public class BlockMovementChecks { return state.getValue(HarvesterBlock.FACING) == facing; if (AllBlocks.MECHANICAL_PLOUGH.has(state)) return state.getValue(PloughBlock.FACING) == facing; - + if (AllBlocks.CART_ASSEMBLER.has(state)) return Direction.DOWN == facing; if (AllBlocks.MECHANICAL_SAW.has(state)) diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankBlock.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankBlock.java index f128fba7a..920d78f34 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankBlock.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankBlock.java @@ -1,6 +1,7 @@ package com.simibubi.create.content.contraptions.fluids.tank; import com.simibubi.create.AllTileEntities; +import com.simibubi.create.api.connectivity.ConnectivityHandler; import com.simibubi.create.content.contraptions.fluids.actors.GenericItemFilling; import com.simibubi.create.content.contraptions.fluids.tank.CreativeFluidTankTileEntity.CreativeSmartFluidTank; import com.simibubi.create.content.contraptions.processing.EmptyingByBasin; @@ -90,10 +91,10 @@ public class FluidTankBlock extends Block implements IWrenchable, ITE p_206840_1_) { p_206840_1_.add(TOP, BOTTOM, SHAPE); } - + @Override public int getLightEmission(BlockState state, BlockGetter world, BlockPos pos) { - FluidTankTileEntity tankAt = FluidTankConnectivityHandler.anyTankAt(world, pos); + FluidTankTileEntity tankAt = ConnectivityHandler.partAt(getTileEntityType(), world, pos); //FluidTankConnectivityHandler.anyTankAt(world, pos); if (tankAt == null) return 0; FluidTankTileEntity controllerTE = tankAt.getControllerTE(); @@ -120,7 +121,7 @@ public class FluidTankBlock extends Block implements IWrenchable, ITE getTileEntityClass() { return FluidTankTileEntity.class; } - + @Override public BlockEntityType getTileEntityType() { return creative ? AllTileEntities.CREATIVE_FLUID_TANK.get() : AllTileEntities.FLUID_TANK.get(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankCTBehaviour.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankCTBehaviour.java index 283981a69..0e9843af9 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankCTBehaviour.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankCTBehaviour.java @@ -1,5 +1,6 @@ package com.simibubi.create.content.contraptions.fluids.tank; +import com.simibubi.create.api.connectivity.ConnectivityHandler; import com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry; import com.simibubi.create.foundation.block.connected.HorizontalCTBehaviour; @@ -21,6 +22,6 @@ public class FluidTankCTBehaviour extends HorizontalCTBehaviour { @Override public boolean connectsTo(BlockState state, BlockState other, BlockAndTintGetter reader, BlockPos pos, BlockPos otherPos, Direction face) { - return state.getBlock() == other.getBlock() && FluidTankConnectivityHandler.isConnected(reader, pos, otherPos); + return state.getBlock() == other.getBlock() && ConnectivityHandler.isConnected(reader, pos, otherPos); //FluidTankConnectivityHandler.isConnected(reader, pos, otherPos); } } diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankItem.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankItem.java index b2697c105..80745e342 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankItem.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankItem.java @@ -1,5 +1,8 @@ package com.simibubi.create.content.contraptions.fluids.tank; +import com.simibubi.create.AllTileEntities; +import com.simibubi.create.api.connectivity.ConnectivityHandler; + import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -71,7 +74,7 @@ public class FluidTankItem extends BlockItem { if (!FluidTankBlock.isTank(placedOnState)) return; - FluidTankTileEntity tankAt = FluidTankConnectivityHandler.anyTankAt(world, placedOnPos); + FluidTankTileEntity tankAt = ConnectivityHandler.partAt(AllTileEntities.FLUID_TANK.get(), world, placedOnPos); //FluidTankConnectivityHandler.anyTankAt(world, placedOnPos); if (tankAt == null) return; FluidTankTileEntity controllerTE = tankAt.getControllerTE(); diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankModel.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankModel.java index 34a326704..5796a7e59 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankModel.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankModel.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Random; import com.simibubi.create.AllSpriteShifts; +import com.simibubi.create.api.connectivity.ConnectivityHandler; import com.simibubi.create.foundation.block.connected.CTModel; import com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry; import com.simibubi.create.foundation.utility.Iterate; @@ -28,20 +29,20 @@ public class FluidTankModel extends CTModel { public static FluidTankModel standard(BakedModel originalModel) { return new FluidTankModel(originalModel, AllSpriteShifts.FLUID_TANK, AllSpriteShifts.COPPER_CASING); } - + public static FluidTankModel creative(BakedModel originalModel) { return new FluidTankModel(originalModel, AllSpriteShifts.CREATIVE_FLUID_TANK, AllSpriteShifts.CREATIVE_CASING); } - + private FluidTankModel(BakedModel originalModel, CTSpriteShiftEntry side, CTSpriteShiftEntry top) { super(originalModel, new FluidTankCTBehaviour(side, top)); } - + @Override protected Builder gatherModelData(Builder builder, BlockAndTintGetter world, BlockPos pos, BlockState state) { CullData cullData = new CullData(); for (Direction d : Iterate.horizontalDirections) - cullData.setCulled(d, FluidTankConnectivityHandler.isConnected(world, pos, pos.relative(d))); + cullData.setCulled(d, ConnectivityHandler.isConnected(world, pos, pos.relative(d))); //FluidTankConnectivityHandler.isConnected(world, pos, pos.relative(d))); return super.gatherModelData(builder, world, pos, state).withInitial(CULL_PROPERTY, cullData); } diff --git a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankTileEntity.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankTileEntity.java index d1eed27d2..d4f0d71c3 100644 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankTileEntity.java +++ b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankTileEntity.java @@ -7,6 +7,8 @@ import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import com.simibubi.create.Create; +import com.simibubi.create.api.connectivity.ConnectivityHandler; import com.simibubi.create.content.contraptions.fluids.tank.FluidTankBlock.Shape; import com.simibubi.create.content.contraptions.goggles.IHaveGoggleInformation; import com.simibubi.create.foundation.config.AllConfigs; @@ -79,7 +81,8 @@ public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleI return; if (!isController()) return; - FluidTankConnectivityHandler.formTanks(this); + //FluidTankConnectivityHandler.formTanks(this); + ConnectivityHandler.formMulti(this); } @Override @@ -103,7 +106,7 @@ public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleI if (fluidLevel != null) fluidLevel.tick(); } - + @Override public BlockPos getLastKnownPos() { return lastKnownPos; @@ -145,7 +148,7 @@ public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleI for (int xOffset = 0; xOffset < width; xOffset++) { for (int zOffset = 0; zOffset < width; zOffset++) { BlockPos pos = this.worldPosition.offset(xOffset, yOffset, zOffset); - FluidTankTileEntity tankAt = FluidTankConnectivityHandler.anyTankAt(level, pos); + FluidTankTileEntity tankAt = ConnectivityHandler.partAt(getType(), level, pos); //FluidTankConnectivityHandler.anyTankAt(level, pos); if (tankAt == null) continue; level.updateNeighbourForOutputSignal(pos, tankAt.getBlockState() @@ -161,7 +164,7 @@ public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleI setChanged(); sendData(); } - + if (isVirtual()) { if (fluidLevel == null) fluidLevel = new InterpolatedChasingValue().start(getFillState()); @@ -462,4 +465,82 @@ public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleI this.fluidLevel = fluidLevel; } + @Override + public void preventConnectivityUpdate() { updateConnectivity = false; } + + @Override + public void notifyMultiUpdated () { + BlockState state = this.getBlockState(); + if (FluidTankBlock.isTank(state)) { // safety + state = state.setValue(FluidTankBlock.BOTTOM, getController().getY() == getBlockPos().getY()); + state = state.setValue(FluidTankBlock.TOP, getController().getY() + height - 1 == getBlockPos().getY()); + level.setBlock(getBlockPos(), state, 22); + } + setWindows(window); + onFluidStackChanged(tankInventory.getFluid()); + setChanged(); + } + + @Override + public void setExtraData (@Nullable Object data) { + if (data instanceof Boolean) window = (boolean)data; + } + + @Override + @Nullable + public Object getExtraData () { return window; } + + @Override + public Object modifyExtraData (Object data) { + if (data instanceof Boolean windows) { + windows |= window; + return windows; + } + return data; + } + + @Override + public Direction.Axis getMainConnectionAxis() { return Direction.Axis.Y; } + + @Override + public int getMaxLength (Direction.Axis longAxis, int width) { + if (longAxis == Direction.Axis.Y) return getMaxHeight(); + return getMaxWidth(); + } + + @Override + public int getMaxWidth () { return MAX_SIZE; } + + @Override + public int getHeight () { return height; } + + @Override + public void setHeight (int height) { this.height = height; } + + @Override + public int getWidth () { return width; } + + @Override + public void setWidth (int width) { this.width = width; } + + @Override + public boolean hasFluid () { return true; } + + @Override + public int getTankSize (int tank) { return getCapacityMultiplier(); } + + @Override + public void setTankSize (int tank, int blocks) { + applyFluidTankSize(blocks); + } + + @Override + public IFluidTank getTank (int tank) { + return tankInventory; + } + + @Override + public FluidStack getFluid (int tank) { + return tankInventory.getFluid().copy(); + } } diff --git a/src/main/java/com/simibubi/create/foundation/tileEntity/IMultiTileContainer.java b/src/main/java/com/simibubi/create/foundation/tileEntity/IMultiTileContainer.java index 77a1161e8..68d3eaa6d 100644 --- a/src/main/java/com/simibubi/create/foundation/tileEntity/IMultiTileContainer.java +++ b/src/main/java/com/simibubi/create/foundation/tileEntity/IMultiTileContainer.java @@ -1,12 +1,69 @@ package com.simibubi.create.foundation.tileEntity; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.IFluidTank; + +import javax.annotation.Nullable; public interface IMultiTileContainer { - - public BlockPos getController(); - public boolean isController(); - public void setController(BlockPos pos); - public BlockPos getLastKnownPos(); + BlockPos getController(); + T getControllerTE (); + boolean isController(); + void setController(BlockPos pos); + void removeController (boolean keepContents); + BlockPos getLastKnownPos(); + + void preventConnectivityUpdate (); + void notifyMultiUpdated (); + + // only used for FluidTank windows at present. Might be useful for similar properties on other things? + default void setExtraData (@Nullable Object data) {} + @Nullable + default Object getExtraData () { return null; } + default Object modifyExtraData (Object data) { return data; } + + // multiblock structural information + Direction.Axis getMainConnectionAxis(); + default Direction.Axis getMainAxisOf (BlockEntity be) { // this feels redundant, but it gives us a default to use when defining ::getMainConnectionAxis + BlockState state = be.getBlockState(); + + Direction.Axis axis; + if (state.hasProperty(BlockStateProperties.HORIZONTAL_AXIS)) { + axis = state.getValue(BlockStateProperties.HORIZONTAL_AXIS); + } + else if (state.hasProperty(BlockStateProperties.FACING)) { + axis = state.getValue(BlockStateProperties.FACING).getAxis(); + } + else if (state.hasProperty(BlockStateProperties.HORIZONTAL_FACING)) { + axis = state.getValue(BlockStateProperties.HORIZONTAL_FACING).getAxis(); + } + else axis = Direction.Axis.Y; + + return axis; + } + + int getMaxLength (Direction.Axis longAxis, int width); + int getMaxWidth (); + + int getHeight (); + void setHeight (int height); + int getWidth (); + void setWidth (int width); + + // optional item handling + default boolean hasItems () { return false; } + + // optional fluid handling + // done here rather than through the Capability to allow greater flexibility + default boolean hasFluid () { return false; } + default int getTankSize (int tank) { return 0; } + default void setTankSize (int tank, int blocks) {} + default IFluidTank getTank (int tank) { return null; } + default FluidStack getFluid (int tank) { return FluidStack.EMPTY; } }