682 lines
29 KiB
Java
682 lines
29 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.HolderLookup;
|
|
import net.minecraft.core.component.DataComponents;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
|
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.ItemStack;
|
|
import net.minecraft.world.item.Items;
|
|
import net.minecraft.world.item.alchemy.PotionContents;
|
|
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.BlockEntity;
|
|
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.Fluid;
|
|
import net.minecraft.world.level.material.Fluids;
|
|
import net.neoforged.neoforge.capabilities.Capabilities;
|
|
import net.neoforged.neoforge.common.NeoForgeMod;
|
|
import net.neoforged.neoforge.fluids.FluidStack;
|
|
import net.neoforged.neoforge.fluids.FluidType;
|
|
import net.neoforged.neoforge.fluids.FluidUtil;
|
|
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
|
|
import net.neoforged.neoforge.fluids.capability.templates.FluidTank;
|
|
import net.neoforged.neoforge.items.IItemHandler;
|
|
import net.neoforged.neoforge.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;
|
|
|
|
import java.util.Optional;
|
|
|
|
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;
|
|
|
|
public BarrelBlockEntity(BlockPos pos, BlockState state) {
|
|
super(EBlockEntities.BARREL.get(), pos, state);
|
|
|
|
this.transparent = BarrelMaterial.TRANSPARENT_BARRELS.contains(state.getBlock());
|
|
}
|
|
|
|
@Override
|
|
public void saveAdditional(CompoundTag nbt, HolderLookup.Provider lookup) {
|
|
super.saveAdditional(nbt, lookup);
|
|
|
|
nbt.put("item", this.item.serializeNBT(lookup));
|
|
nbt.put("tank", this.tank.writeToNBT(lookup, 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 loadAdditional(CompoundTag nbt, HolderLookup.Provider lookup) {
|
|
super.loadAdditional(nbt, lookup);
|
|
|
|
this.item.deserializeNBT(lookup, nbt.getCompound("item"));
|
|
this.tank.readFromNBT(lookup, 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");
|
|
|
|
AbstractCrucibleBlockEntity.updateLight(this.level, this.worldPosition, this.tank.getFluid().getFluid());
|
|
}
|
|
|
|
@Override
|
|
public void writeVisualData(RegistryFriendlyByteBuf buffer) {
|
|
ItemStack.OPTIONAL_STREAM_CODEC.encode(buffer, this.item.getStackInSlot(0));
|
|
FluidStack.OPTIONAL_STREAM_CODEC.encode(buffer, 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(RegistryFriendlyByteBuf buffer) {
|
|
this.item.setStackInSlot(0, ItemStack.OPTIONAL_STREAM_CODEC.decode(buffer));
|
|
this.tank.setFluid(FluidStack.OPTIONAL_STREAM_CODEC.decode(buffer));
|
|
this.compost = buffer.readShort();
|
|
this.progress = buffer.readFloat();
|
|
this.r = buffer.readShort();
|
|
this.g = buffer.readShort();
|
|
this.b = buffer.readShort();
|
|
}
|
|
|
|
@Override
|
|
public void copyVisualData(BlockEntity fromIntegratedServer) {
|
|
if (fromIntegratedServer instanceof BarrelBlockEntity from) {
|
|
this.item.setStackInSlot(0, from.item.getStackInSlot(0).copy());
|
|
this.tank.setFluid(from.tank.getFluid().copy());
|
|
this.compost = from.compost;
|
|
this.progress = from.progress;
|
|
this.r = from.r;
|
|
this.g = from.g;
|
|
this.b = from.b;
|
|
}
|
|
}
|
|
|
|
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 1000. 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();
|
|
}
|
|
|
|
@SuppressWarnings("deprecation")
|
|
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(Level level) {
|
|
if (isBurning()) {
|
|
BlockPos pos = getBlockPos();
|
|
int burnTicks = (int) (this.progress * 300);
|
|
|
|
if (burnTicks % 30 == 0) {
|
|
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) {
|
|
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 FluidTank getTank() {
|
|
return this.tank;
|
|
}
|
|
|
|
@Override
|
|
public InteractionResult useItemOn(Level level, Player player, ItemStack stack, InteractionHand hand) {
|
|
// Collect an item
|
|
if (!getItem().isEmpty()) {
|
|
return giveResultItem(level);
|
|
}
|
|
|
|
// Handle item fluid interaction first
|
|
if (hasNoSolids()) {
|
|
var wasBurning = isBurning();
|
|
|
|
this.isBeingFilledByPlayer = true;
|
|
|
|
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.SUCCESS;
|
|
} else {
|
|
this.isBeingFilledByPlayer = false;
|
|
// try one more time to transfer fluids between item and barrel
|
|
var playerItem = player.getItemInHand(hand);
|
|
if (EConfig.SERVER.allowWaterBottleTransfer.get()) {
|
|
var fluid = new FluidStack(Fluids.WATER, 250);
|
|
|
|
if (playerItem.getItem() == Items.POTION && playerItem.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY).is(Potions.WATER)) {
|
|
if (this.tank.fill(fluid, IFluidHandler.FluidAction.SIMULATE) > 0) {
|
|
if (!player.getAbilities().instabuild) {
|
|
player.setItemInHand(hand, new ItemStack(Items.GLASS_BOTTLE));
|
|
}
|
|
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.SUCCESS;
|
|
}
|
|
} else if (playerItem.getItem() == Items.GLASS_BOTTLE) {
|
|
if (this.tank.drain(fluid, IFluidHandler.FluidAction.SIMULATE).getAmount() == 250) {
|
|
extractWaterBottle(this.tank, level, player, playerItem, fluid);
|
|
|
|
markUpdated();
|
|
return InteractionResult.SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, mix the item's fluid into the barrel's fluid
|
|
var itemFluidCap = playerItem.getCapability(Capabilities.Fluid.ITEM);
|
|
if (itemFluidCap != null) {
|
|
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.baseFluid().amount() && itemFluid.getAmount() == 1000) {
|
|
if (!level.isClientSide()) {
|
|
this.tank.drain(recipe.baseFluid().amount(), IFluidHandler.FluidAction.EXECUTE);
|
|
setItem(recipe.result().copy());
|
|
|
|
if (recipe.consumesAdditive()) {
|
|
itemFluidCap.drain(1000, IFluidHandler.FluidAction.EXECUTE);
|
|
player.setItemInHand(hand, itemFluidCap.getContainer());
|
|
}
|
|
}
|
|
// If a mix was successful, skip rest of logic
|
|
return InteractionResult.SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.SUCCESS;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
var bottle = new ItemStack(Items.POTION);
|
|
bottle.set(DataComponents.POTION_CONTENTS, new PotionContents(Potions.WATER));
|
|
if (!player.addItem(bottle)) {
|
|
player.drop(bottle, false);
|
|
}
|
|
tank.drain(fluid, IFluidHandler.FluidAction.EXECUTE);
|
|
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.SUCCESS;
|
|
}
|
|
|
|
private static void popOutItem(Level level, BlockPos pos, ItemStack stack) {
|
|
if (!level.isClientSide() && !stack.isEmpty()) {
|
|
var rand = level.getRandom();
|
|
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(playerItem, this.tank.getFluid());
|
|
|
|
if (recipe != null) {
|
|
if (!simulate) {
|
|
// Empty barrel
|
|
this.tank.drain(recipe.fluid.amount(), IFluidHandler.FluidAction.EXECUTE);
|
|
// Replace fluid with result
|
|
setItem(recipe.result.copy());
|
|
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.baseFluid().amount(), IFluidHandler.FluidAction.EXECUTE);
|
|
setItem(recipe.result().copy());
|
|
} else if (aboveBlockState.getBlock() instanceof BucketPickup pickup) {
|
|
// If something was picked up, we can craft
|
|
if (!pickup.pickupBlock(null, this.level, abovePos, aboveBlockState).isEmpty()) {
|
|
this.tank.drain(recipe.baseFluid().amount(), IFluidHandler.FluidAction.EXECUTE);
|
|
setItem(recipe.result().copy());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 IItemHandler getItemHandler() {
|
|
return this.item;
|
|
}
|
|
|
|
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.getRandom();
|
|
|
|
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() == NeoForgeMod.WATER_TYPE.value()) {
|
|
var rand = level.getRandom();
|
|
// 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(level);
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
var food = stack.get(DataComponents.FOOD);
|
|
Optional<ItemStack> foodRemainder = food == null ? Optional.empty() : food.usingConvertsTo();
|
|
return foodRemainder.map(ItemStack::copy).orElseGet(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 stack.copyWithCount(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 {
|
|
@Nullable
|
|
private Fluid lastFluid;
|
|
|
|
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();
|
|
|
|
if (this.lastFluid != this.fluid.getFluid()) {
|
|
this.lastFluid = this.fluid.getFluid();
|
|
AbstractCrucibleBlockEntity.updateLight(BarrelBlockEntity.this.level, BarrelBlockEntity.this.worldPosition, this.lastFluid);
|
|
}
|
|
}
|
|
}
|
|
}
|