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..fbf9c4dc3 --- /dev/null +++ b/src/main/java/com/simibubi/create/api/connectivity/ConnectivityHandler.java @@ -0,0 +1,378 @@ +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 instanceof IMultiTileContainer.Fluid ifluid && ifluid.hasTank()) ifluid.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 instanceof IMultiTileContainer.Fluid ifluid && ifluid.hasTank()) { + beTank = ifluid.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; + } + } + if (controller instanceof IMultiTileContainer.Fluid ifluidCon && ifluidCon.hasTank()) { + FluidStack otherFluid = ifluidCon.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 instanceof IMultiTileContainer.Fluid ifluidPart && ifluidPart.hasTank()) { + IFluidTank tankAt = ifluidPart.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); + } + if (be instanceof IMultiTileContainer.Fluid ifluidBE && ifluidBE.hasTank() && beTank != null) { + beTank.fill(fluidAt, IFluidHandler.FluidAction.EXECUTE); + } + } + tankAt.drain(tankAt.getCapacity(), IFluidHandler.FluidAction.EXECUTE); + } + + splitMultiAndInvalidate(part, cache, false); + part.setController(origin); + part.preventConnectivityUpdate(); + cache.put(pos, be); + part.setHeight(height); + part.setWidth(width); + 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 instanceof IMultiTileContainer.Fluid ifluidBE && ifluidBE.hasTank()) { + toDistribute = ifluidBE.getFluid(0); + maxCapacity = ifluidBE.getTankSize(0); + if (!toDistribute.isEmpty() && !be.isRemoved()) toDistribute.shrink(maxCapacity); + ifluidBE.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 instanceof IMultiTileContainer.Fluid ifluidPart ? ifluidPart.getTank(0) : null); + // 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); + if (tank != null) tank.fill(copy, IFluidHandler.FluidAction.EXECUTE); + } + } + if (tryReconnect) { + frontier.add(partAt); + partAt.preventConnectivityUpdate(); + } + if (cache != null) { + cache.put(pos, partAt); + } + } + } + } + if (be instanceof IMultiTileContainer.Inventory iinv && iinv.hasInventory()) { + be.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).invalidate(); + } + if (be instanceof IMultiTileContainer.Fluid ifluid && ifluid.hasTank()) { + 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..5832da41a 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,9 +331,9 @@ 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)); + return ConnectivityHandler.isConnected(world, pos, pos.relative(direction)); //ItemVaultConnectivityHandler.isConnected(world, pos, pos.relative(direction)); if (AllBlocks.STICKER.has(state) && state.getValue(StickerBlock.EXTENDED)) { return direction == state.getValue(StickerBlock.FACING) && !isNotSupportive(world.getBlockState(pos.relative(direction)), direction.getOpposite()); @@ -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..d649dca3a 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); 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/FluidTankConnectivityHandler.java b/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankConnectivityHandler.java deleted file mode 100644 index 5bd68dcca..000000000 --- a/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankConnectivityHandler.java +++ /dev/null @@ -1,381 +0,0 @@ -package com.simibubi.create.content.contraptions.fluids.tank; - -import java.util.ArrayList; -import java.util.Comparator; -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; - -import javax.annotation.Nullable; - -import org.apache.commons.lang3.tuple.Pair; - -import com.simibubi.create.content.contraptions.fluids.tank.CreativeFluidTankTileEntity.CreativeSmartFluidTank; -import com.simibubi.create.foundation.utility.Iterate; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.Direction.Axis; -import net.minecraft.core.Direction.AxisDirection; -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.minecraft.world.level.block.state.BlockState; -import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.fluids.FluidStack; -import net.minecraftforge.fluids.capability.CapabilityFluidHandler; -import net.minecraftforge.fluids.capability.IFluidHandler; -import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; -import net.minecraftforge.fluids.capability.templates.FluidTank; - -public class FluidTankConnectivityHandler { - - public static void formTanks(FluidTankTileEntity te) { - TankSearchCache cache = new TankSearchCache(); - List frontier = new ArrayList<>(); - frontier.add(te); - formTanks(te.getType(), te.getLevel(), cache, frontier); - } - - private static void formTanks(BlockEntityType type, BlockGetter world, TankSearchCache cache, - List frontier) { - PriorityQueue> creationQueue = makeCreationQueue(); - Set visited = new HashSet<>(); - - int minX = Integer.MAX_VALUE; - int minZ = Integer.MAX_VALUE; - for (FluidTankTileEntity fluidTankTileEntity : frontier) { - BlockPos pos = fluidTankTileEntity.getBlockPos(); - minX = Math.min(pos.getX(), minX); - minZ = Math.min(pos.getZ(), minZ); - } - minX -= FluidTankTileEntity.getMaxSize(); - minZ -= FluidTankTileEntity.getMaxSize(); - - while (!frontier.isEmpty()) { - FluidTankTileEntity tank = frontier.remove(0); - BlockPos tankPos = tank.getBlockPos(); - if (visited.contains(tankPos)) - continue; - - visited.add(tankPos); - - int amount = tryToFormNewTank(tank, cache, true); - if (amount > 1) - creationQueue.add(Pair.of(amount, tank)); - - for (Axis axis : Iterate.axes) { - Direction d = Direction.get(AxisDirection.NEGATIVE, axis); - BlockPos next = tankPos.relative(d); - - if (next.getX() <= minX || next.getZ() <= minZ) - continue; - if (visited.contains(next)) - continue; - FluidTankTileEntity nextTank = tankAt(type, world, next); - if (nextTank == null) - continue; - if (nextTank.isRemoved()) - continue; - frontier.add(nextTank); - } - } - - visited.clear(); - - while (!creationQueue.isEmpty()) { - Pair next = creationQueue.poll(); - FluidTankTileEntity toCreate = next.getValue(); - if (visited.contains(toCreate.getBlockPos())) - continue; - visited.add(toCreate.getBlockPos()); - tryToFormNewTank(toCreate, cache, false); - } - - } - - public static void splitTank(FluidTankTileEntity te) { - splitTankAndInvalidate(te, null, false); - } - - private static int tryToFormNewTank(FluidTankTileEntity te, TankSearchCache cache, boolean simulate) { - int bestWidth = 1; - int bestAmount = -1; - - if (!te.isController()) - return 0; - - for (int w = 1; w <= FluidTankTileEntity.getMaxSize(); w++) { - int amount = tryToFormNewTankOfWidth(te, w, cache, true); - if (amount < bestAmount) - continue; - bestWidth = w; - bestAmount = amount; - } - - if (!simulate) { - if (te.width == bestWidth && te.width * te.width * te.height == bestAmount) - return bestAmount; - - splitTankAndInvalidate(te, cache, false); - te.applyFluidTankSize(bestAmount); - tryToFormNewTankOfWidth(te, bestWidth, cache, simulate); - te.updateConnectivity = false; - te.width = bestWidth; - te.height = bestAmount / bestWidth / bestWidth; - - BlockState state = te.getBlockState(); - if (FluidTankBlock.isTank(state)) { - state = state.setValue(FluidTankBlock.BOTTOM, true); - state = state.setValue(FluidTankBlock.TOP, te.height == 1); - te.getLevel() - .setBlock(te.getBlockPos(), state, 22); - } - - te.setWindows(te.window); - te.onFluidStackChanged(te.tankInventory.getFluid()); - te.setChanged(); - } - - return bestAmount; - } - - private static int tryToFormNewTankOfWidth(FluidTankTileEntity te, int width, TankSearchCache cache, - boolean simulate) { - int amount = 0; - int height = 0; - BlockEntityType type = te.getType(); - Level world = te.getLevel(); - BlockPos origin = te.getBlockPos(); - LazyOptional capability = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY); - FluidTank teTank = (FluidTank) capability.orElse(null); - FluidStack fluid = capability.map(ifh -> ifh.getFluidInTank(0)) - .orElse(FluidStack.EMPTY); - - Search: - - for (int yOffset = 0; yOffset < FluidTankTileEntity.getMaxHeight(); yOffset++) { - for (int xOffset = 0; xOffset < width; xOffset++) { - for (int zOffset = 0; zOffset < width; zOffset++) { - - BlockPos pos = origin.offset(xOffset, yOffset, zOffset); - Optional tank = cache.getOrCache(type, world, pos); - if (!tank.isPresent()) - break Search; - - FluidTankTileEntity controller = tank.get(); - int otherWidth = controller.width; - if (otherWidth > width) - break Search; - - BlockPos controllerPos = controller.getBlockPos(); - if (!controllerPos.equals(origin)) { - if (controllerPos.getX() < origin.getX()) - break Search; - if (controllerPos.getZ() < origin.getZ()) - break Search; - if (controllerPos.getX() + otherWidth > origin.getX() + width) - break Search; - if (controllerPos.getZ() + otherWidth > origin.getZ() + width) - break Search; - } - - FluidStack otherFluid = controller.getTankInventory() - .getFluid(); - if (!fluid.isEmpty() && !otherFluid.isEmpty() && !fluid.isFluidEqual(otherFluid)) - break Search; - - } - } - - amount += width * width; - height++; - } - - if (simulate) - return amount; - - boolean opaque = false; - - for (int yOffset = 0; yOffset < height; yOffset++) { - for (int xOffset = 0; xOffset < width; xOffset++) { - for (int zOffset = 0; zOffset < width; zOffset++) { - BlockPos pos = origin.offset(xOffset, yOffset, zOffset); - FluidTankTileEntity tank = tankAt(type, world, pos); - if (tank == te) - continue; - - opaque |= !tank.window; - FluidTank tankTank = tank.tankInventory; - FluidStack fluidInTank = tankTank.getFluid(); - if (!fluidInTank.isEmpty()) { - if (teTank.isEmpty() && teTank instanceof CreativeSmartFluidTank) - ((CreativeSmartFluidTank) teTank).setContainedFluid(fluidInTank); - teTank.fill(fluidInTank, FluidAction.EXECUTE); - } - tankTank.setFluid(FluidStack.EMPTY); - - splitTankAndInvalidate(tank, cache, false); - tank.setController(origin); - tank.updateConnectivity = false; - cache.put(pos, te); - - BlockState state = world.getBlockState(pos); - if (!FluidTankBlock.isTank(state)) - continue; - state = state.setValue(FluidTankBlock.BOTTOM, yOffset == 0); - state = state.setValue(FluidTankBlock.TOP, yOffset == height - 1); - world.setBlock(pos, state, 22); - } - } - } - - te.setWindows(!opaque); - - return amount; - } - - private static void splitTankAndInvalidate(FluidTankTileEntity te, @Nullable TankSearchCache cache, - boolean tryReconnect) { - // tryReconnect helps whenever only few tanks have been removed - - te = te.getControllerTE(); - if (te == null) - return; - - int height = te.height; - int width = te.width; - if (width == 1 && height == 1) - return; - - Level world = te.getLevel(); - BlockPos origin = te.getBlockPos(); - List frontier = new ArrayList<>(); - FluidStack toDistribute = te.tankInventory.getFluid() - .copy(); - int maxCapacity = FluidTankTileEntity.getCapacityMultiplier(); - if (!toDistribute.isEmpty() && !te.isRemoved()) - toDistribute.shrink(maxCapacity); - te.applyFluidTankSize(1); - - for (int yOffset = 0; yOffset < height; yOffset++) { - for (int xOffset = 0; xOffset < width; xOffset++) { - for (int zOffset = 0; zOffset < width; zOffset++) { - - BlockPos pos = origin.offset(xOffset, yOffset, zOffset); - FluidTankTileEntity tankAt = tankAt(te.getType(), world, pos); - if (tankAt == null) - continue; - if (!tankAt.getController() - .equals(origin)) - continue; - FluidTankTileEntity controllerTE = tankAt.getControllerTE(); - tankAt.window = controllerTE == null || controllerTE.window; - tankAt.removeController(true); - - if (!toDistribute.isEmpty() && tankAt != te) { - FluidStack copy = toDistribute.copy(); - FluidTank tankInventory = tankAt.tankInventory; - if (tankInventory.isEmpty() && tankInventory instanceof CreativeSmartFluidTank) - ((CreativeSmartFluidTank) tankInventory).setContainedFluid(toDistribute); - else { - int split = Math.min(maxCapacity, toDistribute.getAmount()); - copy.setAmount(split); - toDistribute.shrink(split); - tankInventory.fill(copy, FluidAction.EXECUTE); - } - } - - if (tryReconnect) { - frontier.add(tankAt); - tankAt.updateConnectivity = false; - } - if (cache != null) - cache.put(pos, tankAt); - } - } - } - - te.fluidCapability.invalidate(); - if (tryReconnect) - formTanks(te.getType(), world, cache == null ? new TankSearchCache() : cache, frontier); - } - - private static PriorityQueue> makeCreationQueue() { - return new PriorityQueue<>(new Comparator>() { - @Override - public int compare(Pair o1, Pair o2) { - return o2.getKey() - o1.getKey(); - } - }); - } - - @Nullable - public static FluidTankTileEntity tankAt(BlockEntityType type, BlockGetter world, BlockPos pos) { - BlockEntity te = world.getBlockEntity(pos); - if (te instanceof FluidTankTileEntity && te.getType() == type) - return (FluidTankTileEntity) te; - return null; - } - - @Nullable - public static FluidTankTileEntity anyTankAt(BlockGetter world, BlockPos pos) { - BlockEntity te = world.getBlockEntity(pos); - if (te instanceof FluidTankTileEntity) - return (FluidTankTileEntity) te; - return null; - } - - private static class TankSearchCache { - Map> controllerMap; - - public TankSearchCache() { - controllerMap = new HashMap<>(); - } - - void put(BlockPos pos, FluidTankTileEntity 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 world, BlockPos pos) { - if (hasVisited(pos)) - return controllerMap.get(pos); - FluidTankTileEntity tankAt = tankAt(type, world, pos); - if (tankAt == null) { - putEmpty(pos); - return Optional.empty(); - } - FluidTankTileEntity controller = tankAt.getControllerTE(); - if (controller == null) { - putEmpty(pos); - return Optional.empty(); - } - put(pos, controller); - return Optional.of(controller); - } - - } - - public static boolean isConnected(BlockGetter world, BlockPos tankPos, BlockPos otherTankPos) { - BlockEntity te1 = world.getBlockEntity(tankPos); - BlockEntity te2 = world.getBlockEntity(otherTankPos); - if (!(te1 instanceof FluidTankTileEntity) || !(te2 instanceof FluidTankTileEntity)) - return false; - return ((FluidTankTileEntity) te1).getController() - .equals(((FluidTankTileEntity) te2).getController()); - } - -} 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..22fb7387e 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,9 @@ package com.simibubi.create.content.contraptions.fluids.tank; +import com.simibubi.create.AllBlocks; +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 +75,10 @@ public class FluidTankItem extends BlockItem { if (!FluidTankBlock.isTank(placedOnState)) return; - FluidTankTileEntity tankAt = FluidTankConnectivityHandler.anyTankAt(world, placedOnPos); + boolean creative = getBlock().equals(AllBlocks.CREATIVE_FLUID_TANK.get()); + FluidTankTileEntity tankAt = ConnectivityHandler.partAt( + creative ? AllTileEntities.CREATIVE_FLUID_TANK.get() : AllTileEntities.FLUID_TANK.get(), 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..b92e7ab30 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,7 @@ import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; +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; @@ -35,7 +36,7 @@ import net.minecraftforge.fluids.capability.IFluidHandler; import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; import net.minecraftforge.fluids.capability.templates.FluidTank; -public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleInformation, IMultiTileContainer { +public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleInformation, IMultiTileContainer.Fluid { private static final int MAX_SIZE = 3; @@ -79,7 +80,7 @@ public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleI return; if (!isController()) return; - FluidTankConnectivityHandler.formTanks(this); + ConnectivityHandler.formMulti(this); } @Override @@ -103,7 +104,7 @@ public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleI if (fluidLevel != null) fluidLevel.tick(); } - + @Override public BlockPos getLastKnownPos() { return lastKnownPos; @@ -145,7 +146,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); if (tankAt == null) continue; level.updateNeighbourForOutputSignal(pos, tankAt.getBlockState() @@ -161,7 +162,7 @@ public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleI setChanged(); sendData(); } - + if (isVirtual()) { if (fluidLevel == null) fluidLevel = new InterpolatedChasingValue().start(getFillState()); @@ -462,4 +463,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 hasTank() { 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/content/logistics/block/vault/ItemVaultBlock.java b/src/main/java/com/simibubi/create/content/logistics/block/vault/ItemVaultBlock.java index e8fb413b8..f7cdb74ea 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/vault/ItemVaultBlock.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/vault/ItemVaultBlock.java @@ -4,6 +4,7 @@ import javax.annotation.Nullable; import com.simibubi.create.AllBlocks; import com.simibubi.create.AllTileEntities; +import com.simibubi.create.api.connectivity.ConnectivityHandler; import com.simibubi.create.content.contraptions.wrench.IWrenchable; import com.simibubi.create.foundation.block.ITE; import com.simibubi.create.foundation.item.ItemHelper; @@ -85,7 +86,7 @@ public class ItemVaultBlock extends Block implements IWrenchable, ITE frontier = new ArrayList<>(); - frontier.add(te); - formVaults(te.getType(), te.getLevel(), cache, frontier); - } - - private static void formVaults(BlockEntityType type, BlockGetter world, VaultSearchCache cache, - List frontier) { - PriorityQueue> creationQueue = makeCreationQueue(); - Set visited = new HashSet<>(); - - int minY = Integer.MAX_VALUE; - for (ItemVaultTileEntity fluidTankTileEntity : frontier) { - BlockPos pos = fluidTankTileEntity.getBlockPos(); - minY = Math.min(pos.getY(), minY); - } - - minY -= 3; - - while (!frontier.isEmpty()) { - ItemVaultTileEntity tank = frontier.remove(0); - BlockPos tankPos = tank.getBlockPos(); - if (visited.contains(tankPos)) - continue; - - visited.add(tankPos); - - int amount = tryToFormNewVault(tank, cache, true); - if (amount > 1) - creationQueue.add(Pair.of(amount, tank)); - - for (Axis axis : Iterate.axes) { - Direction d = Direction.fromAxisAndDirection(axis, AxisDirection.NEGATIVE); - BlockPos next = tankPos.relative(d); - - if (next.getY() <= minY) - continue; - if (visited.contains(next)) - continue; - ItemVaultTileEntity nextTank = vaultAt(type, world, next); - if (nextTank == null) - continue; - if (nextTank.isRemoved()) - continue; - frontier.add(nextTank); - } - } - - visited.clear(); - - while (!creationQueue.isEmpty()) { - Pair next = creationQueue.poll(); - ItemVaultTileEntity toCreate = next.getValue(); - if (visited.contains(toCreate.getBlockPos())) - continue; - visited.add(toCreate.getBlockPos()); - tryToFormNewVault(toCreate, cache, false); - } - - } - - public static void splitVault(ItemVaultTileEntity te) { - splitVaultAndInvalidate(te, null, false); - } - - private static int tryToFormNewVault(ItemVaultTileEntity te, VaultSearchCache cache, boolean simulate) { - int bestWidth = 1; - int bestAmount = -1; - - if (!te.isController()) - return 0; - - for (int w = 1; w <= 3; w++) { - int amount = tryToFormNewVaultOfRadius(te, w, cache, true); - if (amount < bestAmount) - continue; - bestWidth = w; - bestAmount = amount; - } - - if (!simulate) { - if (te.radius == bestWidth && te.radius * te.radius * te.length == bestAmount) - return bestAmount; - - splitVaultAndInvalidate(te, cache, false); - tryToFormNewVaultOfRadius(te, bestWidth, cache, simulate); - te.updateConnectivity = false; - te.radius = bestWidth; - te.length = bestAmount / bestWidth / bestWidth; - - BlockState state = te.getBlockState(); - if (ItemVaultBlock.isVault(state)) - te.getLevel() - .setBlock(te.getBlockPos(), state.setValue(ItemVaultBlock.LARGE, te.radius > 2), 22); - - te.itemCapability.invalidate(); - te.setChanged(); - } - - return bestAmount; - } - - private static int tryToFormNewVaultOfRadius(ItemVaultTileEntity te, int width, VaultSearchCache cache, - boolean simulate) { - int amount = 0; - int height = 0; - BlockEntityType type = te.getType(); - Level world = te.getLevel(); - BlockPos origin = te.getBlockPos(); - boolean alongZ = ItemVaultBlock.getVaultBlockAxis(te.getBlockState()) == Axis.Z; - - Search: - - for (int yOffset = 0; yOffset < ItemVaultTileEntity.getMaxLength(width); yOffset++) { - for (int xOffset = 0; xOffset < width; xOffset++) { - for (int zOffset = 0; zOffset < width; zOffset++) { - - BlockPos pos = - alongZ ? origin.offset(xOffset, zOffset, yOffset) : origin.offset(yOffset, xOffset, zOffset); - Optional tank = cache.getOrCache(type, world, pos); - if (!tank.isPresent()) - break Search; - - ItemVaultTileEntity controller = tank.get(); - int otherWidth = controller.radius; - if (otherWidth > width) - break Search; - if (otherWidth == width && controller.length == ItemVaultTileEntity.getMaxLength(width)) - break Search; - if ((ItemVaultBlock.getVaultBlockAxis(controller.getBlockState()) == Axis.Z) != alongZ) - break Search; - - BlockPos controllerPos = controller.getBlockPos(); - if (!controllerPos.equals(origin)) { - if (alongZ && controllerPos.getX() < origin.getX()) - break Search; - if (controllerPos.getY() < origin.getY()) - break Search; - if (!alongZ && controllerPos.getZ() < origin.getZ()) - break Search; - if (alongZ && controllerPos.getX() + otherWidth > origin.getX() + width) - break Search; - if (controllerPos.getY() + otherWidth > origin.getY() + width) - break Search; - if (!alongZ && controllerPos.getZ() + otherWidth > origin.getZ() + width) - break Search; - } - - } - } - - amount += width * width; - height++; - } - - if (simulate) - return amount; - - for (int yOffset = 0; yOffset < height; yOffset++) { - for (int xOffset = 0; xOffset < width; xOffset++) { - for (int zOffset = 0; zOffset < width; zOffset++) { - BlockPos pos = - alongZ ? origin.offset(xOffset, zOffset, yOffset) : origin.offset(yOffset, xOffset, zOffset); - ItemVaultTileEntity tank = vaultAt(type, world, pos); - if (tank == te) - continue; - - splitVaultAndInvalidate(tank, cache, false); - tank.setController(origin); - tank.updateConnectivity = false; - cache.put(pos, te); - - BlockState state = world.getBlockState(pos); - if (!ItemVaultBlock.isVault(state)) - continue; - state = state.setValue(ItemVaultBlock.LARGE, width > 2); - world.setBlock(pos, state, 22); - } - } - } - - return amount; - } - - private static void splitVaultAndInvalidate(ItemVaultTileEntity te, @Nullable VaultSearchCache cache, - boolean tryReconnect) { - // tryReconnect helps whenever only few tanks have been removed - - te = te.getControllerTE(); - if (te == null) - return; - - int height = te.length; - int width = te.radius; - BlockState state = te.getBlockState(); - boolean alongZ = ItemVaultBlock.getVaultBlockAxis(state) == Axis.Z; - if (width == 1 && height == 1) - return; - - Level world = te.getLevel(); - BlockPos origin = te.getBlockPos(); - List frontier = new ArrayList<>(); - - for (int yOffset = 0; yOffset < height; yOffset++) { - for (int xOffset = 0; xOffset < width; xOffset++) { - for (int zOffset = 0; zOffset < width; zOffset++) { - - BlockPos pos = - alongZ ? origin.offset(xOffset, zOffset, yOffset) : origin.offset(yOffset, xOffset, zOffset); - ItemVaultTileEntity tankAt = vaultAt(te.getType(), world, pos); - if (tankAt == null) - continue; - if (!tankAt.getController() - .equals(origin)) - continue; - - tankAt.removeController(true); - - if (tryReconnect) { - frontier.add(tankAt); - tankAt.updateConnectivity = false; - } - if (cache != null) - cache.put(pos, tankAt); - } - } - } - - te.itemCapability.invalidate(); - if (tryReconnect) - formVaults(te.getType(), world, cache == null ? new VaultSearchCache() : cache, frontier); - } - - private static PriorityQueue> makeCreationQueue() { - return new PriorityQueue<>(new Comparator>() { - @Override - public int compare(Pair o1, Pair o2) { - return o2.getKey() - o1.getKey(); - } - }); - } - - @Nullable - public static ItemVaultTileEntity vaultAt(BlockEntityType type, BlockGetter world, BlockPos pos) { - BlockEntity te = world.getBlockEntity(pos); - if (te instanceof ItemVaultTileEntity && te.getType() == type) - return (ItemVaultTileEntity) te; - return null; - } - - private static class VaultSearchCache { - Map> controllerMap; - - public VaultSearchCache() { - controllerMap = new HashMap<>(); - } - - void put(BlockPos pos, ItemVaultTileEntity 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 world, BlockPos pos) { - if (hasVisited(pos)) - return controllerMap.get(pos); - ItemVaultTileEntity tankAt = vaultAt(type, world, pos); - if (tankAt == null) { - putEmpty(pos); - return Optional.empty(); - } - ItemVaultTileEntity controller = tankAt.getControllerTE(); - if (controller == null) { - putEmpty(pos); - return Optional.empty(); - } - put(pos, controller); - return Optional.of(controller); - } - - } - - public static boolean isConnected(BlockGetter world, BlockPos tankPos, BlockPos otherTankPos) { - BlockEntity te1 = world.getBlockEntity(tankPos); - BlockEntity te2 = world.getBlockEntity(otherTankPos); - if (!(te1 instanceof ItemVaultTileEntity) || !(te2 instanceof ItemVaultTileEntity)) - return false; - return ((ItemVaultTileEntity) te1).getController() - .equals(((ItemVaultTileEntity) te2).getController()); - } - -} diff --git a/src/main/java/com/simibubi/create/content/logistics/block/vault/ItemVaultItem.java b/src/main/java/com/simibubi/create/content/logistics/block/vault/ItemVaultItem.java index 8ac2cc341..fdd76e9bd 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/vault/ItemVaultItem.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/vault/ItemVaultItem.java @@ -1,6 +1,7 @@ package com.simibubi.create.content.logistics.block.vault; import com.simibubi.create.AllTileEntities; +import com.simibubi.create.api.connectivity.ConnectivityHandler; import com.simibubi.create.foundation.utility.VecHelper; import net.minecraft.core.BlockPos; @@ -64,7 +65,7 @@ public class ItemVaultItem extends BlockItem { if (!ItemVaultBlock.isVault(placedOnState)) return; - ItemVaultTileEntity tankAt = ItemVaultConnectivityHandler.vaultAt(AllTileEntities.ITEM_VAULT.get(), world, placedOnPos); + ItemVaultTileEntity tankAt = ConnectivityHandler.partAt(AllTileEntities.ITEM_VAULT.get(), world, placedOnPos); if (tankAt == null) return; ItemVaultTileEntity controllerTE = tankAt.getControllerTE(); diff --git a/src/main/java/com/simibubi/create/content/logistics/block/vault/ItemVaultTileEntity.java b/src/main/java/com/simibubi/create/content/logistics/block/vault/ItemVaultTileEntity.java index 18f767029..fefba7a4a 100644 --- a/src/main/java/com/simibubi/create/content/logistics/block/vault/ItemVaultTileEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/block/vault/ItemVaultTileEntity.java @@ -3,6 +3,7 @@ package com.simibubi.create.content.logistics.block.vault; import java.util.List; import com.simibubi.create.AllTileEntities; +import com.simibubi.create.api.connectivity.ConnectivityHandler; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.tileEntity.IMultiTileContainer; import com.simibubi.create.foundation.tileEntity.SmartTileEntity; @@ -25,7 +26,7 @@ import net.minecraftforge.items.IItemHandlerModifiable; import net.minecraftforge.items.ItemStackHandler; import net.minecraftforge.items.wrapper.CombinedInvWrapper; -public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileContainer { +public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileContainer.Inventory { protected LazyOptional itemCapability; @@ -62,7 +63,7 @@ public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileCo return; if (!isController()) return; - ItemVaultConnectivityHandler.formVaults(this); + ConnectivityHandler.formMulti(this); } protected void updateComparators() { @@ -94,7 +95,7 @@ public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileCo if (updateConnectivity) updateConnectivity(); } - + @Override public BlockPos getLastKnownPos() { return lastKnownPos; @@ -105,7 +106,7 @@ public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileCo return controller == null || worldPosition.getX() == controller.getX() && worldPosition.getY() == controller.getY() && worldPosition.getZ() == controller.getZ(); } - + private void onPositionChanged() { removeController(true); lastKnownPos = worldPosition; @@ -155,7 +156,7 @@ public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileCo public BlockPos getController() { return isController() ? worldPosition : controller; } - + @Override protected void read(CompoundTag compound, boolean clientPacket) { super.read(compound, clientPacket); @@ -209,11 +210,11 @@ public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileCo compound.put("Inventory", inventory.serializeNBT()); } } - + public ItemStackHandler getInventoryOfBlock() { return inventory; } - + public void applyInventoryToBlock(ItemStackHandler handler) { for (int i = 0; i < inventory.getSlots(); i++) inventory.setStackInSlot(i, i < handler.getSlots() ? handler.getStackInSlot(i) : ItemStack.EMPTY); @@ -248,7 +249,7 @@ public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileCo BlockPos vaultPos = alongZ ? worldPosition.offset(xOffset, zOffset, yOffset) : worldPosition.offset(yOffset, xOffset, zOffset); ItemVaultTileEntity vaultAt = - ItemVaultConnectivityHandler.vaultAt(AllTileEntities.ITEM_VAULT.get(), level, vaultPos); + ConnectivityHandler.partAt(AllTileEntities.ITEM_VAULT.get(), level, vaultPos); invs[yOffset * radius * radius + xOffset * radius + zOffset] = vaultAt != null ? vaultAt.inventory : new ItemStackHandler(); } @@ -263,4 +264,45 @@ public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileCo return radius * 3; } + @Override + public void preventConnectivityUpdate() { updateConnectivity = false; } + + @Override + public void notifyMultiUpdated() { + BlockState state = this.getBlockState(); + if (ItemVaultBlock.isVault(state)) { // safety + level.setBlock(getBlockPos(), state.setValue(ItemVaultBlock.LARGE, radius > 2), 22); + } + itemCapability.invalidate(); + setChanged(); + } + + @Override + public Direction.Axis getMainConnectionAxis() { return getMainAxisOf(this); } + + @Override + public int getMaxLength(Direction.Axis longAxis, int width) { + if (longAxis == Direction.Axis.Y) return getMaxWidth(); + return getMaxLength(width); + } + + @Override + public int getMaxWidth() { + return 3; + } + + @Override + public int getHeight() { return length; } + + @Override + public int getWidth() { return radius; } + + @Override + public void setHeight(int height) { this.length = height; } + + @Override + public void setWidth(int width) { this.radius = width; } + + @Override + public boolean hasInventory() { return true; } } 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..f51bd0e53 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,75 @@ 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); + + public interface Inventory extends IMultiTileContainer { + default boolean hasInventory() { return false; } + } + + public interface Fluid extends IMultiTileContainer { + // done here rather than through the Capability to allow greater flexibility + default boolean hasTank() { 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; } + } }