CreateMod/src/main/java/com/simibubi/create/content/contraptions/fluids/tank/FluidTankTileEntity.java
PepperBell e55ae22d7d Fix fluid tank render boxes
- Update render box on client TE initialization and update packet
- Resolves #1669
2021-06-17 14:43:40 -07:00

469 lines
13 KiB
Java

package com.simibubi.create.content.contraptions.fluids.tank;
import static java.lang.Math.abs;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
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;
import com.simibubi.create.foundation.fluid.SmartFluidTank;
import com.simibubi.create.foundation.gui.widgets.InterpolatedChasingValue;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import net.minecraft.block.BlockState;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.Direction;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidAttributes;
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.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.fluids.capability.templates.FluidTank;
public class FluidTankTileEntity extends SmartTileEntity implements IHaveGoggleInformation {
private static final int MAX_SIZE = 3;
protected LazyOptional<IFluidHandler> fluidCapability;
protected boolean forceFluidLevelUpdate;
protected FluidTank tankInventory;
protected BlockPos controller;
protected BlockPos lastKnownPos;
protected boolean updateConnectivity;
protected boolean window;
protected int luminosity;
protected int width;
protected int height;
private static final int SYNC_RATE = 8;
protected int syncCooldown;
protected boolean queuedSync;
// For rendering purposes only
private InterpolatedChasingValue fluidLevel;
private AxisAlignedBB renderBoundingBox;
public FluidTankTileEntity(TileEntityType<?> tileEntityTypeIn) {
super(tileEntityTypeIn);
tankInventory = createInventory();
fluidCapability = LazyOptional.of(() -> tankInventory);
forceFluidLevelUpdate = true;
updateConnectivity = false;
window = true;
height = 1;
width = 1;
refreshCapability();
}
protected SmartFluidTank createInventory() {
return new SmartFluidTank(getCapacityMultiplier(), this::onFluidStackChanged);
}
protected void updateConnectivity() {
updateConnectivity = false;
if (world.isRemote)
return;
if (!isController())
return;
FluidTankConnectivityHandler.formTanks(this);
}
@Override
public void tick() {
super.tick();
if (syncCooldown > 0) {
syncCooldown--;
if (syncCooldown == 0 && queuedSync)
sendData();
}
if (lastKnownPos == null)
lastKnownPos = getPos();
else if (!lastKnownPos.equals(pos) && pos != null) {
onPositionChanged();
return;
}
if (updateConnectivity)
updateConnectivity();
if (fluidLevel != null)
fluidLevel.tick();
}
public boolean isController() {
return controller == null
|| pos.getX() == controller.getX() && pos.getY() == controller.getY() && pos.getZ() == controller.getZ();
}
@Override
public void initialize() {
super.initialize();
sendData();
if (world.isRemote)
updateRenderBoundingBox();
}
private void onPositionChanged() {
removeController(true);
lastKnownPos = pos;
}
protected void onFluidStackChanged(FluidStack newFluidStack) {
if (!hasWorld())
return;
FluidAttributes attributes = newFluidStack.getFluid()
.getAttributes();
int luminosity = (int) (attributes.getLuminosity(newFluidStack) / 1.2f);
boolean reversed = attributes.isLighterThanAir();
int maxY = (int) ((getFillState() * height) + 1);
for (int yOffset = 0; yOffset < height; yOffset++) {
boolean isBright = reversed ? (height - yOffset <= maxY) : (yOffset < maxY);
int actualLuminosity = isBright ? luminosity : luminosity > 0 ? 1 : 0;
for (int xOffset = 0; xOffset < width; xOffset++) {
for (int zOffset = 0; zOffset < width; zOffset++) {
BlockPos pos = this.pos.add(xOffset, yOffset, zOffset);
FluidTankTileEntity tankAt = FluidTankConnectivityHandler.anyTankAt(world, pos);
if (tankAt == null)
continue;
world.updateComparatorOutputLevel(pos, tankAt.getBlockState()
.getBlock());
if (tankAt.luminosity == actualLuminosity)
continue;
tankAt.setLuminosity(actualLuminosity);
}
}
}
if (!world.isRemote) {
markDirty();
sendData();
}
}
protected void setLuminosity(int luminosity) {
if (world.isRemote)
return;
if (this.luminosity == luminosity)
return;
this.luminosity = luminosity;
sendData();
}
public FluidTankTileEntity getControllerTE() {
if (isController())
return this;
TileEntity tileEntity = world.getTileEntity(controller);
if (tileEntity instanceof FluidTankTileEntity)
return (FluidTankTileEntity) tileEntity;
return null;
}
public void applyFluidTankSize(int blocks) {
tankInventory.setCapacity(blocks * getCapacityMultiplier());
int overflow = tankInventory.getFluidAmount() - tankInventory.getCapacity();
if (overflow > 0)
tankInventory.drain(overflow, FluidAction.EXECUTE);
forceFluidLevelUpdate = true;
}
public void removeController(boolean keepFluids) {
if (world.isRemote)
return;
updateConnectivity = true;
if (!keepFluids)
applyFluidTankSize(1);
controller = null;
width = 1;
height = 1;
onFluidStackChanged(tankInventory.getFluid());
BlockState state = getBlockState();
if (FluidTankBlock.isTank(state)) {
state = state.with(FluidTankBlock.BOTTOM, true);
state = state.with(FluidTankBlock.TOP, true);
state = state.with(FluidTankBlock.SHAPE, window ? Shape.WINDOW : Shape.PLAIN);
getWorld().setBlockState(pos, state, 22);
}
refreshCapability();
markDirty();
sendData();
}
public void toggleWindows() {
FluidTankTileEntity te = getControllerTE();
if (te == null)
return;
te.setWindows(!te.window);
}
public void sendDataImmediately() {
syncCooldown = 0;
queuedSync = false;
sendData();
}
@Override
public void sendData() {
if (syncCooldown > 0) {
queuedSync = true;
return;
}
super.sendData();
queuedSync = false;
syncCooldown = SYNC_RATE;
}
public void setWindows(boolean window) {
this.window = window;
for (int yOffset = 0; yOffset < height; yOffset++) {
for (int xOffset = 0; xOffset < width; xOffset++) {
for (int zOffset = 0; zOffset < width; zOffset++) {
BlockPos pos = this.pos.add(xOffset, yOffset, zOffset);
BlockState blockState = world.getBlockState(pos);
if (!FluidTankBlock.isTank(blockState))
continue;
Shape shape = Shape.PLAIN;
if (window) {
// SIZE 1: Every tank has a window
if (width == 1)
shape = Shape.WINDOW;
// SIZE 2: Every tank has a corner window
if (width == 2)
shape = xOffset == 0 ? zOffset == 0 ? Shape.WINDOW_NW : Shape.WINDOW_SW
: zOffset == 0 ? Shape.WINDOW_NE : Shape.WINDOW_SE;
// SIZE 3: Tanks in the center have a window
if (width == 3 && abs(abs(xOffset) - abs(zOffset)) == 1)
shape = Shape.WINDOW;
}
world.setBlockState(pos, blockState.with(FluidTankBlock.SHAPE, shape), 22);
world.getChunkProvider()
.getLightManager()
.checkBlock(pos);
}
}
}
}
public void setController(BlockPos controller) {
if (world.isRemote)
return;
if (controller.equals(this.controller))
return;
this.controller = controller;
refreshCapability();
markDirty();
sendData();
}
private void refreshCapability() {
LazyOptional<IFluidHandler> oldCap = fluidCapability;
fluidCapability = LazyOptional.of(() -> isController() ? tankInventory
: getControllerTE() != null ? getControllerTE().tankInventory : new FluidTank(0));
oldCap.invalidate();
}
public BlockPos getController() {
return isController() ? pos : controller;
}
public void updateRenderBoundingBox() {
if (isController())
renderBoundingBox = super.getRenderBoundingBox().expand(width - 1, height - 1, width - 1);
else
renderBoundingBox = super.getRenderBoundingBox();
}
@Override
@OnlyIn(Dist.CLIENT)
public AxisAlignedBB getRenderBoundingBox() {
if (renderBoundingBox == null) {
renderBoundingBox = super.getRenderBoundingBox();
}
return renderBoundingBox;
}
@Override
@OnlyIn(Dist.CLIENT)
public double getMaxRenderDistanceSquared() {
int dist = 64 + getMaxHeight() * 2;
return dist * dist;
}
@Nullable
public FluidTankTileEntity getOtherFluidTankTileEntity(Direction direction) {
TileEntity otherTE = world.getTileEntity(pos.offset(direction));
if (otherTE instanceof FluidTankTileEntity)
return (FluidTankTileEntity) otherTE;
return null;
}
@Override
public boolean addToGoggleTooltip(List<ITextComponent> tooltip, boolean isPlayerSneaking) {
FluidTankTileEntity controllerTE = getControllerTE();
if (controllerTE == null)
return false;
return containedFluidTooltip(tooltip, isPlayerSneaking,
controllerTE.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY));
}
@Override
protected void fromTag(BlockState state, CompoundNBT compound, boolean clientPacket) {
super.fromTag(state, compound, clientPacket);
BlockPos controllerBefore = controller;
int prevSize = width;
int prevHeight = height;
int prevLum = luminosity;
updateConnectivity = compound.contains("Uninitialized");
luminosity = compound.getInt("Luminosity");
controller = null;
lastKnownPos = null;
if (compound.contains("LastKnownPos"))
lastKnownPos = NBTUtil.readBlockPos(compound.getCompound("LastKnownPos"));
if (compound.contains("Controller"))
controller = NBTUtil.readBlockPos(compound.getCompound("Controller"));
if (isController()) {
window = compound.getBoolean("Window");
width = compound.getInt("Size");
height = compound.getInt("Height");
tankInventory.setCapacity(getTotalTankSize() * getCapacityMultiplier());
tankInventory.readFromNBT(compound.getCompound("TankContent"));
if (tankInventory.getSpace() < 0)
tankInventory.drain(-tankInventory.getSpace(), FluidAction.EXECUTE);
}
if (compound.contains("ForceFluidLevel") || fluidLevel == null)
fluidLevel = new InterpolatedChasingValue().start(getFillState())
.withSpeed(1 / 2f);
if (!clientPacket)
return;
boolean changeOfController =
controllerBefore == null ? controller != null : !controllerBefore.equals(controller);
if (changeOfController || prevSize != width || prevHeight != height) {
if (hasWorld())
world.notifyBlockUpdate(getPos(), getBlockState(), getBlockState(), 16);
if (isController())
tankInventory.setCapacity(getCapacityMultiplier() * getTotalTankSize());
updateRenderBoundingBox();
}
if (isController()) {
float fillState = getFillState();
if (compound.contains("ForceFluidLevel") || fluidLevel == null)
fluidLevel = new InterpolatedChasingValue().start(fillState);
fluidLevel.target(fillState);
}
if (luminosity != prevLum && hasWorld())
world.getChunkProvider()
.getLightManager()
.checkBlock(pos);
if (compound.contains("LazySync"))
fluidLevel.withSpeed(compound.contains("LazySync") ? 1 / 8f : 1 / 2f);
}
public float getFillState() {
return (float) tankInventory.getFluidAmount() / tankInventory.getCapacity();
}
@Override
public void write(CompoundNBT compound, boolean clientPacket) {
if (updateConnectivity)
compound.putBoolean("Uninitialized", true);
if (lastKnownPos != null)
compound.put("LastKnownPos", NBTUtil.writeBlockPos(lastKnownPos));
if (!isController())
compound.put("Controller", NBTUtil.writeBlockPos(controller));
if (isController()) {
compound.putBoolean("Window", window);
compound.put("TankContent", tankInventory.writeToNBT(new CompoundNBT()));
compound.putInt("Size", width);
compound.putInt("Height", height);
}
compound.putInt("Luminosity", luminosity);
super.write(compound, clientPacket);
if (!clientPacket)
return;
if (forceFluidLevelUpdate)
compound.putBoolean("ForceFluidLevel", true);
if (queuedSync)
compound.putBoolean("LazySync", true);
forceFluidLevelUpdate = false;
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
if (!fluidCapability.isPresent())
refreshCapability();
if (cap == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY)
return fluidCapability.cast();
return super.getCapability(cap, side);
}
@Override
public void remove() {
super.remove();
}
@Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) {}
public IFluidTank getTankInventory() {
return tankInventory;
}
public int getTotalTankSize() {
return width * width * height;
}
public static int getMaxSize() {
return MAX_SIZE;
}
public static int getCapacityMultiplier() {
return AllConfigs.SERVER.fluids.fluidTankCapacity.get() * 1000;
}
public static int getMaxHeight() {
return AllConfigs.SERVER.fluids.fluidTankMaxHeight.get();
}
public InterpolatedChasingValue getFluidLevel() {
return fluidLevel;
}
public void setFluidLevel(InterpolatedChasingValue fluidLevel) {
this.fluidLevel = fluidLevel;
}
}