ExDeorum/src/main/java/thedarkcolour/exdeorum/blockentity/MechanicalHammerBlockEntity.java

264 lines
9.3 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.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.loot.LootContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.block.MechanicalHammerBlock;
import thedarkcolour.exdeorum.blockentity.helper.ItemHelper;
import thedarkcolour.exdeorum.config.EConfig;
import thedarkcolour.exdeorum.data.TranslationKeys;
import thedarkcolour.exdeorum.loot.HammerLootModifier;
import thedarkcolour.exdeorum.recipe.RecipeUtil;
import thedarkcolour.exdeorum.recipe.hammer.HammerRecipe;
import thedarkcolour.exdeorum.registry.EBlockEntities;
import thedarkcolour.exdeorum.tag.EItemTags;
public class MechanicalHammerBlockEntity extends AbstractMachineBlockEntity<MechanicalHammerBlockEntity> {
private static final Component TITLE = Component.translatable(TranslationKeys.MECHANICAL_HAMMER_SCREEN_TITLE);
private static final int INPUT_SLOT = 0;
public static final int HAMMER_SLOT = 1;
private static final int OUTPUT_SLOT = 2;
public static final int TOTAL_PROGRESS = 10_000_000;
// process should take 200 ticks or 10 seconds with no efficiency
private static final int PROGRESS_INTERVAL = TOTAL_PROGRESS / 200;
public static final int NOT_RUNNING = -1;
// an integer from 0 to 10,000,000 instead of a decimal number which is inaccurate and buggy
private int progress = NOT_RUNNING;
private float efficiency;
public MechanicalHammerBlockEntity(BlockPos pos, BlockState state) {
super(EBlockEntities.MECHANICAL_HAMMER.get(), pos, state, ItemHandler::new, EConfig.SERVER.mechanicalHammerEnergyStorage.get());
}
public static boolean isValidInput(ItemStack stack) {
return RecipeUtil.getHammerRecipe(stack.getItem()) != null;
}
@Override
protected void saveAdditional(CompoundTag nbt) {
super.saveAdditional(nbt);
nbt.putInt("progress", this.progress);
}
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
this.progress = nbt.getInt("progress");
onHammerChanged();
}
@Override
public Component getDisplayName() {
return TITLE;
}
@Override
public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player pPlayer) {
return new MechanicalHammerMenu(containerId, playerInventory, this);
}
@Override
protected boolean isRunning() {
return this.progress != NOT_RUNNING;
}
@Override
protected void tryStartRunning() {
var input = this.inventory.getStackInSlot(INPUT_SLOT);
if (!input.isEmpty()) {
if (canFitResultIntoOutput(input) != null) {
this.progress = 0;
this.level.setBlock(this.worldPosition, this.getBlockState().setValue(MechanicalHammerBlock.RUNNING, true), 3);
return;
}
}
this.level.setBlock(this.worldPosition, this.getBlockState().setValue(MechanicalHammerBlock.RUNNING, false), 3);
}
@Override
protected void noEnergyTick() {
if (getBlockState().getValue(MechanicalHammerBlock.RUNNING)) {
this.level.setBlock(this.worldPosition, this.getBlockState().setValue(MechanicalHammerBlock.RUNNING, false), 3);
}
}
@Nullable
private HammerRecipe canFitResultIntoOutput(ItemStack input) {
var output = this.inventory.getStackInSlot(OUTPUT_SLOT);
if (output.isEmpty() || output.getCount() < output.getMaxStackSize()) {
var recipe = RecipeUtil.getHammerRecipe(input.getItem());
if (recipe != null && (output.isEmpty() || matchesStack(recipe.result, output))) {
return recipe;
}
}
return null;
}
private static boolean matchesStack(Item item, ItemStack stack) {
return !stack.hasTag() && item == stack.getItem();
}
@Override
protected void runMachineTick() {
var input = this.inventory.getStackInSlot(INPUT_SLOT);
if (!input.isEmpty()) {
this.progress += PROGRESS_INTERVAL * this.efficiency;
if (this.progress >= TOTAL_PROGRESS) {
var recipe = canFitResultIntoOutput(input);
if (recipe != null) {
@SuppressWarnings("DataFlowIssue")
LootContext ctx = RecipeUtil.emptyLootContext((ServerLevel) this.level);
var resultCount = recipe.resultAmount.getInt(ctx);
resultCount += HammerLootModifier.calculateFortuneBonus(this.inventory.getStackInSlot(HAMMER_SLOT), ctx.getRandom(), resultCount == 0);
var output = this.inventory.getStackInSlot(OUTPUT_SLOT);
if (output.isEmpty()) {
this.inventory.setStackInSlot(OUTPUT_SLOT, new ItemStack(recipe.result, resultCount));
} else {
output.setCount(Math.min(output.getMaxStackSize(), resultCount + output.getCount()));
}
input.shrink(1);
damageHammer(ctx.getRandom());
setChanged();
}
this.progress = NOT_RUNNING;
}
} else {
this.level.setBlock(this.worldPosition, this.getBlockState().setValue(MechanicalHammerBlock.RUNNING, false), 3);
}
}
private void damageHammer(RandomSource rand) {
var hammer = this.inventory.getStackInSlot(HAMMER_SLOT);
if (hammer.isDamageableItem()) {
if (hammer.hurt(1, rand, null)) {
hammer.shrink(1);
if (hammer.isEmpty()) {
this.inventory.setStackInSlot(HAMMER_SLOT, ItemStack.EMPTY);
}
}
}
}
private void onHammerChanged() {
var hammer = this.inventory.getStackInSlot(HAMMER_SLOT);
if (hammer.isEmpty()) {
this.efficiency = 1f;
} else {
// This timing allows full efficiency hammer to match full efficiency sieve (55 ticks/craft
// Rewards player for using hammer by doubling speed right off the bat, before efficiency
// although not as fast as Mekanism's crusher, still pretty fast and much cheaper
this.efficiency = 2f + hammer.getEnchantmentLevel(Enchantments.BLOCK_EFFICIENCY) * 0.33f;
}
}
@Override
protected int getEnergyConsumption() {
return EConfig.SERVER.mechanicalHammerEnergyConsumption.get();
}
// The value synced to the client for rendering the arrow in GUI
public int getGuiProgress() {
return Math.round((float)(24 * this.progress) / TOTAL_PROGRESS);
}
public void setGuiProgress(int guiProgress) {
this.progress = (guiProgress * TOTAL_PROGRESS) / 24;
}
public int getProgress() {
return this.progress;
}
public void setProgress(int progress) {
this.progress = progress;
}
private static class ItemHandler extends ItemHelper {
private final MechanicalHammerBlockEntity hammer;
public ItemHandler(MechanicalHammerBlockEntity hammer) {
super(3);
this.hammer = hammer;
}
@Override
public boolean isItemValid(int slot, @NotNull ItemStack stack) {
if (slot == INPUT_SLOT) {
return RecipeUtil.getHammerRecipe(stack.getItem()) != null;
} else if (slot == HAMMER_SLOT) {
return stack.is(EItemTags.HAMMERS);
} else {
return false;
}
}
@Override
public int getSlotLimit(int slot) {
return slot == HAMMER_SLOT ? 1 : super.getSlotLimit(slot);
}
@Override
public boolean canMachineExtract(int slot) {
return slot == OUTPUT_SLOT;
}
@Override
protected void onContentsChanged(int slot) {
if (slot == HAMMER_SLOT) {
this.hammer.onHammerChanged();
} else if (slot == INPUT_SLOT) {
if (getStackInSlot(INPUT_SLOT).isEmpty()) {
this.hammer.progress = NOT_RUNNING;
}
}
}
}
}