/* * 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 . */ 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 itemHandler = LazyOptional.of(() -> this.item); private final LazyOptional 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 LazyOptional getCapability(Capability 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: *
  • When the barrel is updated by a neighboring block (see {@link BarrelBlock#neighborChanged})
  • *
  • When the barrel is first loaded from NBT (see {@link #onLoad})
  • *
  • When the fluid in the barrel changes (see {@link FluidHandler#onContentsChanged()})
  • */ 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 { @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(); } } }