Fully implemented the mechanical hammer in code

This commit is contained in:
thedarkcolour 2024-01-13 21:40:07 -08:00
parent aa66440e8c
commit a56025fcc6
32 changed files with 974 additions and 288 deletions

View File

@ -0,0 +1,61 @@
/*
* 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.block;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.blockentity.MechanicalHammerBlockEntity;
import thedarkcolour.exdeorum.registry.EBlockEntities;
public class MechanicalHammerBlock extends EBlock {
public MechanicalHammerBlock(Properties properties) {
super(properties, EBlockEntities.MECHANICAL_HAMMER);
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState pState, BlockEntityType<T> type) {
return type == EBlockEntities.MECHANICAL_HAMMER.get() && !level.isClientSide ? (BlockEntityTicker<T>) new MechanicalHammerBlockEntity.ServerTicker<>() : null;
}
// todo creative drop and tooltip
@Override
public void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
if (!oldState.is(state.getBlock())) {
if (level.getBlockEntity(pos) instanceof MechanicalHammerBlockEntity hammer) {
hammer.checkPoweredState(level, pos);
}
}
}
@Override
public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) {
if (level.getBlockEntity(pos) instanceof MechanicalHammerBlockEntity hammer) {
hammer.checkPoweredState(level, pos);
}
}
}

View File

@ -69,7 +69,7 @@ public class MechanicalSieveBlock extends EBlock {
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState pState, BlockEntityType<T> type) {
return type == EBlockEntities.MECHANICAL_SIEVE.get() && !level.isClientSide ? (BlockEntityTicker<T>) new MechanicalSieveBlockEntity.ServerTicker() : null;
return type == EBlockEntities.MECHANICAL_SIEVE.get() && !level.isClientSide ? (BlockEntityTicker<T>) new MechanicalSieveBlockEntity.ServerTicker<>() : null;
}
@Override
@ -93,7 +93,7 @@ public class MechanicalSieveBlock extends EBlock {
public void playerWillDestroy(Level level, BlockPos pos, BlockState pState, Player player) {
if (!level.isClientSide && player.isCreative() && level.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
if (level.getBlockEntity(pos) instanceof MechanicalSieveBlockEntity sieve) {
if (!sieve.getMesh().isEmpty()) {
if (!sieve.getLogic().getMesh().isEmpty()) {
var stack = new ItemStack(this);
BlockItem.setBlockEntityData(stack, EBlockEntities.MECHANICAL_SIEVE.get(), sieve.saveWithoutMetadata());
var itemEntity = new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, stack);

View File

@ -57,7 +57,7 @@ public class SieveBlock extends EBlock {
if (!level.isClientSide) {
if (!state.is(newState.getBlock())) {
if (level.getBlockEntity(pos) instanceof SieveBlockEntity sieve) {
var mesh = sieve.getMesh();
var mesh = sieve.getLogic().getMesh();
if (!mesh.isEmpty()) {
dropItem(level, pos, mesh);

View File

@ -0,0 +1,170 @@
/*
* 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.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.EnergyStorage;
import net.minecraftforge.network.NetworkHooks;
import thedarkcolour.exdeorum.blockentity.helper.EnergyHelper;
import thedarkcolour.exdeorum.blockentity.helper.ItemHelper;
import thedarkcolour.exdeorum.client.screen.RedstoneControlWidget;
import javax.annotation.Nonnull;
import java.util.function.Function;
public abstract class AbstractMachineBlockEntity<M extends AbstractMachineBlockEntity<M>> extends EBlockEntity implements MenuProvider {
public final ItemHelper inventory;
public final EnergyHelper energy;
protected int redstoneMode;
// not saved to NBT
protected boolean hasRedstonePower;
private final LazyOptional<ItemHelper> capabilityInventory;
private final LazyOptional<EnergyStorage> capabilityEnergy;
@SuppressWarnings("unchecked")
public AbstractMachineBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state, Function<M, ItemHelper> inventory, int maxEnergy) {
super(type, pos, state);
this.inventory = inventory.apply((M) this);
this.energy = new EnergyHelper(maxEnergy);
this.capabilityInventory = LazyOptional.of(() -> this.inventory);
this.capabilityEnergy = LazyOptional.of(() -> this.energy);
}
@Override
protected void saveAdditional(CompoundTag nbt) {
super.saveAdditional(nbt);
nbt.put("inventory", this.inventory.serializeNBT());
nbt.putInt("energy", this.energy.getEnergyStored());
nbt.putInt("redstoneMode", this.redstoneMode);
}
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
this.inventory.deserializeNBT(nbt.getCompound("inventory"));
this.energy.setStoredEnergy(nbt.getInt("energy"));
this.redstoneMode = Mth.clamp(nbt.getInt("redstoneMode"), 0, 2);
}
@Override
public void onLoad() {
checkPoweredState(this.level, this.worldPosition);
}
public void checkPoweredState(Level level, BlockPos pos) {
this.hasRedstonePower = level.hasNeighborSignal(pos);
}
public void setRedstoneMode(int redstoneMode) {
this.redstoneMode = redstoneMode;
}
public int getRedstoneMode() {
return this.redstoneMode;
}
@SuppressWarnings("NullableProblems")
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @javax.annotation.Nullable Direction side) {
if (cap == ForgeCapabilities.ENERGY) {
return this.capabilityEnergy.cast();
} else if (cap == ForgeCapabilities.ITEM_HANDLER) {
return this.capabilityInventory.cast();
}
return super.getCapability(cap, side);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
this.capabilityEnergy.invalidate();
this.capabilityInventory.invalidate();
}
@Override
public InteractionResult use(Level level, Player player, InteractionHand hand) {
if (player instanceof ServerPlayer serverPlayer) {
NetworkHooks.openScreen(serverPlayer, this, buffer -> {
buffer.writeBlockPos(getBlockPos());
buffer.writeByte(this.redstoneMode);
});
return InteractionResult.CONSUME;
} else {
return InteractionResult.SUCCESS;
}
}
public boolean stillValid(Player player) {
if (this.level.getBlockEntity(this.worldPosition) != this) {
return false;
} else {
return player.distanceToSqr(this.worldPosition.getX() + 0.5, this.worldPosition.getY() + 0.5, this.worldPosition.getZ() + 0.5) <= 64.0;
}
}
protected abstract boolean isRunning();
protected abstract void tryStartRunning();
// Only called serverside
protected abstract void runMachineTick();
protected abstract int getEnergyConsumption();
public static class ServerTicker<M extends AbstractMachineBlockEntity<M>> implements BlockEntityTicker<M> {
@Override
public void tick(Level level, BlockPos pos, BlockState state, M machine) {
if (machine.redstoneMode == RedstoneControlWidget.REDSTONE_MODE_IGNORED || ((machine.redstoneMode == RedstoneControlWidget.REDSTONE_MODE_UNPOWERED)) != machine.hasRedstonePower) {
var energyConsumption = machine.getEnergyConsumption();
if (machine.energy.getEnergyStored() >= energyConsumption) {
if (!machine.isRunning()) {
machine.tryStartRunning();
}
if (machine.isRunning()) {
machine.energy.extractEnergy(energyConsumption, false);
machine.runMachineTick();
}
}
}
}
}
}

View File

@ -63,19 +63,9 @@ public abstract class AbstractSieveBlockEntity extends EBlockEntity implements S
this.logic.loadNbt(nbt);
}
// Used for rendering and for TOP
public ItemStack getMesh() {
return this.logic.getMesh();
}
// Used for rendering
public float getProgress() {
return this.logic.getProgress();
}
// Used for rendering
public ItemStack getContents() {
return this.logic.getContents();
@Override
public SieveLogic getLogic() {
return this.logic;
}
@Override

View File

@ -0,0 +1,244 @@
/*
* 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.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;
private 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 320 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 = -1;
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() && !this.inventory.getStackInSlot(HAMMER_SLOT).isEmpty()) {
if (canFitResultIntoOutput(input) != null) {
this.progress = 0;
}
}
}
@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.inventory.getStackInSlot(HAMMER_SLOT).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;
}
}
}
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.progress = NOT_RUNNING;
}
this.efficiency = 1f + hammer.getEnchantmentLevel(Enchantments.BLOCK_EFFICIENCY) * 0.17f;
}
@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;
}
}
}
}
}

View File

@ -0,0 +1,116 @@
/*
* 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.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.DataSlot;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.item.ItemStack;
import thedarkcolour.exdeorum.ExDeorum;
import thedarkcolour.exdeorum.menu.AbstractMachineMenu;
import thedarkcolour.exdeorum.registry.EMenus;
import thedarkcolour.exdeorum.tag.EItemTags;
public class MechanicalHammerMenu extends AbstractMachineMenu<MechanicalHammerBlockEntity> {
private static final ResourceLocation EMPTY_SLOT_HAMMER = new ResourceLocation(ExDeorum.ID, "item/empty_slot_hammer");
private static final int NUM_SLOTS = 3;
public MechanicalHammerMenu(int containerId, Inventory playerInventory, FriendlyByteBuf data) {
this(containerId, playerInventory, (MechanicalHammerBlockEntity) readPayload(playerInventory, data));
}
public MechanicalHammerMenu(int containerId, Inventory playerInventory, MechanicalHammerBlockEntity machine) {
super(EMenus.MECHANICAL_HAMMER.get(), containerId, playerInventory, machine);
// input slot
addSlot(machine.inventory.createSlot(0, 32, 35));
// hammer slot
addSlot(machine.inventory.createSlot(1, 56, 35).setBackground(InventoryMenu.BLOCK_ATLAS, EMPTY_SLOT_HAMMER));
// output slot
addSlot(machine.inventory.createSlot(2, 116, 35));
addPlayerSlots(playerInventory, 84);
addDataSlot(new ProgressDataSlot());
}
@Override
public ItemStack quickMoveStack(Player player, int clickedSlot) {
var stack = ItemStack.EMPTY;
var slot = this.slots.get(clickedSlot);
if (slot.hasItem()) {
var clickedStack = slot.getItem();
stack = clickedStack.copy();
if (clickedSlot > 1 && clickedSlot <= NUM_SLOTS) { // moving out of output slots
if (!moveItemStackTo(clickedStack, NUM_SLOTS, PLAYER_SLOTS + NUM_SLOTS, true)) {
return ItemStack.EMPTY;
}
} else if (clickedSlot < 2) { // moving out of input/mesh slot
if (!moveItemStackTo(clickedStack, NUM_SLOTS, NUM_SLOTS + PLAYER_SLOTS, false)) {
return ItemStack.EMPTY;
}
} else if (MechanicalHammerBlockEntity.isValidInput(clickedStack)) { // attempting to move into input slot
if (!moveItemStackTo(clickedStack, 0, 1, false)) {
return ItemStack.EMPTY;
}
} else if (clickedStack.is(EItemTags.HAMMERS)) { // attempting to move into mesh slot
if (!moveItemStackTo(clickedStack, 1, 2, false)) {
return ItemStack.EMPTY;
}
} else if (clickedSlot < NUM_SLOTS + 27) { // attempting to move from inventory to hotbar
if (!moveItemStackTo(clickedStack, NUM_SLOTS + 27, NUM_SLOTS + PLAYER_SLOTS, false)) {
return ItemStack.EMPTY;
}
} else if (clickedSlot < NUM_SLOTS + PLAYER_SLOTS) { // attempting to move from hotbar to inventory
if (!moveItemStackTo(clickedStack, NUM_SLOTS, NUM_SLOTS + 27, false)) {
return ItemStack.EMPTY;
}
}
if (clickedStack.isEmpty()) {
slot.set(ItemStack.EMPTY);
}
slot.setChanged();
if (clickedStack.getCount() == stack.getCount()) {
return ItemStack.EMPTY;
}
slot.onTake(player, clickedStack);
broadcastChanges();
}
return stack;
}
private class ProgressDataSlot extends DataSlot {
@Override
public int get() {
return MechanicalHammerMenu.this.machine.getGuiProgress();
}
@Override
public void set(int value) {
MechanicalHammerMenu.this.machine.setGuiProgress(value);
}
}
}

View File

@ -19,35 +19,19 @@
package thedarkcolour.exdeorum.blockentity;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
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.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.EnergyStorage;
import net.minecraftforge.items.ItemHandlerHelper;
import net.minecraftforge.network.NetworkHooks;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.blockentity.helper.EnergyHelper;
import thedarkcolour.exdeorum.blockentity.helper.ItemHelper;
import thedarkcolour.exdeorum.blockentity.logic.SieveLogic;
import thedarkcolour.exdeorum.client.screen.RedstoneControlWidget;
import thedarkcolour.exdeorum.config.EConfig;
import thedarkcolour.exdeorum.data.TranslationKeys;
import thedarkcolour.exdeorum.menu.MechanicalSieveMenu;
@ -55,107 +39,70 @@ import thedarkcolour.exdeorum.recipe.RecipeUtil;
import thedarkcolour.exdeorum.registry.EBlockEntities;
import thedarkcolour.exdeorum.tag.EItemTags;
import javax.annotation.Nonnull;
public class MechanicalSieveBlockEntity extends AbstractSieveBlockEntity implements MenuProvider {
public class MechanicalSieveBlockEntity extends AbstractMachineBlockEntity<MechanicalSieveBlockEntity> implements SieveLogic.Owner {
private static final Component TITLE = Component.translatable(TranslationKeys.MECHANICAL_SIEVE_SCREEN_TITLE);
private static final int INPUT_SLOT = 0;
public static final int MESH_SLOT = 1;
public final ItemHelper inventory;
public final EnergyHelper energy;
private int redstoneMode;
// not saved to NBT
public boolean hasRedstonePower;
private final LazyOptional<ItemHelper> capabilityInventory;
private final LazyOptional<EnergyStorage> capabilityEnergy;
private final SieveLogic logic;
public MechanicalSieveBlockEntity(BlockPos pos, BlockState state) {
super(EBlockEntities.MECHANICAL_SIEVE.get(), pos, state, owner -> new SieveLogic(owner, false, true));
super(EBlockEntities.MECHANICAL_SIEVE.get(), pos, state, ItemHandler::new, EConfig.SERVER.mechanicalSieveEnergyStorage.get());
this.inventory = new ItemHandler(22);
this.energy = new EnergyHelper(EConfig.SERVER.mechanicalSieveEnergyStorage.get());
this.capabilityInventory = LazyOptional.of(() -> this.inventory);
this.capabilityEnergy = LazyOptional.of(() -> this.energy);
this.logic = new SieveLogic(this, false, true);
}
@Override
protected void saveAdditional(CompoundTag nbt) {
super.saveAdditional(nbt);
nbt.put("inventory", this.inventory.serializeNBT());
nbt.putInt("energy", this.energy.getEnergyStored());
nbt.putInt("redstoneMode", this.redstoneMode);
this.logic.saveNbt(nbt);
}
@Override
public void load(CompoundTag nbt) {
super.load(nbt);
this.inventory.deserializeNBT(nbt.getCompound("inventory"));
this.energy.setStoredEnergy(nbt.getInt("energy"));
this.redstoneMode = Mth.clamp(nbt.getInt("redstoneMode"), 0, 2);
this.logic.loadNbt(nbt);
}
@Override
public void onLoad() {
checkPoweredState(this.level, this.worldPosition);
protected boolean isRunning() {
return !this.logic.getContents().isEmpty();
}
@Override
public InteractionResult use(Level level, Player player, InteractionHand hand) {
if (player instanceof ServerPlayer serverPlayer) {
NetworkHooks.openScreen(serverPlayer, this, buffer -> {
buffer.writeBlockPos(getBlockPos());
buffer.writeByte(this.redstoneMode);
});
return InteractionResult.CONSUME;
} else {
return InteractionResult.SUCCESS;
protected void tryStartRunning() {
var input = this.inventory.getStackInSlot(INPUT_SLOT);
if (this.logic.isValidInput(input)) {
this.logic.startSifting(AbstractSieveBlockEntity.singleCopy(input));
input.shrink(1);
}
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @javax.annotation.Nullable Direction side) {
if (cap == ForgeCapabilities.ENERGY) {
return this.capabilityEnergy.cast();
} else if (cap == ForgeCapabilities.ITEM_HANDLER) {
return this.capabilityInventory.cast();
}
return super.getCapability(cap, side);
protected void runMachineTick() {
this.logic.sift(0.01f);
}
@Override
public void invalidateCaps() {
super.invalidateCaps();
this.capabilityEnergy.invalidate();
this.capabilityInventory.invalidate();
protected int getEnergyConsumption() {
return EConfig.SERVER.mechanicalSieveEnergyConsumption.get();
}
private void serverTick() {
if (this.redstoneMode == RedstoneControlWidget.REDSTONE_MODE_IGNORED || ((this.redstoneMode == RedstoneControlWidget.REDSTONE_MODE_UNPOWERED)) != this.hasRedstonePower) {
var energyConsumption = EConfig.SERVER.mechanicalSieveEnergyConsumption.get();
@Override
public void writeVisualData(FriendlyByteBuf buffer) {
buffer.writeItem(this.logic.getMesh());
buffer.writeFloat(this.logic.getProgress());
buffer.writeItem(this.logic.getContents());
}
if (this.energy.getEnergyStored() >= energyConsumption) {
if (this.logic.getContents().isEmpty()) {
var input = this.inventory.getStackInSlot(INPUT_SLOT);
if (this.logic.isValidInput(input)) {
this.logic.startSifting(AbstractSieveBlockEntity.singleCopy(input));
input.shrink(1);
}
}
if (!this.logic.getContents().isEmpty()) {
this.energy.extractEnergy(energyConsumption, false);
this.logic.sift(0.01f);
}
}
}
@Override
public void readVisualData(FriendlyByteBuf buffer) {
this.logic.setMesh(buffer.readItem(), false);
this.logic.setProgress(buffer.readFloat());
this.logic.setContents(buffer.readItem());
}
@Override
@ -205,43 +152,32 @@ public class MechanicalSieveBlockEntity extends AbstractSieveBlockEntity impleme
return TITLE;
}
@Nullable
@Override
public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player pPlayer) {
return new MechanicalSieveMenu(containerId, playerInventory, this);
}
public boolean stillValid(Player player) {
if (this.level.getBlockEntity(this.worldPosition) != this) {
return false;
} else {
return player.distanceToSqr(this.worldPosition.getX() + 0.5, this.worldPosition.getY() + 0.5, this.worldPosition.getZ() + 0.5) <= 64.0;
}
}
@Override
public SieveLogic getLogic() {
return this.logic;
}
public void setRedstoneMode(int redstoneMode) {
this.redstoneMode = redstoneMode;
@SuppressWarnings("DataFlowIssue")
@Override
public ServerLevel getServerLevel() {
return (ServerLevel) this.level;
}
public int getRedstoneMode() {
return this.redstoneMode;
}
private static class ItemHandler extends ItemHelper {
private final MechanicalSieveBlockEntity sieve;
public void checkPoweredState(Level level, BlockPos pos) {
this.hasRedstonePower = level.hasNeighborSignal(pos);
}
private class ItemHandler extends ItemHelper {
public ItemHandler(int size) {
super(size);
public ItemHandler(MechanicalSieveBlockEntity sieve) {
super(22);
this.sieve = sieve;
}
@Override
public boolean isItemValid(int slot, @NotNull ItemStack stack) {
public boolean isItemValid(int slot, ItemStack stack) {
if (slot == INPUT_SLOT) {
return !RecipeUtil.getSieveRecipes(getStackInSlot(1).getItem(), stack).isEmpty();
} else if (slot == MESH_SLOT) {
@ -264,20 +200,13 @@ public class MechanicalSieveBlockEntity extends AbstractSieveBlockEntity impleme
@Override
protected void onContentsChanged(int slot) {
if (slot == MESH_SLOT) {
MechanicalSieveBlockEntity.this.logic.setMesh(MechanicalSieveBlockEntity.this.inventory.getStackInSlot(MESH_SLOT));
this.sieve.logic.setMesh(this.sieve.inventory.getStackInSlot(MESH_SLOT));
}
}
@Override
protected void onLoad() {
MechanicalSieveBlockEntity.this.logic.setMesh(MechanicalSieveBlockEntity.this.inventory.getStackInSlot(MESH_SLOT), false);
}
}
public static class ServerTicker implements BlockEntityTicker<MechanicalSieveBlockEntity> {
@Override
public void tick(Level level, BlockPos pos, BlockState state, MechanicalSieveBlockEntity sieve) {
sieve.serverTick();
this.sieve.logic.setMesh(this.sieve.inventory.getStackInSlot(MESH_SLOT), false);
}
}
}

View File

@ -27,14 +27,11 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.LootParams;
import thedarkcolour.exdeorum.config.EConfig;
import thedarkcolour.exdeorum.recipe.RecipeUtil;
import thedarkcolour.exdeorum.recipe.sieve.SieveRecipe;
import thedarkcolour.exdeorum.tag.EItemTags;
import java.util.Map;
public class SieveLogic {
private final Owner owner;
private final boolean saveMesh;
@ -80,7 +77,7 @@ public class SieveLogic {
// Need epsilon because floating point decimals suck
if (this.progress >= 1.0f - Mth.EPSILON) {
var level = this.owner.getServerLevel();
var context = new LootContext.Builder(new LootParams(level, Map.of(), Map.of(), 0)).create(null);
var context = RecipeUtil.emptyLootContext(level);
var rand = level.random;
var limitDrops = this.contents.getItem() == Items.MOSS_BLOCK && EConfig.SERVER.limitMossSieveDrops.get();
var handledAnyDrops = false;
@ -207,5 +204,7 @@ public class SieveLogic {
boolean handleResultItem(ItemStack result, ServerLevel level, RandomSource rand);
void markUpdated();
SieveLogic getLogic();
}
}

View File

@ -43,6 +43,7 @@ import net.minecraftforge.fml.event.config.ModConfigEvent;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import thedarkcolour.exdeorum.ExDeorum;
import thedarkcolour.exdeorum.client.screen.MechanicalHammerScreen;
import thedarkcolour.exdeorum.client.screen.MechanicalSieveScreen;
import thedarkcolour.exdeorum.client.ter.BarrelRenderer;
import thedarkcolour.exdeorum.client.ter.CrucibleRenderer;
@ -101,6 +102,7 @@ public class ClientHandler {
event.enqueueWork(() -> {
setRenderLayers();
MenuScreens.register(EMenus.MECHANICAL_SIEVE.get(), MechanicalSieveScreen::new);
MenuScreens.register(EMenus.MECHANICAL_HAMMER.get(), MechanicalHammerScreen::new);
});
}

View File

@ -0,0 +1,94 @@
/*
* 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.client.screen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Inventory;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.ExDeorum;
import thedarkcolour.exdeorum.blockentity.MechanicalHammerMenu;
import thedarkcolour.exdeorum.config.EConfig;
import thedarkcolour.exdeorum.data.TranslationKeys;
public class MechanicalHammerScreen extends AbstractContainerScreen<MechanicalHammerMenu> {
private static final ResourceLocation BACKGROUND_TEXTURE = new ResourceLocation(ExDeorum.ID, "textures/gui/container/mechanical_hammer.png");
public static final int RECIPE_CLICK_AREA_POS_X = 80;
public static final int RECIPE_CLICK_AREA_POS_Y = 34;
public static final int RECIPE_CLICK_AREA_WIDTH = 23;
public static final int RECIPE_CLICK_AREA_HEIGHT = 16;
@Nullable
private RedstoneControlWidget redstoneControlWidget;
public MechanicalHammerScreen(MechanicalHammerMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
this.imageWidth = 176;
this.imageHeight = 166;
}
@Override
protected void init() {
super.init();
this.redstoneControlWidget = new RedstoneControlWidget(this.menu, BACKGROUND_TEXTURE, this.leftPos + this.imageWidth, this.topPos + 3);
addRenderableWidget(this.redstoneControlWidget);
}
@Nullable
public RedstoneControlWidget getRedstoneControlWidget() {
return this.redstoneControlWidget;
}
@Override
protected void renderBg(GuiGraphics graphics, float pPartialTick, int pMouseX, int pMouseY) {
int left = this.leftPos;
int top = this.topPos;
graphics.blit(BACKGROUND_TEXTURE, left, top, 0, 0, this.imageWidth, this.imageHeight);
// energy bar
int energy = Mth.floor(54 * ((float) this.menu.prevEnergy / EConfig.SERVER.mechanicalSieveEnergyStorage.get()));
graphics.blit(BACKGROUND_TEXTURE, left + 10, top + 15 + 54 - energy, this.imageWidth, 16 + 54 - energy, 12, energy);
// progress arrow
int progress = Math.min(23, this.menu.machine.getGuiProgress());
graphics.blit(BACKGROUND_TEXTURE, left + RECIPE_CLICK_AREA_POS_X, top + RECIPE_CLICK_AREA_POS_Y, this.imageWidth, 0, progress, 16);
}
@Override
public void render(GuiGraphics graphics, int mx, int my, float pPartialTick) {
renderBackground(graphics);
super.render(graphics, mx, my, pPartialTick);
renderTooltip(graphics, mx, my);
int rx = mx - this.leftPos;
int ry = my - this.topPos;
if (9 <= rx && rx < 23 && 14 <= ry && ry < 70) {
var energyTooltip = Component.translatable(TranslationKeys.ENERGY).append(Component.translatable(TranslationKeys.FRACTION_DISPLAY, this.menu.prevEnergy, EConfig.SERVER.mechanicalSieveEnergyStorage.get())).append(" FE");
graphics.renderTooltip(Minecraft.getInstance().font, energyTooltip, mx, my);
}
}
}

View File

@ -40,14 +40,12 @@ public class MechanicalSieveScreen extends AbstractContainerScreen<MechanicalSie
public static final int RECIPE_CLICK_AREA_WIDTH = 21;
public static final int RECIPE_CLICK_AREA_HEIGHT = 14;
private final MechanicalSieveMenu menu;
@Nullable
private RedstoneControlWidget redstoneControlWidget;
public MechanicalSieveScreen(MechanicalSieveMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
this.menu = menu;
this.imageWidth = 176;
this.imageHeight = 173;
this.inventoryLabelY += 7;
@ -57,7 +55,7 @@ public class MechanicalSieveScreen extends AbstractContainerScreen<MechanicalSie
protected void init() {
super.init();
this.redstoneControlWidget = new RedstoneControlWidget(this, BACKGROUND_TEXTURE, this.leftPos + 176, this.topPos + 3);
this.redstoneControlWidget = new RedstoneControlWidget(this.menu, BACKGROUND_TEXTURE, this.leftPos + this.imageWidth, this.topPos + 3);
addRenderableWidget(this.redstoneControlWidget);
}
@ -73,12 +71,12 @@ public class MechanicalSieveScreen extends AbstractContainerScreen<MechanicalSie
graphics.blit(BACKGROUND_TEXTURE, left, top, 0, 0, this.imageWidth, this.imageHeight);
// energy bar
int energy = Mth.floor(54 * ((float) this.menu.prevSieveEnergy / EConfig.SERVER.mechanicalSieveEnergyStorage.get()));
int energy = Mth.floor(54 * ((float) this.menu.prevEnergy / EConfig.SERVER.mechanicalSieveEnergyStorage.get()));
graphics.blit(BACKGROUND_TEXTURE, left + 10, top + 22 + 54 - energy, this.imageWidth, 14 + 54 - energy, 12, energy);
// progress arrow
int progress = Math.min(21, (int) (this.menu.sieve.getProgress() * 22));
graphics.blit(BACKGROUND_TEXTURE, left + 51, top + 42, this.imageWidth, 0, progress, 14);
int progress = Math.min(21, (int) (this.menu.machine.getLogic().getProgress() * 22));
graphics.blit(BACKGROUND_TEXTURE, left + RECIPE_CLICK_AREA_POS_X, top + RECIPE_CLICK_AREA_POS_Y, this.imageWidth, 0, progress, 14);
}
@Override
@ -91,7 +89,7 @@ public class MechanicalSieveScreen extends AbstractContainerScreen<MechanicalSie
int ry = my - this.topPos;
if (9 <= rx && rx < 23 && 21 <= ry && ry < 77) {
var energyTooltip = Component.translatable(TranslationKeys.ENERGY).append(Component.translatable(TranslationKeys.FRACTION_DISPLAY, this.menu.prevSieveEnergy, EConfig.SERVER.mechanicalSieveEnergyStorage.get())).append(" FE");
var energyTooltip = Component.translatable(TranslationKeys.ENERGY).append(Component.translatable(TranslationKeys.FRACTION_DISPLAY, this.menu.prevEnergy, EConfig.SERVER.mechanicalSieveEnergyStorage.get())).append(" FE");
graphics.renderTooltip(Minecraft.getInstance().font, energyTooltip, mx, my);
}
}

View File

@ -31,6 +31,7 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvents;
import thedarkcolour.exdeorum.data.TranslationKeys;
import thedarkcolour.exdeorum.menu.AbstractMachineMenu;
import java.util.List;
@ -46,7 +47,7 @@ public class RedstoneControlWidget implements GuiEventListener, NarratableEntry,
};
private static final Component REDSTONE_CONTROL_LABEL = Component.translatable(TranslationKeys.REDSTONE_CONTROL_LABEL);
private final MechanicalSieveScreen screen;
private final AbstractMachineMenu<?> screen;
private final ResourceLocation texture;
private final int posX;
private final int posY;
@ -68,7 +69,7 @@ public class RedstoneControlWidget implements GuiEventListener, NarratableEntry,
// Last time (from currentTimeMillis) this button was clicked, used in animation lerp
private long lastClicked = -1L;
public RedstoneControlWidget(MechanicalSieveScreen screen, ResourceLocation texture, int posX, int posY) {
public RedstoneControlWidget(AbstractMachineMenu<?> screen, ResourceLocation texture, int posX, int posY) {
this.screen = screen;
this.texture = texture;
this.posX = posX;
@ -106,7 +107,7 @@ public class RedstoneControlWidget implements GuiEventListener, NarratableEntry,
var font = Minecraft.getInstance().font;
if (this.expanded) {
var redstoneMode = this.screen.getMenu().sieve.getRedstoneMode();
var redstoneMode = this.screen.machine.getRedstoneMode();
graphics.blit(this.texture, this.posX, this.posY, this.expandedU, this.expandedV, this.expandedWidth, this.expandedHeight);
for (int i = 0; i < 3; ++i) {
graphics.blit(this.texture, this.buttonsPosX + (i * 19), this.buttonsPosY, (redstoneMode == i ? this.tabU + 16 : this.tabU), this.tabV + this.tabHeight, 16, 16);
@ -157,9 +158,10 @@ public class RedstoneControlWidget implements GuiEventListener, NarratableEntry,
return false;
}
@SuppressWarnings("DataFlowIssue")
private void setRedstoneMode(int redstoneMode) {
this.screen.getMenu().clickMenuButton(Minecraft.getInstance().player, redstoneMode);
Minecraft.getInstance().gameMode.handleInventoryButtonClick(this.screen.getMenu().containerId, redstoneMode);
this.screen.clickMenuButton(Minecraft.getInstance().player, redstoneMode);
Minecraft.getInstance().gameMode.handleInventoryButtonClick(this.screen.containerId, redstoneMode);
}
private void drawPartialConfig(GuiGraphics graphics) {

View File

@ -23,31 +23,33 @@ import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraftforge.registries.ForgeRegistries;
import thedarkcolour.exdeorum.blockentity.AbstractSieveBlockEntity;
import thedarkcolour.exdeorum.blockentity.EBlockEntity;
import thedarkcolour.exdeorum.blockentity.logic.SieveLogic;
import thedarkcolour.exdeorum.client.RenderUtil;
import java.util.HashMap;
import java.util.Map;
public class SieveRenderer<T extends AbstractSieveBlockEntity> implements BlockEntityRenderer<T> {
public class SieveRenderer<T extends EBlockEntity & SieveLogic.Owner> implements BlockEntityRenderer<T> {
public static final Map<Item, TextureAtlasSprite> MESH_TEXTURES = new HashMap<>();
@Override
public void render(T sieve, float partialTicks, PoseStack stack, MultiBufferSource buffers, int light, int overlay) {
var contents = sieve.getContents();
var logic = sieve.getLogic();
var contents = logic.getContents();
if (!contents.isEmpty() && contents.getItem() instanceof BlockItem blockItem) {
var block = blockItem.getBlock();
var percentage = sieve.getProgress();
var percentage = logic.getProgress();
var face = RenderUtil.getTopFace(block);
face.renderFlatSpriteLerp(buffers, stack, percentage, 0xff, 0xff, 0xff, light, 1.0f, 15f, 13f);
}
var mesh = sieve.getMesh();
var mesh = logic.getMesh();
if (!mesh.isEmpty()) {
var builder = buffers.getBuffer(RenderType.cutoutMipped());
@ -57,7 +59,8 @@ public class SieveRenderer<T extends AbstractSieveBlockEntity> implements BlockE
if (MESH_TEXTURES.containsKey(meshItem)) {
meshSprite = MESH_TEXTURES.get(meshItem);
} else {
ResourceLocation registryName = ForgeRegistries.ITEMS.getKey(meshItem);
@SuppressWarnings("deprecation")
ResourceLocation registryName = BuiltInRegistries.ITEM.getKey(meshItem);
ResourceLocation textureLoc = registryName.withPrefix("item/mesh/");
meshSprite = RenderUtil.blockAtlas.getSprite(textureLoc);
MESH_TEXTURES.put(meshItem, meshSprite);

View File

@ -48,6 +48,7 @@ import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fml.ModList;
import thedarkcolour.exdeorum.ExDeorum;
import thedarkcolour.exdeorum.blockentity.LavaCrucibleBlockEntity;
import thedarkcolour.exdeorum.client.screen.MechanicalHammerScreen;
import thedarkcolour.exdeorum.client.screen.MechanicalSieveScreen;
import thedarkcolour.exdeorum.compat.GroupedSieveRecipe;
import thedarkcolour.exdeorum.compat.ModIds;
@ -322,6 +323,22 @@ public class ExDeorumJeiPlugin implements IModPlugin {
return List.of();
}
});
registration.addGuiContainerHandler(MechanicalHammerScreen.class, new IGuiContainerHandler<>() {
@Override
public Collection<IGuiClickableArea> getGuiClickableAreas(MechanicalHammerScreen containerScreen, double mouseX, double mouseY) {
IGuiClickableArea clickableArea = IGuiClickableArea.createBasic(MechanicalHammerScreen.RECIPE_CLICK_AREA_POS_X, MechanicalHammerScreen.RECIPE_CLICK_AREA_POS_Y, MechanicalHammerScreen.RECIPE_CLICK_AREA_WIDTH, MechanicalHammerScreen.RECIPE_CLICK_AREA_HEIGHT, HAMMER);
return List.of(clickableArea);
}
@Override
public List<Rect2i> getGuiExtraAreas(MechanicalHammerScreen containerScreen) {
var widget = containerScreen.getRedstoneControlWidget();
if (widget != null) {
return widget.getJeiBounds();
}
return List.of();
}
});
}
private static <C extends Container, T extends Recipe<C>> void addRecipes(IRecipeRegistration registration, RecipeType<T> category, Supplier<net.minecraft.world.item.crafting.RecipeType<T>> type) {

View File

@ -80,14 +80,15 @@ public class ExDeorumInfoProvider implements IProbeInfoProvider {
info.tank(crucible.getTank());
}
} else if (te instanceof SieveBlockEntity sieve) {
if (!sieve.getContents().isEmpty()) {
info.text(CompoundText.create().style(TextStyleClass.LABEL).text("Progress: ").style(TextStyleClass.WARNING).text((Math.round(1000 * sieve.getProgress()) / 10) + "%"));
var logic = sieve.getLogic();
if (!logic.getContents().isEmpty()) {
info.text(CompoundText.create().style(TextStyleClass.LABEL).text("Progress: ").style(TextStyleClass.WARNING).text((Math.round(1000 * logic.getProgress()) / 10) + "%"));
}
if (playerEntity.isShiftKeyDown()) {
var mesh = sieve.getMesh();
var mesh = logic.getMesh();
info.horizontal(info.defaultLayoutStyle().spacing(10).alignment(ElementAlignment.ALIGN_CENTER))
.item(sieve.getMesh(), info.defaultItemStyle().width(16).height(16))
.text(CompoundText.create().info(sieve.getMesh().getDescriptionId()));
.item(mesh, info.defaultItemStyle().width(16).height(16))
.text(CompoundText.create().info(mesh.getDescriptionId()));
if (mesh.isEnchanted()) {
var list = new ObjectArrayList<Component>();
var style = info.defaultTextStyle().height(10);

View File

@ -133,6 +133,8 @@ public class EConfig {
public final BooleanValue allowWitchWaterEntityConversion;
public final IntValue mechanicalSieveEnergyStorage;
public final IntValue mechanicalSieveEnergyConsumption;
public final IntValue mechanicalHammerEnergyStorage;
public final IntValue mechanicalHammerEnergyConsumption;
public Server(ForgeConfigSpec.Builder builder) {
builder.comment("Server configuration for Ex Deorum").push("server");
@ -179,6 +181,12 @@ public class EConfig {
this.mechanicalSieveEnergyConsumption = builder
.comment("The amount of FE/t a tick consumed by the mechanical sieve when sifting a block.")
.defineInRange("mechanical_sieve_energy_consumption", 40, 0, Integer.MAX_VALUE);
this.mechanicalHammerEnergyStorage = builder
.comment("The maximum amount of FE the mechanical hammer can have in its energy storage.")
.defineInRange("mechanical_hammer_energy_storage", 40_000, 0, Integer.MAX_VALUE);
this.mechanicalHammerEnergyConsumption = builder
.comment("The amount of FE/t a tick consumed by the mechanical hammer when crushing a block.")
.defineInRange("mechanical_hammer_energy_consumption", 20, 0, Integer.MAX_VALUE);
builder.pop();
}

View File

@ -78,6 +78,8 @@ class English {
english.add(TranslationKeys.REDSTONE_CONTROL_MODES[RedstoneControlWidget.REDSTONE_MODE_POWERED], "Powered");
english.add(TranslationKeys.REDSTONE_CONTROL_LABEL, "Redstone Mode");
english.add(TranslationKeys.REDSTONE_CONTROL_MODE, "Mode: ");
english.add(TranslationKeys.MECHANICAL_HAMMER_SCREEN_TITLE, "Mechanical Hammer");
english.add(TranslationKeys.MECHANICAL_HAMMER_SCREEN_TITLE, "Consuming %s FE/t");
english.addBlock(EBlocks.VEXING_ARCHWOOD_CRUCIBLE, "Vexing Archwood Crucible");
english.addBlock(EBlocks.CASCADING_ARCHWOOD_CRUCIBLE, "Cascading Archwood Crucible");

View File

@ -80,4 +80,6 @@ public class TranslationKeys {
};
public static final String REDSTONE_CONTROL_LABEL = "gui." + ExDeorum.ID + ".redstone_control.label";
public static final String REDSTONE_CONTROL_MODE = "gui." + ExDeorum.ID + ".redstone_control.mode";
public static final String MECHANICAL_HAMMER_SCREEN_TITLE = ExDeorum.ID + ".container.mechanical_hammer";
public static final String MACHINE_FE_PER_TICK = "gui." + ExDeorum.ID + ".machine_fe_per_tick";
}

View File

@ -21,6 +21,7 @@ package thedarkcolour.exdeorum.loot;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.enchantment.Enchantments;
@ -56,22 +57,8 @@ public class HammerLootModifier extends LootModifier {
// fortune handling; more likely to boost drops if there are none to begin with
if (context.hasParam(LootContextParams.TOOL)) {
var stack = context.getParam(LootContextParams.TOOL);
var fortune = stack.getEnchantmentLevel(Enchantments.BLOCK_FORTUNE);
if (fortune != 0) {
var chance = context.getRandom().nextFloat();
if (resultAmount == 0) {
if (chance < 0.06 * fortune) {
resultAmount++;
}
} else {
if (chance < 0.03 * fortune) {
resultAmount++;
}
}
}
var hammer = context.getParam(LootContextParams.TOOL);
resultAmount += calculateFortuneBonus(hammer, context.getRandom(), resultAmount == 0);
}
if (resultAmount > 0) {
@ -89,5 +76,32 @@ public class HammerLootModifier extends LootModifier {
public Codec<? extends IGlobalLootModifier> codec() {
return CODEC;
}
/**
* Calculates the bonus number of drops for a hammer enchanted with fortune.
* @param hammer The hammer in question
* @param rand RNG
* @param zeroBaseDrops Whether there were no drops to begin with
* @return The additional number of drops, to be added to the number of base drops
*/
public static int calculateFortuneBonus(ItemStack hammer, RandomSource rand, boolean zeroBaseDrops) {
var fortune = hammer.getEnchantmentLevel(Enchantments.BLOCK_FORTUNE);
if (fortune != 0) {
var chance = rand.nextFloat();
if (zeroBaseDrops) {
if (chance < 0.06f * fortune) {
return 1;
}
} else {
if (chance < 0.03f * fortune) {
return 1;
}
}
}
return 0;
}
}

View File

@ -0,0 +1,121 @@
/*
* 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.menu;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.blockentity.AbstractMachineBlockEntity;
import thedarkcolour.exdeorum.network.NetworkHandler;
public abstract class AbstractMachineMenu<M extends AbstractMachineBlockEntity<M>> extends AbstractContainerMenu {
protected static final int PLAYER_SLOTS = 36; // hotbar + inventory
@Nullable
private final ServerPlayer player;
public final M machine;
public int prevEnergy;
protected AbstractMachineMenu(MenuType<?> pMenuType, int pContainerId, Inventory playerInventory, M machine) {
super(pMenuType, pContainerId);
this.machine = machine;
if (playerInventory.player instanceof ServerPlayer serverPlayer) {
this.player = serverPlayer;
} else {
this.player = null;
}
}
// todo find a better way to do this
@SuppressWarnings({"DataFlowIssue", "unchecked"})
protected static <M extends AbstractMachineBlockEntity<M>> M readPayload(Inventory playerInventory, FriendlyByteBuf data) {
var machine = (M) playerInventory.player.level().getBlockEntity(data.readBlockPos());
machine.setRedstoneMode(data.readByte());
return machine;
}
// Call after own slots have been added
protected final void addPlayerSlots(Inventory playerInventory, int startY) {
// Inventory
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 9; ++j) {
addSlot(new Slot(playerInventory, j + i * 9 + 9, 8 + j * 18, startY + i * 18));
}
}
// Hotbar
for (int k = 0; k < 9; ++k) {
addSlot(new Slot(playerInventory, k, 8 + k * 18, startY + 58));
}
}
// When the server sends a menu property message, the client handles the synced property here.
public void setClientProperty(int index, int value) {
if (index == 0) {
this.prevEnergy = value;
}
}
@Override
public void broadcastChanges() {
super.broadcastChanges();
if (this.player != null) {
syncProperties(this.player);
}
}
@Override
public void broadcastFullState() {
super.broadcastFullState();
if (this.player != null) {
syncProperties(this.player);
}
}
protected void syncProperties(ServerPlayer player) {
if (this.prevEnergy != this.machine.energy.getEnergyStored()) {
this.prevEnergy = this.machine.energy.getEnergyStored();
NetworkHandler.sendMenuProperty(player, this.containerId, 0, this.prevEnergy);
}
}
@Override
public boolean clickMenuButton(Player pPlayer, int id) {
if (0 <= id && id < 3) {
this.machine.setRedstoneMode(id);
return true;
}
return false;
}
@Override
public boolean stillValid(Player player) {
return this.machine.stillValid(player);
}
}

View File

@ -1,31 +0,0 @@
/*
* 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.menu;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
public abstract class EContainerMenu extends AbstractContainerMenu {
protected EContainerMenu(MenuType<?> pMenuType, int pContainerId) {
super(pMenuType, pContainerId);
}
// When the server sends a menu property message, the client handles the synced property here.
public abstract void setClientProperty(int index, int value);
}

View File

@ -20,38 +20,24 @@ package thedarkcolour.exdeorum.menu;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.InventoryMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.ExDeorum;
import thedarkcolour.exdeorum.blockentity.MechanicalSieveBlockEntity;
import thedarkcolour.exdeorum.network.NetworkHandler;
import thedarkcolour.exdeorum.registry.EMenus;
public class MechanicalSieveMenu extends EContainerMenu {
public class MechanicalSieveMenu extends AbstractMachineMenu<MechanicalSieveBlockEntity> {
private static final ResourceLocation EMPTY_SLOT_MESH = new ResourceLocation(ExDeorum.ID, "item/empty_slot_mesh");
private static final int NUM_SLOTS = 22; // input + mesh, 20 output slots
private static final int PLAYER_SLOTS = 36; // hotbar + inventory
public final MechanicalSieveBlockEntity sieve;
@Nullable
private final ServerPlayer player;
public int prevSieveEnergy;
public MechanicalSieveMenu(int containerId, Inventory playerInventory, FriendlyByteBuf data) {
this(containerId, playerInventory, (MechanicalSieveBlockEntity) playerInventory.player.level().getBlockEntity(data.readBlockPos()));
this.sieve.setRedstoneMode(data.readByte());
this(containerId, playerInventory, (MechanicalSieveBlockEntity) readPayload(playerInventory, data));
}
public MechanicalSieveMenu(int containerId, Inventory playerInventory, MechanicalSieveBlockEntity sieve) {
super(EMenus.MECHANICAL_SIEVE.get(), containerId);
this.sieve = sieve;
super(EMenus.MECHANICAL_SIEVE.get(), containerId, playerInventory, sieve);
// input slot
addSlot(sieve.inventory.createSlot(0, 26, 30));
@ -63,54 +49,7 @@ public class MechanicalSieveMenu extends EContainerMenu {
addSlot(sieve.inventory.createSlot(2 + r * 5 + c, 80 + c * 18, 15 + r * 18));
}
}
// Player slots
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 9; ++j) {
addSlot(new Slot(playerInventory, j + i * 9 + 9, 8 + j * 18, 7 + 84 + i * 18));
}
}
for (int k = 0; k < 9; ++k) {
addSlot(new Slot(playerInventory, k, 8 + k * 18, 149));
}
if (playerInventory.player instanceof ServerPlayer serverPlayer) {
this.player = serverPlayer;
} else {
this.player = null;
}
}
@Override
public void broadcastChanges() {
super.broadcastChanges();
if (this.player != null) {
if (this.prevSieveEnergy != this.sieve.energy.getEnergyStored()) {
this.prevSieveEnergy = this.sieve.energy.getEnergyStored();
NetworkHandler.sendMenuProperty(this.player, this.containerId, 0, this.prevSieveEnergy);
}
}
}
@Override
public void broadcastFullState() {
super.broadcastFullState();
if (this.player != null) {
if (this.prevSieveEnergy != this.sieve.energy.getEnergyStored()) {
this.prevSieveEnergy = this.sieve.energy.getEnergyStored();
NetworkHandler.sendMenuProperty(this.player, this.containerId, 0, this.prevSieveEnergy);
}
}
}
@Override
public void setClientProperty(int index, int value) {
if (index == 0) {
this.prevSieveEnergy = value;
}
addPlayerSlots(playerInventory, 91);
}
@Override
@ -130,11 +69,11 @@ public class MechanicalSieveMenu extends EContainerMenu {
if (!moveItemStackTo(clickedStack, NUM_SLOTS, NUM_SLOTS + PLAYER_SLOTS, false)) {
return ItemStack.EMPTY;
}
} else if (this.sieve.getLogic().isValidInput(clickedStack)) { // attempting to move into input slot
} else if (this.machine.getLogic().isValidInput(clickedStack)) { // attempting to move into input slot
if (!moveItemStackTo(clickedStack, 0, 1, false)) {
return ItemStack.EMPTY;
}
} else if (this.sieve.getLogic().isValidMesh(clickedStack)) { // attempting to move into mesh slot
} else if (this.machine.getLogic().isValidMesh(clickedStack)) { // attempting to move into mesh slot
if (!moveItemStackTo(clickedStack, 1, 2, false)) {
return ItemStack.EMPTY;
}
@ -161,18 +100,4 @@ public class MechanicalSieveMenu extends EContainerMenu {
return stack;
}
@Override
public boolean clickMenuButton(Player player, int id) {
if (0 <= id && id < 3) {
this.sieve.setRedstoneMode(id);
return false;
}
return false;
}
@Override
public boolean stillValid(Player player) {
return this.sieve.stillValid(player);
}
}

View File

@ -23,7 +23,7 @@ import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.entity.player.Player;
import thedarkcolour.exdeorum.blockentity.EBlockEntity;
import thedarkcolour.exdeorum.client.ClientHandler;
import thedarkcolour.exdeorum.menu.EContainerMenu;
import thedarkcolour.exdeorum.menu.AbstractMachineMenu;
public class ClientMessageHandler {
public static boolean isInVoidWorld;
@ -54,7 +54,7 @@ public class ClientMessageHandler {
public static void handleMenuProperty(MenuPropertyMessage msg) {
Player player = Minecraft.getInstance().player;
if (player != null && player.containerMenu instanceof EContainerMenu menu && menu.containerId == msg.containerId()) {
if (player != null && player.containerMenu instanceof AbstractMachineMenu menu && menu.containerId == msg.containerId()) {
menu.setClientProperty(msg.index(), msg.value());
}
}

View File

@ -24,6 +24,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.TagKey;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.Container;
@ -36,7 +37,9 @@ import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.LootDataType;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.providers.number.*;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.fluids.FluidStack;
@ -55,6 +58,7 @@ import thedarkcolour.exdeorum.registry.ERecipeTypes;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public final class RecipeUtil {
@ -298,7 +302,12 @@ public final class RecipeUtil {
}
}
@SuppressWarnings("deprecation")
public static boolean isTagEmpty(TagKey<Item> tag) {
return BuiltInRegistries.ITEM.getTag(tag).map(set -> !set.iterator().hasNext()).orElse(PreferredOres.getPreferredOre(tag) == Items.AIR);
}
public static LootContext emptyLootContext(ServerLevel level) {
return new LootContext.Builder(new LootParams(level, Map.of(), Map.of(), 0)).create(null);
}
}

View File

@ -87,6 +87,7 @@ public class SieveRecipe extends ProbabilityRecipe {
return new SieveRecipe(id, ingredient, mesh, result, resultAmount, byHandOnly);
}
@SuppressWarnings("deprecation")
@Override
public @Nullable SieveRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) {
Ingredient ingredient = Ingredient.fromNetwork(buffer);
@ -96,6 +97,7 @@ public class SieveRecipe extends ProbabilityRecipe {
return new SieveRecipe(id, ingredient, mesh, result, resultAmount, buffer.readBoolean());
}
@SuppressWarnings("deprecation")
@Override
public void toNetwork(FriendlyByteBuf buffer, SieveRecipe recipe) {
recipe.getIngredient().toNetwork(buffer);

View File

@ -26,6 +26,7 @@ import thedarkcolour.exdeorum.ExDeorum;
import thedarkcolour.exdeorum.blockentity.BarrelBlockEntity;
import thedarkcolour.exdeorum.blockentity.InfestedLeavesBlockEntity;
import thedarkcolour.exdeorum.blockentity.LavaCrucibleBlockEntity;
import thedarkcolour.exdeorum.blockentity.MechanicalHammerBlockEntity;
import thedarkcolour.exdeorum.blockentity.MechanicalSieveBlockEntity;
import thedarkcolour.exdeorum.blockentity.SieveBlockEntity;
import thedarkcolour.exdeorum.blockentity.WaterCrucibleBlockEntity;
@ -156,4 +157,5 @@ public class EBlockEntities {
EBlocks.CRYSTALLIZED_SIEVE.get()
).build(null));
public static final RegistryObject<BlockEntityType<MechanicalSieveBlockEntity>> MECHANICAL_SIEVE = BLOCK_ENTITIES.register("mechanical_sieve", () -> BlockEntityType.Builder.of(MechanicalSieveBlockEntity::new, EBlocks.MECHANICAL_SIEVE.get()).build(null));
public static final RegistryObject<BlockEntityType<MechanicalHammerBlockEntity>> MECHANICAL_HAMMER = BLOCK_ENTITIES.register("mechanical_hammer", () -> BlockEntityType.Builder.of(MechanicalHammerBlockEntity::new, EBlocks.MECHANICAL_HAMMER.get()).build(null));
}

View File

@ -121,6 +121,8 @@ public class EBlocks {
public static final RegistryObject<SieveBlock> CRYSTALLIZED_SIEVE = registerSieve("crystallized_sieve", SoundType.GLASS);
// Mechanical Sieve (todo add properties)
public static final RegistryObject<MechanicalSieveBlock> MECHANICAL_SIEVE = BLOCKS.register("mechanical_sieve", () -> new MechanicalSieveBlock(of()));
// Mechanical Hammer (todo add properties)
public static final RegistryObject<MechanicalHammerBlock> MECHANICAL_HAMMER = BLOCKS.register("mechanical_hammer", () -> new MechanicalHammerBlock(of()));
// Lava Crucibles
public static final RegistryObject<LavaCrucibleBlock> PORCELAIN_CRUCIBLE = registerLavaCrucible("porcelain_crucible", true, SoundType.STONE);

View File

@ -225,6 +225,7 @@ public class EItems {
public static final RegistryObject<BlockItem> CRYSTALLIZED_SIEVE = registerItemBlock(EBlocks.CRYSTALLIZED_SIEVE);
// Mechanical Sieves
public static final RegistryObject<BlockItem> MECHANICAL_SIEVE = registerItemBlock(EBlocks.MECHANICAL_SIEVE);
public static final RegistryObject<BlockItem> MECHANICAL_HAMMER = registerItemBlock(EBlocks.MECHANICAL_HAMMER);
// Lava Crucibles
public static final RegistryObject<BlockItem> PORCELAIN_CRUCIBLE = registerItemBlock(EBlocks.PORCELAIN_CRUCIBLE);
@ -365,6 +366,7 @@ public class EItems {
output.accept(CRYSTALLIZED_SIEVE.get());
}
output.accept(MECHANICAL_SIEVE.get());
output.accept(MECHANICAL_HAMMER.get());
output.accept(PORCELAIN_CRUCIBLE.get());
output.accept(WARPED_CRUCIBLE.get());

View File

@ -25,10 +25,12 @@ import net.minecraftforge.network.IContainerFactory;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.RegistryObject;
import thedarkcolour.exdeorum.ExDeorum;
import thedarkcolour.exdeorum.blockentity.MechanicalHammerMenu;
import thedarkcolour.exdeorum.menu.MechanicalSieveMenu;
public class EMenus {
public static final DeferredRegister<MenuType<?>> MENUS = DeferredRegister.create(Registries.MENU, ExDeorum.ID);
public static final RegistryObject<MenuType<MechanicalSieveMenu>> MECHANICAL_SIEVE = MENUS.register("mechanical_sieve", () -> new MenuType<>((IContainerFactory<MechanicalSieveMenu>) MechanicalSieveMenu::new, FeatureFlags.DEFAULT_FLAGS));
public static final RegistryObject<MenuType<MechanicalHammerMenu>> MECHANICAL_HAMMER = MENUS.register("mechanical_hammer", () -> new MenuType<>((IContainerFactory<MechanicalHammerMenu>) MechanicalHammerMenu::new, FeatureFlags.DEFAULT_FLAGS));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB