393 lines
13 KiB
Java
393 lines
13 KiB
Java
package net.minecraft.world.level.block.entity;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
|
import it.unimi.dsi.fastutil.objects.Reference2IntMap.Entry;
|
|
import java.util.List;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.NonNullList;
|
|
import net.minecraft.core.RegistryAccess;
|
|
import net.minecraft.core.HolderLookup.Provider;
|
|
import net.minecraft.core.registries.Registries;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
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;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
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;
|
|
protected NonNullList<ItemStack> 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 index) {
|
|
switch (index) {
|
|
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 index, int value) {
|
|
switch (index) {
|
|
case 0:
|
|
AbstractFurnaceBlockEntity.this.litTimeRemaining = value;
|
|
break;
|
|
case 1:
|
|
AbstractFurnaceBlockEntity.this.litTotalTime = value;
|
|
break;
|
|
case 2:
|
|
AbstractFurnaceBlockEntity.this.cookingTimer = value;
|
|
break;
|
|
case 3:
|
|
AbstractFurnaceBlockEntity.this.cookingTotalTime = value;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getCount() {
|
|
return 4;
|
|
}
|
|
};
|
|
private final Reference2IntOpenHashMap<ResourceKey<Recipe<?>>> recipesUsed = new Reference2IntOpenHashMap<>();
|
|
private final RecipeManager.CachedCheck<SingleRecipeInput, ? extends AbstractCookingRecipe> quickCheck;
|
|
|
|
protected AbstractFurnaceBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState blockState, RecipeType<? extends AbstractCookingRecipe> recipeType) {
|
|
super(type, pos, blockState);
|
|
this.quickCheck = RecipeManager.createCheck(recipeType);
|
|
}
|
|
|
|
private boolean isLit() {
|
|
return this.litTimeRemaining > 0;
|
|
}
|
|
|
|
@Override
|
|
protected void loadAdditional(CompoundTag tag, Provider registries) {
|
|
super.loadAdditional(tag, registries);
|
|
this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
|
|
ContainerHelper.loadAllItems(tag, this.items, registries);
|
|
this.cookingTimer = tag.getShort("cooking_time_spent");
|
|
this.cookingTotalTime = tag.getShort("cooking_total_time");
|
|
this.litTimeRemaining = tag.getShort("lit_time_remaining");
|
|
this.litTotalTime = tag.getShort("lit_total_time");
|
|
CompoundTag compoundTag = tag.getCompound("RecipesUsed");
|
|
|
|
for (String string : compoundTag.getAllKeys()) {
|
|
this.recipesUsed.put(ResourceKey.create(Registries.RECIPE, ResourceLocation.parse(string)), compoundTag.getInt(string));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void saveAdditional(CompoundTag tag, Provider registries) {
|
|
super.saveAdditional(tag, registries);
|
|
tag.putShort("cooking_time_spent", (short)this.cookingTimer);
|
|
tag.putShort("cooking_total_time", (short)this.cookingTotalTime);
|
|
tag.putShort("lit_time_remaining", (short)this.litTimeRemaining);
|
|
tag.putShort("lit_total_time", (short)this.litTotalTime);
|
|
ContainerHelper.saveAllItems(tag, this.items, registries);
|
|
CompoundTag compoundTag = new CompoundTag();
|
|
this.recipesUsed.forEach((resourceKey, integer) -> compoundTag.putInt(resourceKey.location().toString(), integer));
|
|
tag.put("RecipesUsed", compoundTag);
|
|
}
|
|
|
|
public static void serverTick(ServerLevel level, BlockPos pos, BlockState state, AbstractFurnaceBlockEntity furnace) {
|
|
boolean bl = furnace.isLit();
|
|
boolean bl2 = false;
|
|
if (furnace.isLit()) {
|
|
furnace.litTimeRemaining--;
|
|
}
|
|
|
|
ItemStack itemStack = furnace.items.get(1);
|
|
ItemStack itemStack2 = furnace.items.get(0);
|
|
boolean bl3 = !itemStack2.isEmpty();
|
|
boolean bl4 = !itemStack.isEmpty();
|
|
if (furnace.isLit() || bl4 && bl3) {
|
|
SingleRecipeInput singleRecipeInput = new SingleRecipeInput(itemStack2);
|
|
RecipeHolder<? extends AbstractCookingRecipe> recipeHolder;
|
|
if (bl3) {
|
|
recipeHolder = (RecipeHolder<? extends AbstractCookingRecipe>)furnace.quickCheck.getRecipeFor(singleRecipeInput, level).orElse(null);
|
|
} else {
|
|
recipeHolder = null;
|
|
}
|
|
|
|
int i = furnace.getMaxStackSize();
|
|
if (!furnace.isLit() && canBurn(level.registryAccess(), recipeHolder, singleRecipeInput, furnace.items, i)) {
|
|
furnace.litTimeRemaining = furnace.getBurnDuration(level.fuelValues(), itemStack);
|
|
furnace.litTotalTime = furnace.litTimeRemaining;
|
|
if (furnace.isLit()) {
|
|
bl2 = true;
|
|
if (bl4) {
|
|
Item item = itemStack.getItem();
|
|
itemStack.shrink(1);
|
|
if (itemStack.isEmpty()) {
|
|
furnace.items.set(1, item.getCraftingRemainder());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (furnace.isLit() && canBurn(level.registryAccess(), recipeHolder, singleRecipeInput, furnace.items, i)) {
|
|
furnace.cookingTimer++;
|
|
if (furnace.cookingTimer == furnace.cookingTotalTime) {
|
|
furnace.cookingTimer = 0;
|
|
furnace.cookingTotalTime = getTotalCookTime(level, furnace);
|
|
if (burn(level.registryAccess(), recipeHolder, singleRecipeInput, furnace.items, i)) {
|
|
furnace.setRecipeUsed(recipeHolder);
|
|
}
|
|
|
|
bl2 = true;
|
|
}
|
|
} else {
|
|
furnace.cookingTimer = 0;
|
|
}
|
|
} else if (!furnace.isLit() && furnace.cookingTimer > 0) {
|
|
furnace.cookingTimer = Mth.clamp(furnace.cookingTimer - 2, 0, furnace.cookingTotalTime);
|
|
}
|
|
|
|
if (bl != furnace.isLit()) {
|
|
bl2 = true;
|
|
state = state.setValue(AbstractFurnaceBlock.LIT, furnace.isLit());
|
|
level.setBlock(pos, state, 3);
|
|
}
|
|
|
|
if (bl2) {
|
|
setChanged(level, pos, state);
|
|
}
|
|
}
|
|
|
|
private static boolean canBurn(
|
|
RegistryAccess registryAccess,
|
|
@Nullable RecipeHolder<? extends AbstractCookingRecipe> recipe,
|
|
SingleRecipeInput recipeInput,
|
|
NonNullList<ItemStack> items,
|
|
int maxStackSize
|
|
) {
|
|
if (!items.get(0).isEmpty() && recipe != null) {
|
|
ItemStack itemStack = recipe.value().assemble(recipeInput, registryAccess);
|
|
if (itemStack.isEmpty()) {
|
|
return false;
|
|
} else {
|
|
ItemStack itemStack2 = items.get(2);
|
|
if (itemStack2.isEmpty()) {
|
|
return true;
|
|
} else if (!ItemStack.isSameItemSameComponents(itemStack2, itemStack)) {
|
|
return false;
|
|
} else {
|
|
return itemStack2.getCount() < maxStackSize && itemStack2.getCount() < itemStack2.getMaxStackSize()
|
|
? true
|
|
: itemStack2.getCount() < itemStack.getMaxStackSize();
|
|
}
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static boolean burn(
|
|
RegistryAccess registryAccess,
|
|
@Nullable RecipeHolder<? extends AbstractCookingRecipe> recipe,
|
|
SingleRecipeInput recipeInput,
|
|
NonNullList<ItemStack> items,
|
|
int maxStackSize
|
|
) {
|
|
if (recipe != null && canBurn(registryAccess, recipe, recipeInput, items, maxStackSize)) {
|
|
ItemStack itemStack = items.get(0);
|
|
ItemStack itemStack2 = recipe.value().assemble(recipeInput, registryAccess);
|
|
ItemStack itemStack3 = items.get(2);
|
|
if (itemStack3.isEmpty()) {
|
|
items.set(2, itemStack2.copy());
|
|
} else if (ItemStack.isSameItemSameComponents(itemStack3, itemStack2)) {
|
|
itemStack3.grow(1);
|
|
}
|
|
|
|
if (itemStack.is(Blocks.WET_SPONGE.asItem()) && !items.get(1).isEmpty() && items.get(1).is(Items.BUCKET)) {
|
|
items.set(1, new ItemStack(Items.WATER_BUCKET));
|
|
}
|
|
|
|
itemStack.shrink(1);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected int getBurnDuration(FuelValues fuelValues, ItemStack stack) {
|
|
return fuelValues.burnDuration(stack);
|
|
}
|
|
|
|
private static int getTotalCookTime(ServerLevel level, AbstractFurnaceBlockEntity furnace) {
|
|
SingleRecipeInput singleRecipeInput = new SingleRecipeInput(furnace.getItem(0));
|
|
return (Integer)furnace.quickCheck
|
|
.getRecipeFor(singleRecipeInput, level)
|
|
.map(recipeHolder -> ((AbstractCookingRecipe)recipeHolder.value()).cookingTime())
|
|
.orElse(200);
|
|
}
|
|
|
|
@Override
|
|
public int[] getSlotsForFace(Direction side) {
|
|
if (side == Direction.DOWN) {
|
|
return SLOTS_FOR_DOWN;
|
|
} else {
|
|
return side == Direction.UP ? SLOTS_FOR_UP : SLOTS_FOR_SIDES;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean canPlaceItemThroughFace(int index, ItemStack itemStack, @Nullable Direction direction) {
|
|
return this.canPlaceItem(index, itemStack);
|
|
}
|
|
|
|
@Override
|
|
public boolean canTakeItemThroughFace(int index, ItemStack stack, Direction direction) {
|
|
return direction == Direction.DOWN && index == 1 ? stack.is(Items.WATER_BUCKET) || stack.is(Items.BUCKET) : true;
|
|
}
|
|
|
|
@Override
|
|
public int getContainerSize() {
|
|
return this.items.size();
|
|
}
|
|
|
|
@Override
|
|
protected NonNullList<ItemStack> getItems() {
|
|
return this.items;
|
|
}
|
|
|
|
@Override
|
|
protected void setItems(NonNullList<ItemStack> items) {
|
|
this.items = items;
|
|
}
|
|
|
|
@Override
|
|
public void setItem(int slot, ItemStack stack) {
|
|
ItemStack itemStack = this.items.get(slot);
|
|
boolean bl = !stack.isEmpty() && ItemStack.isSameItemSameComponents(itemStack, stack);
|
|
this.items.set(slot, stack);
|
|
stack.limitSize(this.getMaxStackSize(stack));
|
|
if (slot == 0 && !bl && this.level instanceof ServerLevel serverLevel) {
|
|
this.cookingTotalTime = getTotalCookTime(serverLevel, this);
|
|
this.cookingTimer = 0;
|
|
this.setChanged();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean canPlaceItem(int slot, ItemStack stack) {
|
|
if (slot == 2) {
|
|
return false;
|
|
} else if (slot != 1) {
|
|
return true;
|
|
} else {
|
|
ItemStack itemStack = this.items.get(1);
|
|
return this.level.fuelValues().isFuel(stack) || stack.is(Items.BUCKET) && !itemStack.is(Items.BUCKET);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setRecipeUsed(@Nullable RecipeHolder<?> recipe) {
|
|
if (recipe != null) {
|
|
ResourceKey<Recipe<?>> resourceKey = recipe.id();
|
|
this.recipesUsed.addTo(resourceKey, 1);
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public RecipeHolder<?> getRecipeUsed() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void awardUsedRecipes(Player player, List<ItemStack> items) {
|
|
}
|
|
|
|
public void awardUsedRecipesAndPopExperience(ServerPlayer player) {
|
|
List<RecipeHolder<?>> list = this.getRecipesToAwardAndPopExperience(player.serverLevel(), player.position());
|
|
player.awardRecipes(list);
|
|
|
|
for (RecipeHolder<?> recipeHolder : list) {
|
|
if (recipeHolder != null) {
|
|
player.triggerRecipeCrafted(recipeHolder, this.items);
|
|
}
|
|
}
|
|
|
|
this.recipesUsed.clear();
|
|
}
|
|
|
|
public List<RecipeHolder<?>> getRecipesToAwardAndPopExperience(ServerLevel level, Vec3 popVec) {
|
|
List<RecipeHolder<?>> list = Lists.<RecipeHolder<?>>newArrayList();
|
|
|
|
for (Entry<ResourceKey<Recipe<?>>> entry : this.recipesUsed.reference2IntEntrySet()) {
|
|
level.recipeAccess().byKey((ResourceKey<Recipe<?>>)entry.getKey()).ifPresent(recipeHolder -> {
|
|
list.add(recipeHolder);
|
|
createExperience(level, popVec, entry.getIntValue(), ((AbstractCookingRecipe)recipeHolder.value()).experience());
|
|
});
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
private static void createExperience(ServerLevel level, Vec3 popVec, int recipeIndex, float experience) {
|
|
int i = Mth.floor(recipeIndex * experience);
|
|
float f = Mth.frac(recipeIndex * experience);
|
|
if (f != 0.0F && Math.random() < f) {
|
|
i++;
|
|
}
|
|
|
|
ExperienceOrb.award(level, popVec, i);
|
|
}
|
|
|
|
@Override
|
|
public void fillStackedContents(StackedItemContents stackedItemContents) {
|
|
for (ItemStack itemStack : this.items) {
|
|
stackedItemContents.accountStack(itemStack);
|
|
}
|
|
}
|
|
}
|