Merge pull request #3039 from talrey/mc1.18/dev

Generify Connectivity Handlers
This commit is contained in:
simibubi 2022-05-22 16:57:40 +02:00 committed by GitHub
commit 4a5eab831e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 614 additions and 748 deletions

View file

@ -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 <T extends BlockEntity & IMultiTileContainer> void formMulti(T be) {
SearchCache<T> cache = new SearchCache<>();
List<T> frontier = new ArrayList<>();
frontier.add(be);
formMulti(be.getType(), be.getLevel(), cache, frontier);
}
private static <T extends BlockEntity & IMultiTileContainer> void formMulti(BlockEntityType<?> type, BlockGetter level, SearchCache<T> cache, List<T> frontier) {
PriorityQueue<Pair<Integer, T>> creationQueue = makeCreationQueue();
Set<BlockPos> 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<Integer, T> next = creationQueue.poll();
T toCreate = next.getValue();
if (visited.contains(toCreate.getBlockPos())) continue;
visited.add(toCreate.getBlockPos());
tryToFormNewMulti(toCreate, cache, false);
}
}
private static <T extends BlockEntity & IMultiTileContainer> int tryToFormNewMulti(T be, SearchCache<T> 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 <T extends BlockEntity & IMultiTileContainer> int tryToFormNewMultiOfWidth(T be, int width, SearchCache<T> 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<T> 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 <T extends BlockEntity & IMultiTileContainer> void splitMulti(T be) {
splitMultiAndInvalidate(be, null, false);
}
// tryReconnect helps whenever only a few tanks have been removed
private static <T extends BlockEntity & IMultiTileContainer> void splitMultiAndInvalidate(T be, @Nullable SearchCache<T> 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<T> 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 <T extends BlockEntity & IMultiTileContainer> PriorityQueue<Pair<Integer, T>> makeCreationQueue() {
return new PriorityQueue<>((one, two) -> two.getKey() - one.getKey());
}
@Nullable
public static <T extends BlockEntity & IMultiTileContainer> 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 <T extends BlockEntity & IMultiTileContainer> 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 extends BlockEntity & IMultiTileContainer> T checked(BlockEntity be) {
if (be instanceof IMultiTileContainer) return (T)be;
return null;
}
private static class SearchCache<T extends BlockEntity & IMultiTileContainer> {
Map<BlockPos, Optional<T>> 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<T> 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);
}
}
}

View file

@ -5,6 +5,7 @@ import java.util.List;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllTags.AllBlockTags; 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.AttachedActorBlock;
import com.simibubi.create.content.contraptions.components.actors.HarvesterBlock; import com.simibubi.create.content.contraptions.components.actors.HarvesterBlock;
import com.simibubi.create.content.contraptions.components.actors.PloughBlock; import com.simibubi.create.content.contraptions.components.actors.PloughBlock;
@ -330,9 +331,9 @@ public class BlockMovementChecks {
return direction.getAxis() != state.getValue(SailBlock.FACING) return direction.getAxis() != state.getValue(SailBlock.FACING)
.getAxis(); .getAxis();
if (state.getBlock() instanceof FluidTankBlock) 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) 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)) { if (AllBlocks.STICKER.has(state) && state.getValue(StickerBlock.EXTENDED)) {
return direction == state.getValue(StickerBlock.FACING) return direction == state.getValue(StickerBlock.FACING)
&& !isNotSupportive(world.getBlockState(pos.relative(direction)), direction.getOpposite()); && !isNotSupportive(world.getBlockState(pos.relative(direction)), direction.getOpposite());

View file

@ -1,6 +1,7 @@
package com.simibubi.create.content.contraptions.fluids.tank; package com.simibubi.create.content.contraptions.fluids.tank;
import com.simibubi.create.AllTileEntities; 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.actors.GenericItemFilling;
import com.simibubi.create.content.contraptions.fluids.tank.CreativeFluidTankTileEntity.CreativeSmartFluidTank; import com.simibubi.create.content.contraptions.fluids.tank.CreativeFluidTankTileEntity.CreativeSmartFluidTank;
import com.simibubi.create.content.contraptions.processing.EmptyingByBasin; import com.simibubi.create.content.contraptions.processing.EmptyingByBasin;
@ -93,7 +94,7 @@ public class FluidTankBlock extends Block implements IWrenchable, ITE<FluidTankT
@Override @Override
public int getLightEmission(BlockState state, BlockGetter world, BlockPos pos) { 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) if (tankAt == null)
return 0; return 0;
FluidTankTileEntity controllerTE = tankAt.getControllerTE(); FluidTankTileEntity controllerTE = tankAt.getControllerTE();
@ -120,7 +121,7 @@ public class FluidTankBlock extends Block implements IWrenchable, ITE<FluidTankT
return InteractionResult.PASS; return InteractionResult.PASS;
FluidExchange exchange = null; FluidExchange exchange = null;
FluidTankTileEntity te = FluidTankConnectivityHandler.anyTankAt(world, pos); FluidTankTileEntity te = ConnectivityHandler.partAt(getTileEntityType(), world, pos);
if (te == null) if (te == null)
return InteractionResult.FAIL; return InteractionResult.FAIL;
@ -231,7 +232,7 @@ public class FluidTankBlock extends Block implements IWrenchable, ITE<FluidTankT
return; return;
FluidTankTileEntity tankTE = (FluidTankTileEntity) te; FluidTankTileEntity tankTE = (FluidTankTileEntity) te;
world.removeBlockEntity(pos); world.removeBlockEntity(pos);
FluidTankConnectivityHandler.splitTank(tankTE); ConnectivityHandler.splitMulti(tankTE);
} }
} }

View file

@ -1,5 +1,6 @@
package com.simibubi.create.content.contraptions.fluids.tank; 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.CTSpriteShiftEntry;
import com.simibubi.create.foundation.block.connected.HorizontalCTBehaviour; import com.simibubi.create.foundation.block.connected.HorizontalCTBehaviour;
@ -21,6 +22,6 @@ public class FluidTankCTBehaviour extends HorizontalCTBehaviour {
@Override @Override
public boolean connectsTo(BlockState state, BlockState other, BlockAndTintGetter reader, BlockPos pos, BlockPos otherPos, public boolean connectsTo(BlockState state, BlockState other, BlockAndTintGetter reader, BlockPos pos, BlockPos otherPos,
Direction face) { 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);
} }
} }

View file

@ -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<FluidTankTileEntity> frontier = new ArrayList<>();
frontier.add(te);
formTanks(te.getType(), te.getLevel(), cache, frontier);
}
private static void formTanks(BlockEntityType<?> type, BlockGetter world, TankSearchCache cache,
List<FluidTankTileEntity> frontier) {
PriorityQueue<Pair<Integer, FluidTankTileEntity>> creationQueue = makeCreationQueue();
Set<BlockPos> 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<Integer, FluidTankTileEntity> 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<IFluidHandler> 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<FluidTankTileEntity> 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<FluidTankTileEntity> 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<Pair<Integer, FluidTankTileEntity>> makeCreationQueue() {
return new PriorityQueue<>(new Comparator<Pair<Integer, FluidTankTileEntity>>() {
@Override
public int compare(Pair<Integer, FluidTankTileEntity> o1, Pair<Integer, FluidTankTileEntity> 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<BlockPos, Optional<FluidTankTileEntity>> 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<FluidTankTileEntity> 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());
}
}

View file

@ -1,5 +1,9 @@
package com.simibubi.create.content.contraptions.fluids.tank; 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.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@ -71,7 +75,10 @@ public class FluidTankItem extends BlockItem {
if (!FluidTankBlock.isTank(placedOnState)) if (!FluidTankBlock.isTank(placedOnState))
return; 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) if (tankAt == null)
return; return;
FluidTankTileEntity controllerTE = tankAt.getControllerTE(); FluidTankTileEntity controllerTE = tankAt.getControllerTE();

View file

@ -7,6 +7,7 @@ import java.util.List;
import java.util.Random; import java.util.Random;
import com.simibubi.create.AllSpriteShifts; 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.CTModel;
import com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry; import com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry;
import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Iterate;
@ -41,7 +42,7 @@ public class FluidTankModel extends CTModel {
protected Builder gatherModelData(Builder builder, BlockAndTintGetter world, BlockPos pos, BlockState state) { protected Builder gatherModelData(Builder builder, BlockAndTintGetter world, BlockPos pos, BlockState state) {
CullData cullData = new CullData(); CullData cullData = new CullData();
for (Direction d : Iterate.horizontalDirections) 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); return super.gatherModelData(builder, world, pos, state).withInitial(CULL_PROPERTY, cullData);
} }

View file

@ -7,6 +7,7 @@ import java.util.List;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; 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.fluids.tank.FluidTankBlock.Shape;
import com.simibubi.create.content.contraptions.goggles.IHaveGoggleInformation; import com.simibubi.create.content.contraptions.goggles.IHaveGoggleInformation;
import com.simibubi.create.foundation.config.AllConfigs; 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.IFluidHandler.FluidAction;
import net.minecraftforge.fluids.capability.templates.FluidTank; 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; private static final int MAX_SIZE = 3;
@ -79,7 +80,7 @@ public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleI
return; return;
if (!isController()) if (!isController())
return; return;
FluidTankConnectivityHandler.formTanks(this); ConnectivityHandler.formMulti(this);
} }
@Override @Override
@ -145,7 +146,7 @@ public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleI
for (int xOffset = 0; xOffset < width; xOffset++) { for (int xOffset = 0; xOffset < width; xOffset++) {
for (int zOffset = 0; zOffset < width; zOffset++) { for (int zOffset = 0; zOffset < width; zOffset++) {
BlockPos pos = this.worldPosition.offset(xOffset, yOffset, 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) if (tankAt == null)
continue; continue;
level.updateNeighbourForOutputSignal(pos, tankAt.getBlockState() level.updateNeighbourForOutputSignal(pos, tankAt.getBlockState()
@ -462,4 +463,82 @@ public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleI
this.fluidLevel = fluidLevel; 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();
}
} }

View file

@ -4,6 +4,7 @@ import javax.annotation.Nullable;
import com.simibubi.create.AllBlocks; import com.simibubi.create.AllBlocks;
import com.simibubi.create.AllTileEntities; import com.simibubi.create.AllTileEntities;
import com.simibubi.create.api.connectivity.ConnectivityHandler;
import com.simibubi.create.content.contraptions.wrench.IWrenchable; import com.simibubi.create.content.contraptions.wrench.IWrenchable;
import com.simibubi.create.foundation.block.ITE; import com.simibubi.create.foundation.block.ITE;
import com.simibubi.create.foundation.item.ItemHelper; import com.simibubi.create.foundation.item.ItemHelper;
@ -85,7 +86,7 @@ public class ItemVaultBlock extends Block implements IWrenchable, ITE<ItemVaultT
.getBlockEntity(context.getClickedPos()); .getBlockEntity(context.getClickedPos());
if (te instanceof ItemVaultTileEntity) { if (te instanceof ItemVaultTileEntity) {
ItemVaultTileEntity vault = (ItemVaultTileEntity) te; ItemVaultTileEntity vault = (ItemVaultTileEntity) te;
ItemVaultConnectivityHandler.splitVault(vault); ConnectivityHandler.splitMulti(vault);
vault.removeController(true); vault.removeController(true);
} }
state = state.setValue(LARGE, false); state = state.setValue(LARGE, false);
@ -100,10 +101,10 @@ public class ItemVaultBlock extends Block implements IWrenchable, ITE<ItemVaultT
BlockEntity te = world.getBlockEntity(pos); BlockEntity te = world.getBlockEntity(pos);
if (!(te instanceof ItemVaultTileEntity)) if (!(te instanceof ItemVaultTileEntity))
return; return;
ItemVaultTileEntity tankTE = (ItemVaultTileEntity) te; ItemVaultTileEntity vaultTE = (ItemVaultTileEntity) te;
ItemHelper.dropContents(world, pos, tankTE.inventory); ItemHelper.dropContents(world, pos, vaultTE.inventory);
world.removeBlockEntity(pos); world.removeBlockEntity(pos);
ItemVaultConnectivityHandler.splitVault(tankTE); ConnectivityHandler.splitMulti(vaultTE);
} }
} }

View file

@ -1,6 +1,7 @@
package com.simibubi.create.content.logistics.block.vault; package com.simibubi.create.content.logistics.block.vault;
import com.simibubi.create.AllSpriteShifts; import com.simibubi.create.AllSpriteShifts;
import com.simibubi.create.api.connectivity.ConnectivityHandler;
import com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry; import com.simibubi.create.foundation.block.connected.CTSpriteShiftEntry;
import com.simibubi.create.foundation.block.connected.ConnectedTextureBehaviour; import com.simibubi.create.foundation.block.connected.ConnectedTextureBehaviour;
@ -62,7 +63,7 @@ public class ItemVaultCTBehaviour extends ConnectedTextureBehaviour {
@Override @Override
public boolean connectsTo(BlockState state, BlockState other, BlockAndTintGetter reader, BlockPos pos, public boolean connectsTo(BlockState state, BlockState other, BlockAndTintGetter reader, BlockPos pos,
BlockPos otherPos, Direction face) { BlockPos otherPos, Direction face) {
return state == other && ItemVaultConnectivityHandler.isConnected(reader, pos, otherPos); return state == other && ConnectivityHandler.isConnected(reader, pos, otherPos); //ItemVaultConnectivityHandler.isConnected(reader, pos, otherPos);
} }
} }

View file

@ -1,329 +0,0 @@
package com.simibubi.create.content.logistics.block.vault;
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.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;
public class ItemVaultConnectivityHandler {
public static void formVaults(ItemVaultTileEntity te) {
VaultSearchCache cache = new VaultSearchCache();
List<ItemVaultTileEntity> frontier = new ArrayList<>();
frontier.add(te);
formVaults(te.getType(), te.getLevel(), cache, frontier);
}
private static void formVaults(BlockEntityType<?> type, BlockGetter world, VaultSearchCache cache,
List<ItemVaultTileEntity> frontier) {
PriorityQueue<Pair<Integer, ItemVaultTileEntity>> creationQueue = makeCreationQueue();
Set<BlockPos> 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<Integer, ItemVaultTileEntity> 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<ItemVaultTileEntity> 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<ItemVaultTileEntity> 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<Pair<Integer, ItemVaultTileEntity>> makeCreationQueue() {
return new PriorityQueue<>(new Comparator<Pair<Integer, ItemVaultTileEntity>>() {
@Override
public int compare(Pair<Integer, ItemVaultTileEntity> o1, Pair<Integer, ItemVaultTileEntity> 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<BlockPos, Optional<ItemVaultTileEntity>> 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<ItemVaultTileEntity> 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());
}
}

View file

@ -1,6 +1,7 @@
package com.simibubi.create.content.logistics.block.vault; package com.simibubi.create.content.logistics.block.vault;
import com.simibubi.create.AllTileEntities; import com.simibubi.create.AllTileEntities;
import com.simibubi.create.api.connectivity.ConnectivityHandler;
import com.simibubi.create.foundation.utility.VecHelper; import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@ -64,7 +65,7 @@ public class ItemVaultItem extends BlockItem {
if (!ItemVaultBlock.isVault(placedOnState)) if (!ItemVaultBlock.isVault(placedOnState))
return; return;
ItemVaultTileEntity tankAt = ItemVaultConnectivityHandler.vaultAt(AllTileEntities.ITEM_VAULT.get(), world, placedOnPos); ItemVaultTileEntity tankAt = ConnectivityHandler.partAt(AllTileEntities.ITEM_VAULT.get(), world, placedOnPos);
if (tankAt == null) if (tankAt == null)
return; return;
ItemVaultTileEntity controllerTE = tankAt.getControllerTE(); ItemVaultTileEntity controllerTE = tankAt.getControllerTE();

View file

@ -3,6 +3,7 @@ package com.simibubi.create.content.logistics.block.vault;
import java.util.List; import java.util.List;
import com.simibubi.create.AllTileEntities; import com.simibubi.create.AllTileEntities;
import com.simibubi.create.api.connectivity.ConnectivityHandler;
import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.tileEntity.IMultiTileContainer; import com.simibubi.create.foundation.tileEntity.IMultiTileContainer;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity; 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.ItemStackHandler;
import net.minecraftforge.items.wrapper.CombinedInvWrapper; import net.minecraftforge.items.wrapper.CombinedInvWrapper;
public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileContainer { public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileContainer.Inventory {
protected LazyOptional<IItemHandler> itemCapability; protected LazyOptional<IItemHandler> itemCapability;
@ -62,7 +63,7 @@ public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileCo
return; return;
if (!isController()) if (!isController())
return; return;
ItemVaultConnectivityHandler.formVaults(this); ConnectivityHandler.formMulti(this);
} }
protected void updateComparators() { protected void updateComparators() {
@ -248,7 +249,7 @@ public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileCo
BlockPos vaultPos = alongZ ? worldPosition.offset(xOffset, zOffset, yOffset) BlockPos vaultPos = alongZ ? worldPosition.offset(xOffset, zOffset, yOffset)
: worldPosition.offset(yOffset, xOffset, zOffset); : worldPosition.offset(yOffset, xOffset, zOffset);
ItemVaultTileEntity vaultAt = 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] = invs[yOffset * radius * radius + xOffset * radius + zOffset] =
vaultAt != null ? vaultAt.inventory : new ItemStackHandler(); vaultAt != null ? vaultAt.inventory : new ItemStackHandler();
} }
@ -263,4 +264,45 @@ public class ItemVaultTileEntity extends SmartTileEntity implements IMultiTileCo
return radius * 3; 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; }
} }

View file

@ -1,12 +1,75 @@
package com.simibubi.create.foundation.tileEntity; package com.simibubi.create.foundation.tileEntity;
import net.minecraft.core.BlockPos; 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 interface IMultiTileContainer {
public BlockPos getController(); BlockPos getController();
public boolean isController(); <T extends BlockEntity & IMultiTileContainer> T getControllerTE ();
public void setController(BlockPos pos); boolean isController();
public BlockPos getLastKnownPos(); 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; }
}
} }