package net.minecraft.world.level.block.entity; import com.google.common.collect.Lists; import com.mojang.serialization.Codec; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap.Entry; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; import net.minecraft.core.NonNullList; import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.Mth; import net.minecraft.world.ContainerHelper; import net.minecraft.world.WorldlyContainer; import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.StackedItemContents; import net.minecraft.world.inventory.ContainerData; import net.minecraft.world.inventory.RecipeCraftingHolder; import net.minecraft.world.inventory.StackedContentsCompatible; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.crafting.AbstractCookingRecipe; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.item.crafting.SingleRecipeInput; import net.minecraft.world.level.block.AbstractFurnaceBlock; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, RecipeCraftingHolder, StackedContentsCompatible { protected static final int SLOT_INPUT = 0; protected static final int SLOT_FUEL = 1; protected static final int SLOT_RESULT = 2; public static final int DATA_LIT_TIME = 0; private static final int[] SLOTS_FOR_UP = new int[]{0}; private static final int[] SLOTS_FOR_DOWN = new int[]{2, 1}; private static final int[] SLOTS_FOR_SIDES = new int[]{1}; public static final int DATA_LIT_DURATION = 1; public static final int DATA_COOKING_PROGRESS = 2; public static final int DATA_COOKING_TOTAL_TIME = 3; public static final int NUM_DATA_VALUES = 4; public static final int BURN_TIME_STANDARD = 200; public static final int BURN_COOL_SPEED = 2; private static final Codec>, Integer>> RECIPES_USED_CODEC = Codec.unboundedMap(Recipe.KEY_CODEC, Codec.INT); private static final short DEFAULT_COOKING_TIMER = 0; private static final short DEFAULT_COOKING_TOTAL_TIME = 0; private static final short DEFAULT_LIT_TIME_REMAINING = 0; private static final short DEFAULT_LIT_TOTAL_TIME = 0; protected NonNullList items = NonNullList.withSize(3, ItemStack.EMPTY); int litTimeRemaining; int litTotalTime; int cookingTimer; int cookingTotalTime; protected final ContainerData dataAccess = new ContainerData() { @Override public int get(int p_58431_) { switch (p_58431_) { case 0: return AbstractFurnaceBlockEntity.this.litTimeRemaining; case 1: return AbstractFurnaceBlockEntity.this.litTotalTime; case 2: return AbstractFurnaceBlockEntity.this.cookingTimer; case 3: return AbstractFurnaceBlockEntity.this.cookingTotalTime; default: return 0; } } @Override public void set(int p_58433_, int p_58434_) { switch (p_58433_) { case 0: AbstractFurnaceBlockEntity.this.litTimeRemaining = p_58434_; break; case 1: AbstractFurnaceBlockEntity.this.litTotalTime = p_58434_; break; case 2: AbstractFurnaceBlockEntity.this.cookingTimer = p_58434_; break; case 3: AbstractFurnaceBlockEntity.this.cookingTotalTime = p_58434_; } } @Override public int getCount() { return 4; } }; private final Reference2IntOpenHashMap>> recipesUsed = new Reference2IntOpenHashMap<>(); private final RecipeManager.CachedCheck quickCheck; protected AbstractFurnaceBlockEntity( BlockEntityType p_154991_, BlockPos p_154992_, BlockState p_154993_, RecipeType p_154994_ ) { super(p_154991_, p_154992_, p_154993_); this.quickCheck = RecipeManager.createCheck((RecipeType)p_154994_); } private boolean isLit() { return this.litTimeRemaining > 0; } @Override protected void loadAdditional(CompoundTag p_335441_, HolderLookup.Provider p_330623_) { super.loadAdditional(p_335441_, p_330623_); this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); ContainerHelper.loadAllItems(p_335441_, this.items, p_330623_); this.cookingTimer = p_335441_.getShortOr("cooking_time_spent", (short)0); this.cookingTotalTime = p_335441_.getShortOr("cooking_total_time", (short)0); this.litTimeRemaining = p_335441_.getShortOr("lit_time_remaining", (short)0); this.litTotalTime = p_335441_.getShortOr("lit_total_time", (short)0); this.recipesUsed.clear(); this.recipesUsed.putAll(p_335441_.read("RecipesUsed", RECIPES_USED_CODEC).orElse(Map.of())); } @Override protected void saveAdditional(CompoundTag p_187452_, HolderLookup.Provider p_330192_) { super.saveAdditional(p_187452_, p_330192_); p_187452_.putShort("cooking_time_spent", (short)this.cookingTimer); p_187452_.putShort("cooking_total_time", (short)this.cookingTotalTime); p_187452_.putShort("lit_time_remaining", (short)this.litTimeRemaining); p_187452_.putShort("lit_total_time", (short)this.litTotalTime); ContainerHelper.saveAllItems(p_187452_, this.items, p_330192_); p_187452_.store("RecipesUsed", RECIPES_USED_CODEC, this.recipesUsed); } public static void serverTick(ServerLevel p_364207_, BlockPos p_155015_, BlockState p_155016_, AbstractFurnaceBlockEntity p_155017_) { boolean flag = p_155017_.isLit(); boolean flag1 = false; if (p_155017_.isLit()) { p_155017_.litTimeRemaining--; } ItemStack itemstack = p_155017_.items.get(1); ItemStack itemstack1 = p_155017_.items.get(0); boolean flag2 = !itemstack1.isEmpty(); boolean flag3 = !itemstack.isEmpty(); if (p_155017_.isLit() || flag3 && flag2) { SingleRecipeInput singlerecipeinput = new SingleRecipeInput(itemstack1); RecipeHolder recipeholder; if (flag2) { recipeholder = p_155017_.quickCheck.getRecipeFor(singlerecipeinput, p_364207_).orElse(null); } else { recipeholder = null; } int i = p_155017_.getMaxStackSize(); if (!p_155017_.isLit() && canBurn(p_364207_.registryAccess(), recipeholder, singlerecipeinput, p_155017_.items, i)) { p_155017_.litTimeRemaining = p_155017_.getBurnDuration(p_364207_.fuelValues(), itemstack); p_155017_.litTotalTime = p_155017_.litTimeRemaining; if (p_155017_.isLit()) { flag1 = true; if (flag3) { Item item = itemstack.getItem(); itemstack.shrink(1); if (itemstack.isEmpty()) { p_155017_.items.set(1, item.getCraftingRemainder()); } } } } if (p_155017_.isLit() && canBurn(p_364207_.registryAccess(), recipeholder, singlerecipeinput, p_155017_.items, i)) { p_155017_.cookingTimer++; if (p_155017_.cookingTimer == p_155017_.cookingTotalTime) { p_155017_.cookingTimer = 0; p_155017_.cookingTotalTime = getTotalCookTime(p_364207_, p_155017_); if (burn(p_364207_.registryAccess(), recipeholder, singlerecipeinput, p_155017_.items, i)) { p_155017_.setRecipeUsed(recipeholder); } flag1 = true; } } else { p_155017_.cookingTimer = 0; } } else if (!p_155017_.isLit() && p_155017_.cookingTimer > 0) { p_155017_.cookingTimer = Mth.clamp(p_155017_.cookingTimer - 2, 0, p_155017_.cookingTotalTime); } if (flag != p_155017_.isLit()) { flag1 = true; p_155016_ = p_155016_.setValue(AbstractFurnaceBlock.LIT, p_155017_.isLit()); p_364207_.setBlock(p_155015_, p_155016_, 3); } if (flag1) { setChanged(p_364207_, p_155015_, p_155016_); } } private static boolean canBurn( RegistryAccess p_266924_, @Nullable RecipeHolder p_299207_, SingleRecipeInput p_364069_, NonNullList p_155007_, int p_155008_ ) { if (!p_155007_.get(0).isEmpty() && p_299207_ != null) { ItemStack itemstack = p_299207_.value().assemble(p_364069_, p_266924_); if (itemstack.isEmpty()) { return false; } else { ItemStack itemstack1 = p_155007_.get(2); if (itemstack1.isEmpty()) { return true; } else if (!ItemStack.isSameItemSameComponents(itemstack1, itemstack)) { return false; } else { return itemstack1.getCount() < p_155008_ && itemstack1.getCount() < itemstack1.getMaxStackSize() ? true : itemstack1.getCount() < itemstack.getMaxStackSize(); } } } else { return false; } } private static boolean burn( RegistryAccess p_266740_, @Nullable RecipeHolder p_299450_, SingleRecipeInput p_364092_, NonNullList p_267073_, int p_267157_ ) { if (p_299450_ != null && canBurn(p_266740_, p_299450_, p_364092_, p_267073_, p_267157_)) { ItemStack itemstack = p_267073_.get(0); ItemStack itemstack1 = p_299450_.value().assemble(p_364092_, p_266740_); ItemStack itemstack2 = p_267073_.get(2); if (itemstack2.isEmpty()) { p_267073_.set(2, itemstack1.copy()); } else if (ItemStack.isSameItemSameComponents(itemstack2, itemstack1)) { itemstack2.grow(1); } if (itemstack.is(Blocks.WET_SPONGE.asItem()) && !p_267073_.get(1).isEmpty() && p_267073_.get(1).is(Items.BUCKET)) { p_267073_.set(1, new ItemStack(Items.WATER_BUCKET)); } itemstack.shrink(1); return true; } else { return false; } } protected int getBurnDuration(FuelValues p_363312_, ItemStack p_58343_) { return p_363312_.burnDuration(p_58343_); } private static int getTotalCookTime(ServerLevel p_364532_, AbstractFurnaceBlockEntity p_222694_) { SingleRecipeInput singlerecipeinput = new SingleRecipeInput(p_222694_.getItem(0)); return p_222694_.quickCheck.getRecipeFor(singlerecipeinput, p_364532_).map(p_360485_ -> p_360485_.value().cookingTime()).orElse(200); } @Override public int[] getSlotsForFace(Direction p_58363_) { if (p_58363_ == Direction.DOWN) { return SLOTS_FOR_DOWN; } else { return p_58363_ == Direction.UP ? SLOTS_FOR_UP : SLOTS_FOR_SIDES; } } @Override public boolean canPlaceItemThroughFace(int p_58336_, ItemStack p_58337_, @Nullable Direction p_58338_) { return this.canPlaceItem(p_58336_, p_58337_); } @Override public boolean canTakeItemThroughFace(int p_58392_, ItemStack p_58393_, Direction p_58394_) { return p_58394_ == Direction.DOWN && p_58392_ == 1 ? p_58393_.is(Items.WATER_BUCKET) || p_58393_.is(Items.BUCKET) : true; } @Override public int getContainerSize() { return this.items.size(); } @Override protected NonNullList getItems() { return this.items; } @Override protected void setItems(NonNullList p_327930_) { this.items = p_327930_; } @Override public void setItem(int p_58333_, ItemStack p_58334_) { ItemStack itemstack = this.items.get(p_58333_); boolean flag = !p_58334_.isEmpty() && ItemStack.isSameItemSameComponents(itemstack, p_58334_); this.items.set(p_58333_, p_58334_); p_58334_.limitSize(this.getMaxStackSize(p_58334_)); if (p_58333_ == 0 && !flag && this.level instanceof ServerLevel serverlevel) { this.cookingTotalTime = getTotalCookTime(serverlevel, this); this.cookingTimer = 0; this.setChanged(); } } @Override public boolean canPlaceItem(int p_58389_, ItemStack p_58390_) { if (p_58389_ == 2) { return false; } else if (p_58389_ != 1) { return true; } else { ItemStack itemstack = this.items.get(1); return this.level.fuelValues().isFuel(p_58390_) || p_58390_.is(Items.BUCKET) && !itemstack.is(Items.BUCKET); } } @Override public void setRecipeUsed(@Nullable RecipeHolder p_297739_) { if (p_297739_ != null) { ResourceKey> resourcekey = p_297739_.id(); this.recipesUsed.addTo(resourcekey, 1); } } @Nullable @Override public RecipeHolder getRecipeUsed() { return null; } @Override public void awardUsedRecipes(Player p_58396_, List p_282202_) { } public void awardUsedRecipesAndPopExperience(ServerPlayer p_155004_) { List> list = this.getRecipesToAwardAndPopExperience(p_155004_.serverLevel(), p_155004_.position()); p_155004_.awardRecipes(list); for (RecipeHolder recipeholder : list) { if (recipeholder != null) { p_155004_.triggerRecipeCrafted(recipeholder, this.items); } } this.recipesUsed.clear(); } public List> getRecipesToAwardAndPopExperience(ServerLevel p_154996_, Vec3 p_154997_) { List> list = Lists.newArrayList(); for (Entry>> entry : this.recipesUsed.reference2IntEntrySet()) { p_154996_.recipeAccess().byKey(entry.getKey()).ifPresent(p_360484_ -> { list.add((RecipeHolder)p_360484_); createExperience(p_154996_, p_154997_, entry.getIntValue(), ((AbstractCookingRecipe)p_360484_.value()).experience()); }); } return list; } private static void createExperience(ServerLevel p_154999_, Vec3 p_155000_, int p_155001_, float p_155002_) { int i = Mth.floor(p_155001_ * p_155002_); float f = Mth.frac(p_155001_ * p_155002_); if (f != 0.0F && Math.random() < f) { i++; } ExperienceOrb.award(p_154999_, p_155000_, i); } @Override public void fillStackedContents(StackedItemContents p_363325_) { for (ItemStack itemstack : this.items) { p_363325_.accountStack(itemstack); } } @Override public void preRemoveSideEffects(BlockPos p_391732_, BlockState p_392347_) { super.preRemoveSideEffects(p_391732_, p_392347_); if (this.level instanceof ServerLevel serverlevel) { this.getRecipesToAwardAndPopExperience(serverlevel, Vec3.atCenterOf(p_391732_)); } } }