CreateMod/src/main/java/com/simibubi/create/content/processing/basin/BasinBlockEntity.java
simibubi 6819fc1c42 Issue. Gesundheit
- Fixed an incompatibility with Supplementaries leading to a server crash
- Fixed fallback damage bar colour for air powered tools
- Various NPE guards
- Fixed sequenced assembly recipes conflicting when starting with a filling step
- Potato cannons can no longer plant crops on the side of farmland
- Fixed Mechanical rollers consuming filter items despite not supporting them
- Fixed brass tunnels not refunding previous filter items when changed
- Fixed catalyst ingredients getting consumed in the basin
2023-07-04 17:43:54 +02:00

790 lines
26 KiB
Java

package com.simibubi.create.content.processing.basin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import javax.annotation.Nonnull;
import com.google.common.collect.ImmutableList;
import com.simibubi.create.AllParticleTypes;
import com.simibubi.create.AllTags;
import com.simibubi.create.content.equipment.goggles.IHaveGoggleInformation;
import com.simibubi.create.content.fluids.FluidFX;
import com.simibubi.create.content.fluids.particle.FluidParticleData;
import com.simibubi.create.content.kinetics.belt.behaviour.DirectBeltInputBehaviour;
import com.simibubi.create.content.kinetics.mixer.MechanicalMixerBlockEntity;
import com.simibubi.create.content.processing.burner.BlazeBurnerBlock;
import com.simibubi.create.content.processing.burner.BlazeBurnerBlock.HeatLevel;
import com.simibubi.create.foundation.blockEntity.SmartBlockEntity;
import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour;
import com.simibubi.create.foundation.blockEntity.behaviour.ValueBoxTransform;
import com.simibubi.create.foundation.blockEntity.behaviour.filtering.FilteringBehaviour;
import com.simibubi.create.foundation.blockEntity.behaviour.fluid.SmartFluidTankBehaviour;
import com.simibubi.create.foundation.blockEntity.behaviour.fluid.SmartFluidTankBehaviour.TankSegment;
import com.simibubi.create.foundation.blockEntity.behaviour.inventory.InvManipulationBehaviour;
import com.simibubi.create.foundation.fluid.CombinedTankWrapper;
import com.simibubi.create.foundation.item.ItemHelper;
import com.simibubi.create.foundation.item.SmartInventory;
import com.simibubi.create.foundation.utility.AnimationTickHolder;
import com.simibubi.create.foundation.utility.Components;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.IntAttached;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.LangBuilder;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import com.simibubi.create.foundation.utility.animation.LerpedFloat;
import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Direction.Axis;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
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.minecraft.world.phys.Vec3;
import net.minecraftforge.common.capabilities.Capability;
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;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.ItemHandlerHelper;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.items.wrapper.CombinedInvWrapper;
public class BasinBlockEntity extends SmartBlockEntity implements IHaveGoggleInformation {
private boolean areFluidsMoving;
LerpedFloat ingredientRotationSpeed;
LerpedFloat ingredientRotation;
public BasinInventory inputInventory;
public SmartFluidTankBehaviour inputTank;
protected SmartInventory outputInventory;
protected SmartFluidTankBehaviour outputTank;
private FilteringBehaviour filtering;
private boolean contentsChanged;
private Couple<SmartInventory> invs;
private Couple<SmartFluidTankBehaviour> tanks;
protected LazyOptional<IItemHandlerModifiable> itemCapability;
protected LazyOptional<IFluidHandler> fluidCapability;
List<Direction> disabledSpoutputs;
Direction preferredSpoutput;
protected List<ItemStack> spoutputBuffer;
protected List<FluidStack> spoutputFluidBuffer;
int recipeBackupCheck;
public static final int OUTPUT_ANIMATION_TIME = 10;
List<IntAttached<ItemStack>> visualizedOutputItems;
List<IntAttached<FluidStack>> visualizedOutputFluids;
public BasinBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
inputInventory = new BasinInventory(9, this);
inputInventory.whenContentsChanged($ -> contentsChanged = true);
outputInventory = new BasinInventory(9, this).forbidInsertion()
.withMaxStackSize(64);
areFluidsMoving = false;
itemCapability = LazyOptional.of(() -> new CombinedInvWrapper(inputInventory, outputInventory));
contentsChanged = true;
ingredientRotation = LerpedFloat.angular()
.startWithValue(0);
ingredientRotationSpeed = LerpedFloat.linear()
.startWithValue(0);
invs = Couple.create(inputInventory, outputInventory);
tanks = Couple.create(inputTank, outputTank);
visualizedOutputItems = Collections.synchronizedList(new ArrayList<>());
visualizedOutputFluids = Collections.synchronizedList(new ArrayList<>());
disabledSpoutputs = new ArrayList<>();
preferredSpoutput = null;
spoutputBuffer = new ArrayList<>();
spoutputFluidBuffer = new ArrayList<>();
recipeBackupCheck = 20;
}
@Override
public void addBehaviours(List<BlockEntityBehaviour> behaviours) {
behaviours.add(new DirectBeltInputBehaviour(this));
filtering = new FilteringBehaviour(this, new BasinValueBox()).withCallback(newFilter -> contentsChanged = true)
.forRecipes();
behaviours.add(filtering);
inputTank = new SmartFluidTankBehaviour(SmartFluidTankBehaviour.INPUT, this, 2, 1000, true)
.whenFluidUpdates(() -> contentsChanged = true);
outputTank = new SmartFluidTankBehaviour(SmartFluidTankBehaviour.OUTPUT, this, 2, 1000, true)
.whenFluidUpdates(() -> contentsChanged = true)
.forbidInsertion();
behaviours.add(inputTank);
behaviours.add(outputTank);
fluidCapability = LazyOptional.of(() -> {
LazyOptional<? extends IFluidHandler> inputCap = inputTank.getCapability();
LazyOptional<? extends IFluidHandler> outputCap = outputTank.getCapability();
return new CombinedTankWrapper(outputCap.orElse(null), inputCap.orElse(null));
});
}
@Override
protected void read(CompoundTag compound, boolean clientPacket) {
super.read(compound, clientPacket);
inputInventory.deserializeNBT(compound.getCompound("InputItems"));
outputInventory.deserializeNBT(compound.getCompound("OutputItems"));
preferredSpoutput = null;
if (compound.contains("PreferredSpoutput"))
preferredSpoutput = NBTHelper.readEnum(compound, "PreferredSpoutput", Direction.class);
disabledSpoutputs.clear();
ListTag disabledList = compound.getList("DisabledSpoutput", Tag.TAG_STRING);
disabledList.forEach(d -> disabledSpoutputs.add(Direction.valueOf(((StringTag) d).getAsString())));
spoutputBuffer = NBTHelper.readItemList(compound.getList("Overflow", Tag.TAG_COMPOUND));
spoutputFluidBuffer = NBTHelper.readCompoundList(compound.getList("FluidOverflow", Tag.TAG_COMPOUND),
FluidStack::loadFluidStackFromNBT);
if (!clientPacket)
return;
NBTHelper.iterateCompoundList(compound.getList("VisualizedItems", Tag.TAG_COMPOUND),
c -> visualizedOutputItems.add(IntAttached.with(OUTPUT_ANIMATION_TIME, ItemStack.of(c))));
NBTHelper.iterateCompoundList(compound.getList("VisualizedFluids", Tag.TAG_COMPOUND),
c -> visualizedOutputFluids
.add(IntAttached.with(OUTPUT_ANIMATION_TIME, FluidStack.loadFluidStackFromNBT(c))));
}
@Override
public void write(CompoundTag compound, boolean clientPacket) {
super.write(compound, clientPacket);
compound.put("InputItems", inputInventory.serializeNBT());
compound.put("OutputItems", outputInventory.serializeNBT());
if (preferredSpoutput != null)
NBTHelper.writeEnum(compound, "PreferredSpoutput", preferredSpoutput);
ListTag disabledList = new ListTag();
disabledSpoutputs.forEach(d -> disabledList.add(StringTag.valueOf(d.name())));
compound.put("DisabledSpoutput", disabledList);
compound.put("Overflow", NBTHelper.writeItemList(spoutputBuffer));
compound.put("FluidOverflow",
NBTHelper.writeCompoundList(spoutputFluidBuffer, fs -> fs.writeToNBT(new CompoundTag())));
if (!clientPacket)
return;
compound.put("VisualizedItems", NBTHelper.writeCompoundList(visualizedOutputItems, ia -> ia.getValue()
.serializeNBT()));
compound.put("VisualizedFluids", NBTHelper.writeCompoundList(visualizedOutputFluids, ia -> ia.getValue()
.writeToNBT(new CompoundTag())));
visualizedOutputItems.clear();
visualizedOutputFluids.clear();
}
@Override
public void destroy() {
super.destroy();
ItemHelper.dropContents(level, worldPosition, inputInventory);
ItemHelper.dropContents(level, worldPosition, outputInventory);
spoutputBuffer.forEach(is -> Block.popResource(level, worldPosition, is));
}
@Override
public void remove() {
super.remove();
onEmptied();
}
public void onEmptied() {
getOperator().ifPresent(be -> be.basinRemoved = true);
}
@Override
public void invalidate() {
super.invalidate();
itemCapability.invalidate();
fluidCapability.invalidate();
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, Direction side) {
if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY)
return itemCapability.cast();
if (cap == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY)
return fluidCapability.cast();
return super.getCapability(cap, side);
}
@Override
public void notifyUpdate() {
super.notifyUpdate();
}
@Override
public void lazyTick() {
super.lazyTick();
if (!level.isClientSide) {
updateSpoutput();
if (recipeBackupCheck-- > 0)
return;
recipeBackupCheck = 20;
if (isEmpty())
return;
notifyChangeOfContents();
return;
}
BlockEntity blockEntity = level.getBlockEntity(worldPosition.above(2));
if (!(blockEntity instanceof MechanicalMixerBlockEntity)) {
setAreFluidsMoving(false);
return;
}
MechanicalMixerBlockEntity mixer = (MechanicalMixerBlockEntity) blockEntity;
setAreFluidsMoving(mixer.running && mixer.runningTicks <= 20);
}
public boolean isEmpty() {
return inputInventory.isEmpty() && outputInventory.isEmpty() && inputTank.isEmpty() && outputTank.isEmpty();
}
public void onWrenched(Direction face) {
BlockState blockState = getBlockState();
Direction currentFacing = blockState.getValue(BasinBlock.FACING);
disabledSpoutputs.remove(face);
if (currentFacing == face) {
if (preferredSpoutput == face)
preferredSpoutput = null;
disabledSpoutputs.add(face);
} else
preferredSpoutput = face;
updateSpoutput();
}
private void updateSpoutput() {
BlockState blockState = getBlockState();
Direction currentFacing = blockState.getValue(BasinBlock.FACING);
Direction newFacing = Direction.DOWN;
for (Direction test : Iterate.horizontalDirections) {
boolean canOutputTo = BasinBlock.canOutputTo(level, worldPosition, test);
if (canOutputTo && !disabledSpoutputs.contains(test))
newFacing = test;
}
if (preferredSpoutput != null && BasinBlock.canOutputTo(level, worldPosition, preferredSpoutput)
&& preferredSpoutput != Direction.UP)
newFacing = preferredSpoutput;
if (newFacing == currentFacing)
return;
level.setBlockAndUpdate(worldPosition, blockState.setValue(BasinBlock.FACING, newFacing));
if (newFacing.getAxis()
.isVertical())
return;
for (int slot = 0; slot < outputInventory.getSlots(); slot++) {
ItemStack extractItem = outputInventory.extractItem(slot, 64, true);
if (extractItem.isEmpty())
continue;
if (acceptOutputs(ImmutableList.of(extractItem), Collections.emptyList(), true))
acceptOutputs(ImmutableList.of(outputInventory.extractItem(slot, 64, false)), Collections.emptyList(),
false);
}
IFluidHandler handler = outputTank.getCapability()
.orElse(null);
for (int slot = 0; slot < handler.getTanks(); slot++) {
FluidStack fs = handler.getFluidInTank(slot)
.copy();
if (fs.isEmpty())
continue;
if (acceptOutputs(Collections.emptyList(), ImmutableList.of(fs), true)) {
handler.drain(fs, FluidAction.EXECUTE);
acceptOutputs(Collections.emptyList(), ImmutableList.of(fs), false);
}
}
notifyChangeOfContents();
notifyUpdate();
}
@Override
public void tick() {
super.tick();
if (level.isClientSide) {
createFluidParticles();
tickVisualizedOutputs();
ingredientRotationSpeed.tickChaser();
ingredientRotation.setValue(ingredientRotation.getValue() + ingredientRotationSpeed.getValue());
}
if ((!spoutputBuffer.isEmpty() || !spoutputFluidBuffer.isEmpty()) && !level.isClientSide)
tryClearingSpoutputOverflow();
if (!contentsChanged)
return;
contentsChanged = false;
getOperator().ifPresent(be -> be.basinChecker.scheduleUpdate());
for (Direction offset : Iterate.horizontalDirections) {
BlockPos toUpdate = worldPosition.above()
.relative(offset);
BlockState stateToUpdate = level.getBlockState(toUpdate);
if (stateToUpdate.getBlock() instanceof BasinBlock
&& stateToUpdate.getValue(BasinBlock.FACING) == offset.getOpposite()) {
BlockEntity be = level.getBlockEntity(toUpdate);
if (be instanceof BasinBlockEntity)
((BasinBlockEntity) be).contentsChanged = true;
}
}
}
private void tryClearingSpoutputOverflow() {
BlockState blockState = getBlockState();
if (!(blockState.getBlock() instanceof BasinBlock))
return;
Direction direction = blockState.getValue(BasinBlock.FACING);
BlockEntity be = level.getBlockEntity(worldPosition.below()
.relative(direction));
FilteringBehaviour filter = null;
InvManipulationBehaviour inserter = null;
if (be != null) {
filter = BlockEntityBehaviour.get(level, be.getBlockPos(), FilteringBehaviour.TYPE);
inserter = BlockEntityBehaviour.get(level, be.getBlockPos(), InvManipulationBehaviour.TYPE);
}
IItemHandler targetInv = be == null ? null
: be.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, direction.getOpposite())
.orElse(inserter == null ? null : inserter.getInventory());
IFluidHandler targetTank = be == null ? null
: be.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, direction.getOpposite())
.orElse(null);
boolean update = false;
for (Iterator<ItemStack> iterator = spoutputBuffer.iterator(); iterator.hasNext();) {
ItemStack itemStack = iterator.next();
if (direction == Direction.DOWN) {
Block.popResource(level, worldPosition, itemStack);
iterator.remove();
update = true;
continue;
}
if (targetInv == null)
break;
if (!ItemHandlerHelper.insertItemStacked(targetInv, itemStack, true)
.isEmpty())
continue;
if (filter != null && !filter.test(itemStack))
continue;
update = true;
ItemHandlerHelper.insertItemStacked(targetInv, itemStack.copy(), false);
iterator.remove();
visualizedOutputItems.add(IntAttached.withZero(itemStack));
}
for (Iterator<FluidStack> iterator = spoutputFluidBuffer.iterator(); iterator.hasNext();) {
FluidStack fluidStack = iterator.next();
if (direction == Direction.DOWN) {
iterator.remove();
update = true;
continue;
}
if (targetTank == null)
break;
for (boolean simulate : Iterate.trueAndFalse) {
FluidAction action = simulate ? FluidAction.SIMULATE : FluidAction.EXECUTE;
int fill = targetTank instanceof SmartFluidTankBehaviour.InternalFluidHandler
? ((SmartFluidTankBehaviour.InternalFluidHandler) targetTank).forceFill(fluidStack.copy(), action)
: targetTank.fill(fluidStack.copy(), action);
if (fill != fluidStack.getAmount())
break;
if (simulate)
continue;
update = true;
iterator.remove();
visualizedOutputFluids.add(IntAttached.withZero(fluidStack));
}
}
if (update) {
notifyChangeOfContents();
sendData();
}
}
public float getTotalFluidUnits(float partialTicks) {
int renderedFluids = 0;
float totalUnits = 0;
for (SmartFluidTankBehaviour behaviour : getTanks()) {
if (behaviour == null)
continue;
for (TankSegment tankSegment : behaviour.getTanks()) {
if (tankSegment.getRenderedFluid()
.isEmpty())
continue;
float units = tankSegment.getTotalUnits(partialTicks);
if (units < 1)
continue;
totalUnits += units;
renderedFluids++;
}
}
if (renderedFluids == 0)
return 0;
if (totalUnits < 1)
return 0;
return totalUnits;
}
private Optional<BasinOperatingBlockEntity> getOperator() {
if (level == null)
return Optional.empty();
BlockEntity be = level.getBlockEntity(worldPosition.above(2));
if (be instanceof BasinOperatingBlockEntity)
return Optional.of((BasinOperatingBlockEntity) be);
return Optional.empty();
}
public FilteringBehaviour getFilter() {
return filtering;
}
public void notifyChangeOfContents() {
contentsChanged = true;
}
public SmartInventory getInputInventory() {
return inputInventory;
}
public SmartInventory getOutputInventory() {
return outputInventory;
}
public boolean canContinueProcessing() {
return spoutputBuffer.isEmpty() && spoutputFluidBuffer.isEmpty();
}
public boolean acceptOutputs(List<ItemStack> outputItems, List<FluidStack> outputFluids, boolean simulate) {
outputInventory.allowInsertion();
outputTank.allowInsertion();
boolean acceptOutputsInner = acceptOutputsInner(outputItems, outputFluids, simulate);
outputInventory.forbidInsertion();
outputTank.forbidInsertion();
return acceptOutputsInner;
}
private boolean acceptOutputsInner(List<ItemStack> outputItems, List<FluidStack> outputFluids, boolean simulate) {
BlockState blockState = getBlockState();
if (!(blockState.getBlock() instanceof BasinBlock))
return false;
Direction direction = blockState.getValue(BasinBlock.FACING);
if (direction != Direction.DOWN) {
BlockEntity be = level.getBlockEntity(worldPosition.below()
.relative(direction));
InvManipulationBehaviour inserter =
be == null ? null : BlockEntityBehaviour.get(level, be.getBlockPos(), InvManipulationBehaviour.TYPE);
IItemHandler targetInv = be == null ? null
: be.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, direction.getOpposite())
.orElse(inserter == null ? null : inserter.getInventory());
IFluidHandler targetTank = be == null ? null
: be.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, direction.getOpposite())
.orElse(null);
boolean externalTankNotPresent = targetTank == null;
if (!outputItems.isEmpty() && targetInv == null)
return false;
if (!outputFluids.isEmpty() && externalTankNotPresent) {
// Special case - fluid outputs but output only accepts items
targetTank = outputTank.getCapability()
.orElse(null);
if (targetTank == null)
return false;
if (!acceptFluidOutputsIntoBasin(outputFluids, simulate, targetTank))
return false;
}
if (simulate)
return true;
for (ItemStack itemStack : outputItems) {
spoutputBuffer.add(itemStack.copy());
}
if (!externalTankNotPresent)
for (FluidStack fluidStack : outputFluids)
spoutputFluidBuffer.add(fluidStack.copy());
return true;
}
IItemHandler targetInv = outputInventory;
IFluidHandler targetTank = outputTank.getCapability()
.orElse(null);
if (targetInv == null && !outputItems.isEmpty())
return false;
if (!acceptItemOutputsIntoBasin(outputItems, simulate, targetInv))
return false;
if (outputFluids.isEmpty())
return true;
if (targetTank == null)
return false;
if (!acceptFluidOutputsIntoBasin(outputFluids, simulate, targetTank))
return false;
return true;
}
private boolean acceptFluidOutputsIntoBasin(List<FluidStack> outputFluids, boolean simulate,
IFluidHandler targetTank) {
for (FluidStack fluidStack : outputFluids) {
FluidAction action = simulate ? FluidAction.SIMULATE : FluidAction.EXECUTE;
int fill = targetTank instanceof SmartFluidTankBehaviour.InternalFluidHandler
? ((SmartFluidTankBehaviour.InternalFluidHandler) targetTank).forceFill(fluidStack.copy(), action)
: targetTank.fill(fluidStack.copy(), action);
if (fill != fluidStack.getAmount())
return false;
}
return true;
}
private boolean acceptItemOutputsIntoBasin(List<ItemStack> outputItems, boolean simulate, IItemHandler targetInv) {
for (ItemStack itemStack : outputItems) {
if (!ItemHandlerHelper.insertItemStacked(targetInv, itemStack.copy(), simulate)
.isEmpty())
return false;
}
return true;
}
public void readOnlyItems(CompoundTag compound) {
inputInventory.deserializeNBT(compound.getCompound("InputItems"));
outputInventory.deserializeNBT(compound.getCompound("OutputItems"));
}
public static HeatLevel getHeatLevelOf(BlockState state) {
if (state.hasProperty(BlazeBurnerBlock.HEAT_LEVEL))
return state.getValue(BlazeBurnerBlock.HEAT_LEVEL);
return AllTags.AllBlockTags.PASSIVE_BOILER_HEATERS.matches(state) ? HeatLevel.SMOULDERING : HeatLevel.NONE;
}
public Couple<SmartFluidTankBehaviour> getTanks() {
return tanks;
}
public Couple<SmartInventory> getInvs() {
return invs;
}
// client things
private void tickVisualizedOutputs() {
visualizedOutputFluids.forEach(IntAttached::decrement);
visualizedOutputItems.forEach(IntAttached::decrement);
visualizedOutputFluids.removeIf(IntAttached::isOrBelowZero);
visualizedOutputItems.removeIf(IntAttached::isOrBelowZero);
}
private void createFluidParticles() {
Random r = level.random;
if (!visualizedOutputFluids.isEmpty())
createOutputFluidParticles(r);
if (!areFluidsMoving && r.nextFloat() > 1 / 8f)
return;
int segments = 0;
for (SmartFluidTankBehaviour behaviour : getTanks()) {
if (behaviour == null)
continue;
for (TankSegment tankSegment : behaviour.getTanks())
if (!tankSegment.isEmpty(0))
segments++;
}
if (segments < 2)
return;
float totalUnits = getTotalFluidUnits(0);
if (totalUnits == 0)
return;
float fluidLevel = Mth.clamp(totalUnits / 2000, 0, 1);
float rim = 2 / 16f;
float space = 12 / 16f;
float surface = worldPosition.getY() + rim + space * fluidLevel + 1 / 32f;
if (areFluidsMoving) {
createMovingFluidParticles(surface, segments);
return;
}
for (SmartFluidTankBehaviour behaviour : getTanks()) {
if (behaviour == null)
continue;
for (TankSegment tankSegment : behaviour.getTanks()) {
if (tankSegment.isEmpty(0))
continue;
float x = worldPosition.getX() + rim + space * r.nextFloat();
float z = worldPosition.getZ() + rim + space * r.nextFloat();
level.addAlwaysVisibleParticle(
new FluidParticleData(AllParticleTypes.BASIN_FLUID.get(), tankSegment.getRenderedFluid()), x,
surface, z, 0, 0, 0);
}
}
}
private void createOutputFluidParticles(Random r) {
BlockState blockState = getBlockState();
if (!(blockState.getBlock() instanceof BasinBlock))
return;
Direction direction = blockState.getValue(BasinBlock.FACING);
if (direction == Direction.DOWN)
return;
Vec3 directionVec = Vec3.atLowerCornerOf(direction.getNormal());
Vec3 outVec = VecHelper.getCenterOf(worldPosition)
.add(directionVec.scale(.65)
.subtract(0, 1 / 4f, 0));
Vec3 outMotion = directionVec.scale(1 / 16f)
.add(0, -1 / 16f, 0);
for (int i = 0; i < 2; i++) {
visualizedOutputFluids.forEach(ia -> {
FluidStack fluidStack = ia.getValue();
ParticleOptions fluidParticle = FluidFX.getFluidParticle(fluidStack);
Vec3 m = VecHelper.offsetRandomly(outMotion, r, 1 / 16f);
level.addAlwaysVisibleParticle(fluidParticle, outVec.x, outVec.y, outVec.z, m.x, m.y, m.z);
});
}
}
private void createMovingFluidParticles(float surface, int segments) {
Vec3 pointer = new Vec3(1, 0, 0).scale(1 / 16f);
float interval = 360f / segments;
Vec3 centerOf = VecHelper.getCenterOf(worldPosition);
float intervalOffset = (AnimationTickHolder.getTicks() * 18) % 360;
int currentSegment = 0;
for (SmartFluidTankBehaviour behaviour : getTanks()) {
if (behaviour == null)
continue;
for (TankSegment tankSegment : behaviour.getTanks()) {
if (tankSegment.isEmpty(0))
continue;
float angle = interval * (1 + currentSegment) + intervalOffset;
Vec3 vec = centerOf.add(VecHelper.rotate(pointer, angle, Axis.Y));
level.addAlwaysVisibleParticle(
new FluidParticleData(AllParticleTypes.BASIN_FLUID.get(), tankSegment.getRenderedFluid()), vec.x(),
surface, vec.z(), 1, 0, 0);
currentSegment++;
}
}
}
public boolean areFluidsMoving() {
return areFluidsMoving;
}
public boolean setAreFluidsMoving(boolean areFluidsMoving) {
this.areFluidsMoving = areFluidsMoving;
ingredientRotationSpeed.chase(areFluidsMoving ? 20 : 0, .1f, Chaser.EXP);
return areFluidsMoving;
}
@Override
public boolean addToGoggleTooltip(List<Component> tooltip, boolean isPlayerSneaking) {
Lang.translate("gui.goggles.basin_contents")
.forGoggles(tooltip);
IItemHandlerModifiable items = itemCapability.orElse(new ItemStackHandler());
IFluidHandler fluids = fluidCapability.orElse(new FluidTank(0));
boolean isEmpty = true;
for (int i = 0; i < items.getSlots(); i++) {
ItemStack stackInSlot = items.getStackInSlot(i);
if (stackInSlot.isEmpty())
continue;
Lang.text("")
.add(Components.translatable(stackInSlot.getDescriptionId())
.withStyle(ChatFormatting.GRAY))
.add(Lang.text(" x" + stackInSlot.getCount())
.style(ChatFormatting.GREEN))
.forGoggles(tooltip, 1);
isEmpty = false;
}
LangBuilder mb = Lang.translate("generic.unit.millibuckets");
for (int i = 0; i < fluids.getTanks(); i++) {
FluidStack fluidStack = fluids.getFluidInTank(i);
if (fluidStack.isEmpty())
continue;
Lang.text("")
.add(Lang.fluidName(fluidStack)
.add(Lang.text(" "))
.style(ChatFormatting.GRAY)
.add(Lang.number(fluidStack.getAmount())
.add(mb)
.style(ChatFormatting.BLUE)))
.forGoggles(tooltip, 1);
isEmpty = false;
}
if (isEmpty)
tooltip.remove(0);
return true;
}
class BasinValueBox extends ValueBoxTransform.Sided {
@Override
protected Vec3 getSouthLocation() {
return VecHelper.voxelSpace(8, 12, 16.05);
}
@Override
protected boolean isSideActive(BlockState state, Direction direction) {
return direction.getAxis()
.isHorizontal();
}
}
}