package ru.betterend.blocks.entities; import java.util.Iterator; import java.util.List; import java.util.Map; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.block.entity.LockableContainerBlockEntity; import net.minecraft.entity.ExperienceOrbEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.inventory.Inventories; import net.minecraft.inventory.SidedInventory; import net.minecraft.item.Item; import net.minecraft.item.ItemConvertible; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.nbt.CompoundTag; import net.minecraft.recipe.BlastingRecipe; import net.minecraft.recipe.Recipe; import net.minecraft.recipe.RecipeFinder; import net.minecraft.recipe.RecipeInputProvider; import net.minecraft.recipe.RecipeType; import net.minecraft.recipe.RecipeUnlocker; import net.minecraft.screen.PropertyDelegate; import net.minecraft.screen.ScreenHandler; import net.minecraft.tag.Tag; import net.minecraft.text.Text; import net.minecraft.text.TranslatableText; import net.minecraft.util.Identifier; import net.minecraft.util.Tickable; import net.minecraft.util.collection.DefaultedList; import net.minecraft.util.math.Direction; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import ru.betterend.BetterEnd; import ru.betterend.blocks.EndStoneSmelter; import ru.betterend.client.gui.EndStoneSmelterScreenHandler; import ru.betterend.recipe.AlloyingRecipe; import ru.betterend.registry.BlockEntityRegistry; public class EndStoneSmelterBlockEntity extends LockableContainerBlockEntity implements SidedInventory, RecipeUnlocker, RecipeInputProvider, Tickable { private static final int[] TOP_SLOTS = new int[] { 0, 1 }; private static final int[] BOTTOM_SLOTS = new int[] { 2, 3 }; private static final int[] SIDE_SLOTS = new int[] { 3 }; private static final Map availableFuels = Maps.newHashMap(); private final Object2IntOpenHashMap recipesUsed; protected DefaultedList inventory; protected final PropertyDelegate propertyDelegate; private Recipe lastRecipe; private int smeltTimeTotal; private int smeltTime; private int burnTime; private int fuelTime; public EndStoneSmelterBlockEntity() { super(BlockEntityRegistry.END_STONE_SMELTER); this.inventory = DefaultedList.ofSize(4, ItemStack.EMPTY); this.recipesUsed = new Object2IntOpenHashMap(); this.propertyDelegate = new PropertyDelegate() { public int get(int index) { switch(index) { case 0: return EndStoneSmelterBlockEntity.this.burnTime; case 1: return EndStoneSmelterBlockEntity.this.fuelTime; case 2: return EndStoneSmelterBlockEntity.this.smeltTime; case 3: return EndStoneSmelterBlockEntity.this.smeltTimeTotal; default: return 0; } } public void set(int index, int value) { switch(index) { case 0: EndStoneSmelterBlockEntity.this.burnTime = value; break; case 1: EndStoneSmelterBlockEntity.this.fuelTime = value; break; case 2: EndStoneSmelterBlockEntity.this.smeltTime = value; break; case 3: EndStoneSmelterBlockEntity.this.smeltTimeTotal = value; } } public int size() { return 4; } }; this.registerFuels(); } private void registerFuels() { registerFuel(Items.LAVA_BUCKET, 16000); registerFuel(Blocks.COAL_BLOCK, 12000); registerFuel(Items.BLAZE_ROD, 2000); } private boolean isBurning() { return this.burnTime > 0; } @Override public int size() { return this.inventory.size(); } @Override public boolean isEmpty() { Iterator iterator = this.inventory.iterator(); ItemStack itemStack; do { if (!iterator.hasNext()) { return true; } itemStack = iterator.next(); } while (itemStack.isEmpty()); return false; } @Override public ItemStack getStack(int slot) { return this.inventory.get(slot); } @Override public ItemStack removeStack(int slot, int amount) { return Inventories.splitStack(this.inventory, slot, amount); } @Override public ItemStack removeStack(int slot) { return Inventories.removeStack(this.inventory, slot); } @Override public void setStack(int slot, ItemStack stack) { ItemStack itemStack = this.inventory.get(slot); boolean stackValid = !stack.isEmpty() && stack.isItemEqualIgnoreDamage(itemStack) && ItemStack.areTagsEqual(stack, itemStack); this.inventory.set(slot, stack); if (stack.getCount() > getMaxCountPerStack()) { stack.setCount(getMaxCountPerStack()); } if ((slot == 0 || slot == 1) && !stackValid) { this.smeltTimeTotal = this.getSmeltTime(); this.smeltTime = 0; this.markDirty(); } } protected int getSmeltTime() { int smeltTime = this.world.getRecipeManager().getFirstMatch(AlloyingRecipe.TYPE, this, world) .map(AlloyingRecipe::getSmeltTime).orElse(0); if (smeltTime == 0) { smeltTime = this.world.getRecipeManager().getFirstMatch(RecipeType.BLASTING, this, world) .map(BlastingRecipe::getCookTime).orElse(200); smeltTime /= 1.5; } return smeltTime; } public void dropExperience(PlayerEntity player) { List> list = Lists.newArrayList(); ObjectIterator> usedRecipes = this.recipesUsed.object2IntEntrySet().iterator(); while(usedRecipes.hasNext()) { Entry entry = usedRecipes.next(); world.getRecipeManager().get(entry.getKey()).ifPresent((recipe) -> { list.add(recipe); if (recipe instanceof AlloyingRecipe) { AlloyingRecipe alloying = (AlloyingRecipe) recipe; this.dropExperience(player.world, player.getPos(), entry.getIntValue(), alloying.getExperience()); } else { BlastingRecipe blasting = (BlastingRecipe) recipe; this.dropExperience(player.world, player.getPos(), entry.getIntValue(), blasting.getExperience()); } }); } player.unlockRecipes(list); this.recipesUsed.clear(); } private void dropExperience(World world, Vec3d vec3d, int i, float f) { int j = MathHelper.floor(i * f); float g = MathHelper.fractionalPart(i * f); if (g != 0.0F && Math.random() < g) { j++; } while(j > 0) { int k = ExperienceOrbEntity.roundToOrbSize(j); j -= k; world.spawnEntity(new ExperienceOrbEntity(world, vec3d.x, vec3d.y, vec3d.z, k)); } } @Override public boolean canPlayerUse(PlayerEntity player) { if (this.world.getBlockEntity(this.pos) != this) { return false; } else { return player.squaredDistanceTo(this.pos.getX() + 0.5D, this.pos.getY() + 0.5D, this.pos.getZ() + 0.5D) <= 64.0D; } } @Override public void clear() { this.inventory.clear(); } @Override protected Text getContainerName() { return new TranslatableText(String.format("block.%s.%s", BetterEnd.MOD_ID, EndStoneSmelter.ID)); } @Override protected ScreenHandler createScreenHandler(int syncId, PlayerInventory playerInventory) { return new EndStoneSmelterScreenHandler(syncId, playerInventory, this, propertyDelegate); } @Override public void tick() { boolean initialBurning = this.isBurning(); boolean smelting = false; if (initialBurning) { this.burnTime--; } boolean burning = this.isBurning(); if (!this.world.isClient) { ItemStack fuel = this.inventory.get(2); if (!burning && (fuel.isEmpty() || inventory.get(0).isEmpty() && inventory.get(1).isEmpty())) { if (!burning && smeltTime > 0) { this.smeltTime = MathHelper.clamp(smeltTime - 2, 0, smeltTimeTotal); } } else { Recipe recipe = this.world.getRecipeManager().getFirstMatch(AlloyingRecipe.TYPE, this, world).orElse(null); if (recipe == null) { recipe = this.world.getRecipeManager().getFirstMatch(RecipeType.BLASTING, this, world).orElse(null); } if (!burning && canAcceptRecipeOutput(recipe)) { this.burnTime = this.getFuelTime(fuel); this.fuelTime = this.burnTime; burning = this.isBurning(); if (burning) { smelting = true; if (!fuel.isEmpty()) { Item item = fuel.getItem(); fuel.decrement(1); if (fuel.isEmpty()) { Item remainFuel = item.getRecipeRemainder(); this.inventory.set(2, remainFuel == null ? ItemStack.EMPTY : new ItemStack(remainFuel)); } } } } if (burning && canAcceptRecipeOutput(recipe)) { this.smeltTime++; if (smeltTime == smeltTimeTotal) { this.smeltTime = 0; this.smeltTimeTotal = this.getSmeltTime(); this.craftRecipe(recipe); smelting = true; } } else { this.smeltTime = 0; } } if (initialBurning != burning) { smelting = true; this.world.setBlockState(pos, world.getBlockState(pos).with(EndStoneSmelter.LIT, burning), 3); } } if (smelting) { this.markDirty(); } } protected boolean canAcceptRecipeOutput(Recipe recipe) { if (recipe == null) return false; boolean validInput = false; if (recipe instanceof AlloyingRecipe) { validInput = !inventory.get(0).isEmpty() && !inventory.get(1).isEmpty(); } else { validInput = !inventory.get(0).isEmpty() || !inventory.get(1).isEmpty(); } if (validInput) { ItemStack result = recipe.getOutput(); if (result.isEmpty()) { return false; } else { ItemStack output = this.inventory.get(3); int outCount = output.getCount(); int total = outCount + result.getCount(); if (output.isEmpty()) { return true; } else if (!output.isItemEqualIgnoreDamage(result)) { return false; } else if (outCount < this.getMaxCountPerStack() && outCount < output.getMaxCount()) { return this.getMaxCountPerStack() >= total; } else { return output.getCount() < result.getMaxCount(); } } } return false; } private void craftRecipe(Recipe recipe) { if (recipe == null || !canAcceptRecipeOutput(recipe)) return; ItemStack result = recipe.getOutput(); ItemStack output = this.inventory.get(3); if (output.isEmpty()) { this.inventory.set(3, result.copy()); } else if (output.getItem() == result.getItem()) { output.increment(result.getCount()); } if (!this.world.isClient) { this.setLastRecipe(recipe); } if (recipe instanceof AlloyingRecipe) { this.inventory.get(0).decrement(1); this.inventory.get(1).decrement(1); } else { if (!this.inventory.get(0).isEmpty()) { this.inventory.get(0).decrement(1); } else { this.inventory.get(1).decrement(1); } } } @Override public void provideRecipeInputs(RecipeFinder finder) { Iterator inventory = this.inventory.iterator(); while(inventory.hasNext()) { ItemStack itemStack = inventory.next(); finder.addItem(itemStack); } } @Override public void setLastRecipe(Recipe recipe) { if (recipe != null) { Identifier recipeId = recipe.getId(); this.recipesUsed.addTo(recipeId, 1); this.lastRecipe = recipe; } } @Override public Recipe getLastRecipe() { return this.lastRecipe; } @Override public int[] getAvailableSlots(Direction side) { if (side == Direction.DOWN) { return BOTTOM_SLOTS; } else { return side == Direction.UP ? TOP_SLOTS : SIDE_SLOTS; } } @Override public boolean canInsert(int slot, ItemStack stack, Direction dir) { return this.isValid(slot, stack); } @Override public boolean canExtract(int slot, ItemStack stack, Direction dir) { if (dir == Direction.DOWN && slot == 2) { if (stack.getItem() != Items.BUCKET) { return false; } } return true; } protected int getFuelTime(ItemStack fuel) { if (fuel.isEmpty()) { return 0; } else { Item item = fuel.getItem(); return availableFuels.getOrDefault(item, 0); } } @Override public void fromTag(BlockState state, CompoundTag tag) { super.fromTag(state, tag); this.inventory = DefaultedList.ofSize(this.size(), ItemStack.EMPTY); Inventories.fromTag(tag, this.inventory); this.burnTime = tag.getShort("BurnTime"); this.smeltTime = tag.getShort("SmeltTime"); this.smeltTimeTotal = tag.getShort("SmeltTimeTotal"); this.fuelTime = this.getFuelTime(this.inventory.get(2)); CompoundTag compoundTag = tag.getCompound("RecipesUsed"); Iterator recipes = compoundTag.getKeys().iterator(); while(recipes.hasNext()) { String id = recipes.next(); this.recipesUsed.put(new Identifier(id), compoundTag.getInt(id)); } } @Override public CompoundTag toTag(CompoundTag tag) { super.toTag(tag); tag.putShort("BurnTime", (short)this.burnTime); tag.putShort("SmeltTime", (short)this.smeltTime); tag.putShort("SmeltTimeTotal", (short)this.smeltTimeTotal); Inventories.toTag(tag, this.inventory); CompoundTag usedRecipes = new CompoundTag(); this.recipesUsed.forEach((identifier, integer) -> { usedRecipes.putInt(identifier.toString(), integer); }); tag.put("RecipesUsed", usedRecipes); return tag; } public boolean isValid(int slot, ItemStack stack) { if (slot == 3) { return false; } else if (slot != 0 || slot != 1) { return true; } else { ItemStack itemStack = this.inventory.get(2); return canUseAsFuel(stack) || stack.getItem() == Items.BUCKET && itemStack.getItem() != Items.BUCKET; } } public static boolean canUseAsFuel(ItemStack stack) { return availableFuels.containsKey(stack.getItem()); } public static void registerFuel(ItemConvertible fuel, int time) { if (availableFuels.containsKey(fuel)) { availableFuels.replace(fuel.asItem(), time); } else { availableFuels.put(fuel.asItem(), time); } } public static void registerFuel(Tag tag, int time) { Iterator tagItems = tag.values().iterator(); tagItems.forEachRemaining(item -> registerFuel(item, time)); } public static Map availableFuels() { return availableFuels; } }