Filling a barrel with a water bucket now happens outside the normal tank mechanics if that water bottle has NBT. This allows NBT data to be preserved from the bucket when depositing or extracting water with NBT. This fixes some issues with Thirst Was Taken's water purity system.
750 lines
32 KiB
Java
750 lines
32 KiB
Java
/*
|
|
* Ex Deorum
|
|
* Copyright (c) 2024 thedarkcolour
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package thedarkcolour.exdeorum.blockentity;
|
|
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.network.FriendlyByteBuf;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.tags.FluidTags;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.world.InteractionHand;
|
|
import net.minecraft.world.InteractionResult;
|
|
import net.minecraft.world.entity.item.ItemEntity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.BowlFoodItem;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.item.Items;
|
|
import net.minecraft.world.item.alchemy.PotionUtils;
|
|
import net.minecraft.world.item.alchemy.Potions;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.minecraft.world.level.block.BucketPickup;
|
|
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.material.FlowingFluid;
|
|
import net.minecraft.world.level.material.Fluids;
|
|
import net.minecraftforge.common.ForgeMod;
|
|
import net.minecraftforge.common.capabilities.Capability;
|
|
import net.minecraftforge.common.capabilities.ForgeCapabilities;
|
|
import net.minecraftforge.common.util.LazyOptional;
|
|
import net.minecraftforge.fluids.FluidStack;
|
|
import net.minecraftforge.fluids.FluidType;
|
|
import net.minecraftforge.fluids.FluidUtil;
|
|
import net.minecraftforge.fluids.IFluidTank;
|
|
import net.minecraftforge.fluids.capability.IFluidHandler;
|
|
import net.minecraftforge.items.IItemHandler;
|
|
import net.minecraftforge.items.ItemHandlerHelper;
|
|
import net.minecraftforge.items.ItemStackHandler;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import thedarkcolour.exdeorum.block.BarrelBlock;
|
|
import thedarkcolour.exdeorum.blockentity.helper.FluidHelper;
|
|
import thedarkcolour.exdeorum.client.CompostColors;
|
|
import thedarkcolour.exdeorum.config.EConfig;
|
|
import thedarkcolour.exdeorum.material.BarrelMaterial;
|
|
import thedarkcolour.exdeorum.recipe.RecipeUtil;
|
|
import thedarkcolour.exdeorum.recipe.barrel.BarrelFluidMixingRecipe;
|
|
import thedarkcolour.exdeorum.recipe.barrel.FluidTransformationRecipe;
|
|
import thedarkcolour.exdeorum.registry.EBlockEntities;
|
|
import thedarkcolour.exdeorum.registry.ESounds;
|
|
|
|
public class BarrelBlockEntity extends ETankBlockEntity {
|
|
private static final int MOSS_SPREAD_RANGE = 2;
|
|
private static final int MAX_CAPACITY = 1000;
|
|
|
|
private final BarrelBlockEntity.ItemHandler item = new BarrelBlockEntity.ItemHandler();
|
|
private final BarrelBlockEntity.FluidHandler tank = new BarrelBlockEntity.FluidHandler();
|
|
public float progress;
|
|
public short compost;
|
|
// compost colors (has to be shorts because Java bytes are signed and only go to 127, when need 255)
|
|
// also used for fluid transformation (destination color)
|
|
public short r, g, b;
|
|
// Used to avoid triggering obsidian dupes in onContentsChanged, because Forge's FluidUtil actually modifies the tank for some reason
|
|
private boolean isBeingFilledByPlayer;
|
|
public final boolean transparent;
|
|
// Current transformation recipe
|
|
@Nullable
|
|
public FluidTransformationRecipe currentTransformRecipe = null;
|
|
|
|
private final LazyOptional<IItemHandler> itemHandler = LazyOptional.of(() -> this.item);
|
|
private final LazyOptional<IFluidHandler> fluidHandler = LazyOptional.of(() -> this.tank);
|
|
|
|
public BarrelBlockEntity(BlockPos pos, BlockState state) {
|
|
super(EBlockEntities.BARREL.get(), pos, state);
|
|
|
|
this.transparent = BarrelMaterial.TRANSPARENT_BARRELS.contains(state.getBlock());
|
|
}
|
|
|
|
@Override
|
|
public <T> LazyOptional<T> getCapability(Capability<T> cap, @Nullable Direction side) {
|
|
if (cap == ForgeCapabilities.FLUID_HANDLER) {
|
|
return this.fluidHandler.cast();
|
|
} else if (cap == ForgeCapabilities.ITEM_HANDLER) {
|
|
return this.itemHandler.cast();
|
|
}
|
|
|
|
return super.getCapability(cap, side);
|
|
}
|
|
|
|
@Override
|
|
public void invalidateCaps() {
|
|
super.invalidateCaps();
|
|
this.fluidHandler.invalidate();
|
|
this.itemHandler.invalidate();
|
|
}
|
|
|
|
@Override
|
|
public void saveAdditional(CompoundTag nbt) {
|
|
super.saveAdditional(nbt);
|
|
|
|
nbt.put("item", this.item.serializeNBT());
|
|
nbt.put("tank", this.tank.writeToNBT(new CompoundTag()));
|
|
nbt.putShort("compost", this.compost);
|
|
nbt.putFloat("progress", this.progress);
|
|
nbt.putShort("r", this.r);
|
|
nbt.putShort("g", this.g);
|
|
nbt.putShort("b", this.b);
|
|
}
|
|
|
|
@Override
|
|
public void load(CompoundTag nbt) {
|
|
super.load(nbt);
|
|
|
|
this.item.deserializeNBT(nbt.getCompound("item"));
|
|
this.tank.readFromNBT(nbt.getCompound("tank"));
|
|
this.compost = nbt.getShort("compost");
|
|
this.progress = nbt.getFloat("progress");
|
|
this.r = nbt.getShort("r");
|
|
this.g = nbt.getShort("g");
|
|
this.b = nbt.getShort("b");
|
|
}
|
|
|
|
@Override
|
|
public void writeVisualData(FriendlyByteBuf buffer) {
|
|
buffer.writeItem(this.item.getStackInSlot(0));
|
|
buffer.writeFluidStack(this.tank.getFluid());
|
|
buffer.writeShort(this.compost);
|
|
buffer.writeFloat(this.progress);
|
|
buffer.writeShort(this.r);
|
|
buffer.writeShort(this.g);
|
|
buffer.writeShort(this.b);
|
|
}
|
|
|
|
@Override
|
|
public void readVisualData(FriendlyByteBuf buffer) {
|
|
this.item.setStackInSlot(0, buffer.readItem());
|
|
this.tank.setFluid(buffer.readFluidStack());
|
|
this.compost = buffer.readShort();
|
|
this.progress = buffer.readFloat();
|
|
this.r = buffer.readShort();
|
|
this.g = buffer.readShort();
|
|
this.b = buffer.readShort();
|
|
}
|
|
|
|
public boolean isBrewing() {
|
|
return this.tank.getFluidAmount() == MAX_CAPACITY && this.progress != 0.0f && !isBurning();
|
|
}
|
|
|
|
public boolean isBurning() {
|
|
return this.getBlockState().ignitedByLava() && isHotFluid(this.tank.getFluid().getFluid().getFluidType()) && this.progress != 0.0f;
|
|
}
|
|
|
|
// Composting is in progress if at MAX_CAPACITY. When finished, compost is set back to 0
|
|
public boolean isComposting() {
|
|
return this.compost == MAX_CAPACITY;
|
|
}
|
|
|
|
// Returns true if there are no solid ingredients (can a fluid be inserted?)
|
|
public boolean hasNoSolids() {
|
|
return this.compost <= 0 && this.item.getStackInSlot(0).isEmpty();
|
|
}
|
|
|
|
public boolean hasFullWater() {
|
|
return this.tank.getFluidAmount() == MAX_CAPACITY && this.tank.getFluid().getFluid().is(FluidTags.WATER);
|
|
}
|
|
|
|
// Burning temp of wood according to google is 300 C or ~575 kelvin
|
|
// Molten Constantan from Thermal Expansion is 650 kelvin, so this should be fine
|
|
public static boolean isHotFluid(FluidType fluidType) {
|
|
return fluidType.getTemperature() > 575;
|
|
}
|
|
|
|
private void spawnParticlesIfBurning() {
|
|
if (isBurning()) {
|
|
BlockPos pos = getBlockPos();
|
|
int burnTicks = (int) (this.progress * 300);
|
|
|
|
if (burnTicks % 30 == 0) {
|
|
this.level.addParticle(ParticleTypes.LARGE_SMOKE, pos.getX() + Math.random(), pos.getY() + 1.2, pos.getZ() + Math.random(), 0.0, 0.0, 0.0);
|
|
} else if (burnTicks % 5 == 0) {
|
|
this.level.addParticle(ParticleTypes.SMOKE, pos.getX() + Math.random(), pos.getY() + 1.2, pos.getZ() + Math.random(), 0.0, 0.0, 0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
public ItemStack getItem() {
|
|
return this.item.getStackInSlot(0);
|
|
}
|
|
|
|
private void setItem(ItemStack item) {
|
|
this.item.setStackInSlot(0, item);
|
|
}
|
|
|
|
@Override
|
|
public IFluidTank getTank() {
|
|
return this.tank;
|
|
}
|
|
|
|
@Override
|
|
public InteractionResult use(Level level, Player player, InteractionHand hand) {
|
|
// Collect an item
|
|
if (!getItem().isEmpty()) {
|
|
return giveResultItem(level);
|
|
}
|
|
|
|
// Handle item fluid interaction first
|
|
if (hasNoSolids()) {
|
|
var wasBurning = isBurning();
|
|
|
|
this.isBeingFilledByPlayer = true;
|
|
var playerItem = player.getItemInHand(hand);
|
|
|
|
// Insert water bucket with NBT
|
|
if (playerItem.getItem() == Items.WATER_BUCKET && playerItem.hasTag()) {
|
|
var fluid = new FluidStack(Fluids.WATER, 1000, playerItem.getTag().copy());
|
|
if (this.tank.fill(fluid, IFluidHandler.FluidAction.SIMULATE) == 1000) {
|
|
this.tank.fill(fluid, IFluidHandler.FluidAction.EXECUTE);
|
|
if (!player.getAbilities().instabuild) {
|
|
playerItem.shrink(1);
|
|
if (!player.addItem(new ItemStack(Items.BUCKET))) {
|
|
player.drop(new ItemStack(Items.BUCKET), false);
|
|
}
|
|
}
|
|
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.BUCKET_EMPTY, SoundSource.NEUTRAL, 1.0F, 1.0F);
|
|
this.isBeingFilledByPlayer = false;
|
|
tryInWorldFluidMixing();
|
|
markUpdated();
|
|
|
|
if (wasBurning && !isHotFluid(this.tank.getFluid().getFluid().getFluidType())) {
|
|
this.progress = 0.0f;
|
|
}
|
|
|
|
return InteractionResult.sidedSuccess(level.isClientSide);
|
|
}
|
|
}
|
|
|
|
// Extract liquid with NBT
|
|
if (playerItem.getItem() == Items.BUCKET) {
|
|
var currentFluid = this.tank.getFluid();
|
|
if (currentFluid.getFluid() == Fluids.WATER && currentFluid.hasTag() && currentFluid.getAmount() >= 1000) {
|
|
if (this.tank.drain(1000, IFluidHandler.FluidAction.SIMULATE).getAmount() == 1000) {
|
|
this.tank.drain(1000, IFluidHandler.FluidAction.EXECUTE);
|
|
var filledBucket = new ItemStack(Items.WATER_BUCKET);
|
|
filledBucket.setTag(currentFluid.getTag().copy());
|
|
if (!player.getAbilities().instabuild) {
|
|
playerItem.shrink(1);
|
|
if (!player.addItem(filledBucket)) {
|
|
player.drop(filledBucket, false);
|
|
}
|
|
}
|
|
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.BUCKET_FILL, SoundSource.NEUTRAL, 1.0F, 1.0F);
|
|
this.isBeingFilledByPlayer = false;
|
|
tryInWorldFluidMixing();
|
|
markUpdated();
|
|
return InteractionResult.sidedSuccess(level.isClientSide);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FluidUtil.interactWithFluidHandler(player, hand, this.tank)) {
|
|
this.isBeingFilledByPlayer = false;
|
|
tryInWorldFluidMixing();
|
|
markUpdated();
|
|
|
|
// If the item is a fluid handler, try to transfer fluids
|
|
if (wasBurning && !isHotFluid(this.tank.getFluid().getFluid().getFluidType())) {
|
|
this.progress = 0.0f;
|
|
}
|
|
|
|
return InteractionResult.sidedSuccess(level.isClientSide);
|
|
} else {
|
|
this.isBeingFilledByPlayer = false;
|
|
// try one more time to transfer fluids between item and barrel
|
|
if (EConfig.SERVER.allowWaterBottleTransfer.get()) {
|
|
var fluid = new FluidStack(Fluids.WATER, 250);
|
|
|
|
if (playerItem.getItem() == Items.POTION && PotionUtils.getPotion(playerItem) == Potions.WATER) {
|
|
// Transfer any extra NBT tags from other mods to the fluid
|
|
var nbt = playerItem.getTag().copy();
|
|
nbt.remove("Potion");
|
|
if (nbt.isEmpty()) nbt = null;
|
|
fluid = new FluidStack(Fluids.WATER, 250, nbt);
|
|
|
|
if (this.tank.fill(fluid, IFluidHandler.FluidAction.SIMULATE) > 0) {
|
|
if (!player.getAbilities().instabuild) {
|
|
playerItem.shrink(1);
|
|
ItemStack emptyBottle = new ItemStack(Items.GLASS_BOTTLE);
|
|
if (!player.addItem(emptyBottle)) {
|
|
player.drop(emptyBottle, false);
|
|
}
|
|
}
|
|
this.tank.fill(fluid, IFluidHandler.FluidAction.EXECUTE);
|
|
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.BOTTLE_FILL, SoundSource.NEUTRAL, 1.0F, 1.0F);
|
|
|
|
markUpdated();
|
|
return InteractionResult.sidedSuccess(level.isClientSide);
|
|
}
|
|
} else if (playerItem.getItem() == Items.GLASS_BOTTLE) {
|
|
// Check if current fluid is water and use any NBT data
|
|
var currentFluid = this.tank.getFluid();
|
|
if (currentFluid.getRawFluid() == Fluids.WATER) {
|
|
fluid.setTag(currentFluid.getTag());
|
|
}
|
|
if (this.tank.drain(fluid, IFluidHandler.FluidAction.SIMULATE).getAmount() == 250) {
|
|
extractWaterBottle(this.tank, level, player, playerItem, fluid);
|
|
|
|
markUpdated();
|
|
return InteractionResult.sidedSuccess(level.isClientSide);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, mix the item's fluid into the barrel's fluid
|
|
var itemFluidCapOptional = playerItem.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).resolve();
|
|
if (itemFluidCapOptional.isPresent()) {
|
|
var itemFluidCap = itemFluidCapOptional.get();
|
|
var itemFluid = itemFluidCap.drain(1000, IFluidHandler.FluidAction.SIMULATE);
|
|
BarrelFluidMixingRecipe recipe = RecipeUtil.getFluidMixingRecipe(this.tank.getFluid(), itemFluid.getFluid());
|
|
|
|
// If draining item fluid was possible and tank has enough fluid to mix...
|
|
if (recipe != null && this.tank.getFluidAmount() >= recipe.baseFluidAmount && itemFluid.getAmount() == 1000) {
|
|
if (!level.isClientSide) {
|
|
this.tank.drain(recipe.baseFluidAmount, IFluidHandler.FluidAction.EXECUTE);
|
|
ItemStack result = new ItemStack(recipe.result);
|
|
result.setTag(recipe.getResultNbt());
|
|
setItem(result);
|
|
|
|
if (recipe.consumesAdditive) {
|
|
itemFluidCap.drain(1000, IFluidHandler.FluidAction.EXECUTE);
|
|
if (!player.getAbilities().instabuild) {
|
|
playerItem.shrink(1);
|
|
ItemStack container = itemFluidCap.getContainer();
|
|
if (!container.isEmpty()) {
|
|
if (!player.addItem(container)) {
|
|
player.drop(container, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// If a mix was successful, skip rest of logic
|
|
return InteractionResult.sidedSuccess(level.isClientSide);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the barrel has no solids and no fluid mixing/transfer happened
|
|
var playerItem = player.getItemInHand(hand);
|
|
if (!level.isClientSide) {
|
|
// mix item ingredient into fluid OR turn into compost (delegated to item handler)
|
|
var handItem = this.item.insertItem(0, player.getAbilities().instabuild ? playerItem.copy() : playerItem, false);
|
|
|
|
if (!player.getAbilities().instabuild) {
|
|
player.setItemInHand(hand, handItem);
|
|
giveResultItem(level);
|
|
}
|
|
}
|
|
|
|
return InteractionResult.sidedSuccess(level.isClientSide);
|
|
}
|
|
|
|
// Also used by Water Crucibles
|
|
protected static void extractWaterBottle(IFluidHandler tank, Level level, Player player, ItemStack playerItem, FluidStack fluid) {
|
|
if (!player.getAbilities().instabuild) {
|
|
playerItem.shrink(1);
|
|
}
|
|
tank.drain(fluid, IFluidHandler.FluidAction.EXECUTE);
|
|
var bottle = PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.WATER);
|
|
if (fluid.hasTag()) {
|
|
var nbt = bottle.getOrCreateTag();
|
|
nbt.merge(fluid.getTag());
|
|
}
|
|
if (!player.addItem(bottle)) {
|
|
player.drop(bottle, false);
|
|
}
|
|
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.BOTTLE_EMPTY, SoundSource.NEUTRAL, 1.0F, 1.0F);
|
|
}
|
|
|
|
// Pops the item out of the barrel (ex. dirt that has finished composting)
|
|
private InteractionResult giveResultItem(Level level) {
|
|
if (!level.isClientSide) {
|
|
popOutItem(level, this.worldPosition, this.item.extract(false));
|
|
|
|
// Empty contents
|
|
setItem(ItemStack.EMPTY);
|
|
markUpdated();
|
|
}
|
|
|
|
return InteractionResult.sidedSuccess(level.isClientSide);
|
|
}
|
|
|
|
private static void popOutItem(Level level, BlockPos pos, ItemStack stack) {
|
|
if (!level.isClientSide && !stack.isEmpty()) {
|
|
var rand = level.random;
|
|
var itemEntity = new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 1.5, pos.getZ() + 0.5, stack);
|
|
itemEntity.setDeltaMovement(rand.nextGaussian() * 0.05, 0.2, rand.nextGaussian() * 0.05);
|
|
level.addFreshEntity(itemEntity);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param input The input item to craft with
|
|
* @param simulate Whether the craft should actually take place
|
|
* @return Whether a craft was made or is possible
|
|
*/
|
|
private boolean tryCrafting(ItemStack input, boolean simulate) {
|
|
boolean crafted = false;
|
|
if (!this.tank.isEmpty()) {
|
|
crafted = tryMixing(input, simulate);
|
|
} else if (!isComposting()) {
|
|
crafted = tryComposting(input, simulate);
|
|
}
|
|
|
|
if (crafted) {
|
|
if (!simulate) {
|
|
markUpdated();
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param playerItem The item to try to mix with this barrel's fluid
|
|
* @param simulate Whether this is a test or the player is actually mixing with the barrel
|
|
* @return Whether something was mixed (if simulated, whether the mix is possible)
|
|
*/
|
|
private boolean tryMixing(ItemStack playerItem, boolean simulate) {
|
|
if (isBurning()) {
|
|
return false;
|
|
}
|
|
|
|
var recipe = RecipeUtil.getBarrelMixingRecipe(this.level.getRecipeManager(), playerItem, this.tank.getFluid());
|
|
|
|
if (recipe != null) {
|
|
if (!simulate) {
|
|
// Empty barrel
|
|
this.tank.drain(recipe.fluidAmount, IFluidHandler.FluidAction.EXECUTE);
|
|
// Replace fluid with result
|
|
ItemStack result = new ItemStack(recipe.result);
|
|
result.setTag(recipe.getResultNbt());
|
|
setItem(result);
|
|
this.level.playSound(null, this.worldPosition, ESounds.BARREL_MIXING.get(), SoundSource.BLOCKS, 0.8f, 1.0f);
|
|
}
|
|
// Mixing was successful, so return true
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private boolean tryComposting(ItemStack stack, boolean simulate) {
|
|
if (simulate) {
|
|
return RecipeUtil.isCompostable(stack);
|
|
} else {
|
|
var recipe = RecipeUtil.getBarrelCompostRecipe(stack);
|
|
if (recipe != null) {
|
|
addCompost(stack, recipe.getVolume());
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void addCompost(ItemStack playerItem, int volume) {
|
|
int oldCompost = this.compost;
|
|
this.compost = (short) Math.min(MAX_CAPACITY, this.compost + volume);
|
|
|
|
if (this.compost != 0) {
|
|
if (!CompostColors.isLoaded()) {
|
|
CompostColors.loadColors();
|
|
}
|
|
|
|
float weightNew = (float) (this.compost - oldCompost) / this.compost;
|
|
float weightOld = 1 - weightNew;
|
|
var color = CompostColors.COLORS.getOrDefault(playerItem.getItem(), CompostColors.DEFAULT_COLOR);
|
|
|
|
this.r = (short) (weightNew * color.x + weightOld * this.r);
|
|
this.g = (short) (weightNew * color.y + weightOld * this.g);
|
|
this.b = (short) (weightNew * color.z + weightOld * this.b);
|
|
}
|
|
|
|
this.level.playSound(null, this.worldPosition, ESounds.BARREL_ADD_COMPOST.get(), SoundSource.BLOCKS);
|
|
}
|
|
|
|
/**
|
|
* Called in three cases, which should cover all the possible edge cases:
|
|
* <li> When the barrel is updated by a neighboring block (see {@link BarrelBlock#neighborChanged}) </li>
|
|
* <li> When the barrel is first loaded from NBT (see {@link #onLoad}) </li>
|
|
* <li> When the fluid in the barrel changes (see {@link FluidHandler#onContentsChanged()}) </li>
|
|
*/
|
|
public void tryInWorldFluidMixing() {
|
|
if (!this.level.isClientSide) {
|
|
if (!this.tank.isEmpty() && this.item.getStackInSlot(0).isEmpty()) {
|
|
var abovePos = this.worldPosition.above();
|
|
var aboveBlockState = this.level.getBlockState(abovePos);
|
|
var aboveFluidState = aboveBlockState.getFluidState();
|
|
var aboveFluid = aboveFluidState.getType();
|
|
|
|
if (aboveFluid != Fluids.EMPTY) {
|
|
BarrelFluidMixingRecipe recipe = RecipeUtil.getFluidMixingRecipe(this.tank.getFluid(), aboveFluid instanceof FlowingFluid flowing ? flowing.getSource() : aboveFluid);
|
|
|
|
if (recipe != null) {
|
|
// If additive is not consumed, just craft
|
|
// If additive is consumed, check that the additive can be consumed before crafting
|
|
if (!recipe.consumesAdditive) {
|
|
this.tank.drain(recipe.baseFluidAmount, IFluidHandler.FluidAction.EXECUTE);
|
|
setItem(new ItemStack(recipe.result));
|
|
} else if (aboveBlockState.getBlock() instanceof BucketPickup pickup) {
|
|
// If something was picked up, we can craft
|
|
if (!pickup.pickupBlock(this.level, abovePos, aboveBlockState).isEmpty()) {
|
|
this.tank.drain(recipe.baseFluidAmount, IFluidHandler.FluidAction.EXECUTE);
|
|
setItem(new ItemStack(recipe.result));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void updateFluidTransform() {
|
|
if (!this.level.isClientSide) {
|
|
if (this.tank.getFluidAmount() != MAX_CAPACITY) {
|
|
this.currentTransformRecipe = null;
|
|
} else {
|
|
var belowState = this.level.getBlockState(this.worldPosition.below());
|
|
this.currentTransformRecipe = RecipeUtil.getFluidTransformationRecipe(this.tank.getFluid().getFluid(), belowState);
|
|
|
|
if (this.currentTransformRecipe != null) {
|
|
var color = this.currentTransformRecipe.resultColor;
|
|
this.r = (short) ((color >> 16) & 0xff);
|
|
this.g = (short) ((color >> 8) & 0xff);
|
|
this.b = (short) ((color) & 0xff);
|
|
markUpdated();
|
|
}
|
|
}
|
|
this.progress = 0.0f;
|
|
}
|
|
}
|
|
|
|
public static class Ticker implements BlockEntityTicker<BarrelBlockEntity> {
|
|
@Override
|
|
public void tick(Level level, BlockPos pos, BlockState state, BarrelBlockEntity barrel) {
|
|
if (!level.isClientSide) {
|
|
var tank = barrel.tank;
|
|
// Turn compost to dirt
|
|
if (barrel.isComposting()) {
|
|
barrel.doCompost();
|
|
} else if (isHotFluid(tank.getFluid().getFluid().getFluidType()) && state.ignitedByLava()) {
|
|
if ((barrel.progress += getProgressStep()) >= 1.0f) {
|
|
if (tank.getFluidAmount() == MAX_CAPACITY) {
|
|
var fluid = tank.getFluid().getFluid();
|
|
level.setBlockAndUpdate(pos, fluid.getFluidType().getBlockForFluidState(level, pos, fluid.defaultFluidState()));
|
|
} else {
|
|
level.setBlockAndUpdate(pos, Blocks.FIRE.defaultBlockState());
|
|
}
|
|
}
|
|
barrel.markUpdated();
|
|
} else if (barrel.currentTransformRecipe != null) {
|
|
var recipe = barrel.currentTransformRecipe;
|
|
var catalysts = 0;
|
|
|
|
for (var cursor : BlockPos.betweenClosed(pos.getX() - 1, pos.getY() - 1, pos.getZ() - 1, pos.getX() + 1, pos.getY() - 1, pos.getZ() + 1)) {
|
|
if (recipe.catalyst.test(level.getBlockState(cursor))) {
|
|
catalysts++;
|
|
|
|
if (!recipe.byproducts.isEmpty()) {
|
|
var rand = level.random;
|
|
|
|
if (rand.nextInt(1500) == 0) {
|
|
var above = cursor.above();
|
|
|
|
if (level.getBlockState(above).isAir()) {
|
|
var byproduct = recipe.byproducts.getRandom(rand);
|
|
if (byproduct.canSurvive(level, above)) {
|
|
level.setBlockAndUpdate(above, byproduct);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (catalysts == 0) {
|
|
barrel.currentTransformRecipe = null;
|
|
} else {
|
|
barrel.progress += catalysts * (1.0f / barrel.currentTransformRecipe.duration);
|
|
|
|
if (barrel.progress >= 1.0f - Mth.EPSILON) {
|
|
// Reset progress
|
|
barrel.progress = 0.0f;
|
|
level.playSound(null, pos, ESounds.BARREL_FLUID_TRANSFORM.get(), SoundSource.BLOCKS, 1.0f, 0.6f);
|
|
tank.setFluid(FluidStack.EMPTY);
|
|
tank.fill(new FluidStack(recipe.resultFluid, 1000), IFluidHandler.FluidAction.EXECUTE);
|
|
}
|
|
barrel.markUpdated();
|
|
}
|
|
} else if (EConfig.SERVER.barrelsCollectRainWater.get() && tank.getFluidAmount() < MAX_CAPACITY && level.isRainingAt(pos.above()) && barrel.item.getStackInSlot(0).isEmpty() && barrel.compost == 0) {
|
|
fillRainWater(barrel, tank);
|
|
|
|
// avoid checking fluid transform until full
|
|
if (tank.getFluidAmount() == MAX_CAPACITY) {
|
|
barrel.updateFluidTransform();
|
|
}
|
|
} else if (barrel.hasFullWater()) {
|
|
if (tank.getFluid().getFluid().getFluidType() == ForgeMod.WATER_TYPE.get()) {
|
|
var rand = level.random;
|
|
// Leak water to create moss (only wooden barrels do this)
|
|
if (state.ignitedByLava() && rand.nextInt(500) == 0) {
|
|
var randomPos = pos.offset(rand.nextIntBetweenInclusive(-MOSS_SPREAD_RANGE, MOSS_SPREAD_RANGE), -1, rand.nextIntBetweenInclusive(-MOSS_SPREAD_RANGE, MOSS_SPREAD_RANGE));
|
|
var randomBlock = level.getBlockState(randomPos).getBlock();
|
|
|
|
if (randomBlock == Blocks.COBBLESTONE) {
|
|
level.setBlock(randomPos, Blocks.MOSSY_COBBLESTONE.defaultBlockState(), 3);
|
|
} else if (randomBlock == Blocks.STONE_BRICKS) {
|
|
level.setBlock(randomPos, Blocks.MOSSY_STONE_BRICKS.defaultBlockState(), 3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
barrel.spawnParticlesIfBurning();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void fillRainWater(EBlockEntity block, FluidHelper tank) {
|
|
if (tank.isEmpty()) {
|
|
tank.setFluid(new FluidStack(Fluids.WATER, 1));
|
|
block.markUpdated();
|
|
} else if (tank.getFluid().getFluid() == Fluids.WATER) {
|
|
tank.getFluid().grow(1);
|
|
block.markUpdated();
|
|
}
|
|
}
|
|
|
|
private static float getProgressStep() {
|
|
return EConfig.SERVER.barrelProgressStep.get().floatValue();
|
|
}
|
|
|
|
private void doCompost() {
|
|
this.progress += getProgressStep();
|
|
markUpdated();
|
|
|
|
if (this.progress >= 1.0f) {
|
|
this.progress = 0.0f;
|
|
this.compost = 0;
|
|
setItem(new ItemStack(Items.DIRT));
|
|
this.level.playSound(null, this.worldPosition, ESounds.BARREL_COMPOST.get(), SoundSource.BLOCKS);
|
|
}
|
|
}
|
|
|
|
private static ItemStack getRemainderItem(ItemStack stack) {
|
|
return (stack.getItem() instanceof BowlFoodItem || stack.getItem() == Items.SUSPICIOUS_STEW) ? new ItemStack(Items.BOWL) : stack.getCraftingRemainingItem();
|
|
}
|
|
|
|
@Override
|
|
public void onLoad() {
|
|
tryInWorldFluidMixing();
|
|
updateFluidTransform();
|
|
}
|
|
|
|
// Inner class
|
|
private class ItemHandler extends ItemStackHandler {
|
|
@Override
|
|
protected int getStackLimit(int slot, ItemStack stack) {
|
|
return 1;
|
|
}
|
|
|
|
@Override
|
|
public boolean isItemValid(int slot, ItemStack stack) {
|
|
return tryCrafting(stack, true);
|
|
}
|
|
|
|
@Override
|
|
public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) {
|
|
if (stack.isEmpty())
|
|
return ItemStack.EMPTY;
|
|
validateSlotIndex(slot);
|
|
if (!this.stacks.get(slot).isEmpty())
|
|
return stack;
|
|
|
|
if (tryCrafting(stack, simulate)) {
|
|
if (stack.getCount() == 1) {
|
|
return getRemainderItem(stack);
|
|
} else {
|
|
popOutItem(BarrelBlockEntity.this.level, BarrelBlockEntity.this.worldPosition, getRemainderItem(stack));
|
|
return ItemHandlerHelper.copyStackWithSize(stack, stack.getCount() - 1);
|
|
}
|
|
} else {
|
|
return stack;
|
|
}
|
|
}
|
|
|
|
public ItemStack extract(boolean simulate) {
|
|
return extractItem(0, 64, simulate);
|
|
}
|
|
|
|
@Override
|
|
protected void onContentsChanged(int slot) {
|
|
if (!BarrelBlockEntity.this.level.isClientSide) {
|
|
markUpdated();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Inner class
|
|
private class FluidHandler extends FluidHelper {
|
|
public FluidHandler() {
|
|
super(MAX_CAPACITY);
|
|
}
|
|
|
|
@Override
|
|
public boolean isFluidValid(FluidStack stack) {
|
|
return !isBrewing() && BarrelBlockEntity.this.hasNoSolids();
|
|
}
|
|
|
|
@Override
|
|
protected void onContentsChanged() {
|
|
if (!BarrelBlockEntity.this.isBeingFilledByPlayer) {
|
|
BarrelBlockEntity.this.tryInWorldFluidMixing();
|
|
BarrelBlockEntity.this.markUpdated();
|
|
}
|
|
BarrelBlockEntity.this.updateFluidTransform();
|
|
}
|
|
}
|
|
}
|