Merge pull request #40 from thedarkcolour/mechanical-hammer

Mechanical hammer
This commit is contained in:
thedarkcolour 2024-02-01 18:58:03 -08:00 committed by GitHub
commit faeef9fc36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 1240 additions and 292 deletions

View File

@ -1,4 +1,4 @@
// 1.20.1 2024-01-10T20:18:25.7260241 Loot Tables
// 1.20.1 2024-01-25T14:16:46.237861 Loot Tables
105d8a61ea7145d7798146d385d4aad24fd1588d data/exdeorum/loot_tables/blocks/acacia_barrel.json
1e77127a82cbba0937bb02694f65cf1893aeffcb data/exdeorum/loot_tables/blocks/acacia_crucible.json
fcc00910a8cc94bed6339d6833fcec53c501a0d7 data/exdeorum/loot_tables/blocks/acacia_sieve.json
@ -73,6 +73,7 @@ b38104ee25127d9c65ad9e323ed879f76df7a048 data/exdeorum/loot_tables/blocks/mangro
475b89fd8f09834652f80c93d8a6d0964d708ead data/exdeorum/loot_tables/blocks/maple_barrel.json
54f36187d7fb97dedc4680d14e2ad7d70b5c64af data/exdeorum/loot_tables/blocks/maple_crucible.json
7ffe80360af055f3977d05b5684a299886bcb756 data/exdeorum/loot_tables/blocks/maple_sieve.json
926a9224e747bf53eb81448c1abb55d73fce5092 data/exdeorum/loot_tables/blocks/mechanical_hammer.json
a84508222cb36b07cb20ee31915d802bcc411149 data/exdeorum/loot_tables/blocks/mechanical_sieve.json
cad973c873a2e50ccfac91e88eadb3c2462d39d1 data/exdeorum/loot_tables/blocks/oak_barrel.json
f94bc97efbfd26ccf7dba32d414fb5e33decd5f6 data/exdeorum/loot_tables/blocks/oak_crucible.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2024-01-26T16:29:51.8613552 ModKit Language: en_us for mod 'exdeorum'
2f38e208308fb2a7f506e2ce2b400970d1c27b57 assets/exdeorum/lang/en_us.json
// 1.20.1 2024-01-25T14:16:46.2316734 ModKit Language: en_us for mod 'exdeorum'
248ab1a837b9857fb1928e826cb02f9f142e8d96 assets/exdeorum/lang/en_us.json

View File

@ -1,4 +1,4 @@
// 1.20.1 2024-01-10T20:18:25.7240245 ModKit Item Models for mod 'exdeorum'
// 1.20.1 2024-01-25T14:16:46.2347562 ModKit Item Models for mod 'exdeorum'
4ba3bb2c6174ac3728a4b85e34681f118ec8eb34 assets/exdeorum/models/item/acacia_barrel.json
c03ce41f7c071498fcbd5f5225e91dcb2f365fbb assets/exdeorum/models/item/acacia_crucible.json
3b4f1d45c0d9c4cd1d9a5cdf6ddc8d2c9791bca5 assets/exdeorum/models/item/acacia_sieve.json
@ -105,6 +105,7 @@ ff89dc05408074da0e9d41bfef91dfe975302403 assets/exdeorum/models/item/mahogany_cr
c3f2af2a88cd97148b05efbd6e24fc2558fcc0b8 assets/exdeorum/models/item/maple_barrel.json
cc045825c562e9133858ce5cfe6e6f1dcb747d8e assets/exdeorum/models/item/maple_crucible.json
a64e9b9ce91ac6b2f36690a770afc52b8900a614 assets/exdeorum/models/item/maple_sieve.json
59cdfe2c26f9d4a16ab7e8e7a87c97403eafa562 assets/exdeorum/models/item/mechanical_hammer.json
1e2b482f5fc4d283f5ca12919b575f86dc4a9541 assets/exdeorum/models/item/mechanical_sieve.json
d543d3e18bdcf2bf79a762b52cc61a4161124db1 assets/exdeorum/models/item/mycelium_spores.json
1f48b2ce3452ce5d02142c9f663ae7bdb7d1d934 assets/exdeorum/models/item/netherite_hammer.json

View File

@ -84,6 +84,7 @@
"block.exdeorum.maple_barrel": "Maple Barrel",
"block.exdeorum.maple_crucible": "Maple Crucible",
"block.exdeorum.maple_sieve": "Maple Sieve",
"block.exdeorum.mechanical_hammer": "Mechanical Hammer",
"block.exdeorum.mechanical_sieve": "Mechanical Sieve",
"block.exdeorum.oak_barrel": "Oak Barrel",
"block.exdeorum.oak_crucible": "Oak Crucible",
@ -118,6 +119,7 @@
"block.exdeorum.willow_crucible": "Willow Crucible",
"block.exdeorum.willow_sieve": "Willow Sieve",
"block.exdeorum.witch_water": "Witch Water",
"exdeorum.container.mechanical_hammer": "Mechanical Hammer",
"exdeorum.container.mechanical_sieve": "Mechanical Sieve",
"fluid_type.exdeorum.witch_water": "Witch Water",
"generator.exdeorum.void_world": "Void World",
@ -145,6 +147,7 @@
"gui.exdeorum.redstone_control.unpowered": "Unpowered",
"info.exdeorum.crimson_nylium_spores": "Use on netherrack to turn it into a crimson nylium block.",
"info.exdeorum.grass_seeds": "Use on dirt to turn it into a grass block.",
"info.exdeorum.mechanical_hammer": "The Mechanical Hammer is a machine that, when supplied with Forge Energy (FE), will hammer blocks without a player having to do it themselves. It can operate without a hammer, but adding any hammer will double the speed, and efficiency enchantments on the hammer will further increase speed. It also supports three different modes of redstone control. Since Ex Deorum does not provide a way to generate FE, you will need another mod to provide power.",
"info.exdeorum.mechanical_sieve": "The Mechanical Sieve is a machine that, when supplied with a mesh and Forge Energy (FE), will sift blocks without a player having to do it themselves. It also supports three different modes of redstone control. Since Ex Deorum does not provide a way to generate FE, you will need another mod to provide power.",
"info.exdeorum.mycelium_spores": "Use on dirt to turn it into mycelium.",
"info.exdeorum.sculk_core": "Use a sculk core on a Sculk Shrieker to enable it to spawn Wardens. Normally, Sculk Shriekers placed by players cannot spawn Wardens, so this item is useful for obtaining Sculk items in a SkyBlock world.",

View File

@ -0,0 +1,3 @@
{
"parent": "exdeorum:block/mechanical_hammer_off"
}

View File

@ -0,0 +1,35 @@
{
"parent": "minecraft:recipes/root",
"criteria": {
"has_item": {
"conditions": {
"items": [
{
"items": [
"minecraft:hopper"
]
}
]
},
"trigger": "minecraft:inventory_changed"
},
"has_the_recipe": {
"conditions": {
"recipe": "exdeorum:mechanical_hammer"
},
"trigger": "minecraft:recipe_unlocked"
}
},
"requirements": [
[
"has_item",
"has_the_recipe"
]
],
"rewards": {
"recipes": [
"exdeorum:mechanical_hammer"
]
},
"sends_telemetry_event": true
}

View File

@ -0,0 +1,26 @@
{
"type": "minecraft:block",
"pools": [
{
"bonus_rolls": 0.0,
"conditions": [
{
"condition": "minecraft:survives_explosion"
}
],
"entries": [
{
"type": "minecraft:item",
"functions": [
{
"function": "exdeorum:machine"
}
],
"name": "exdeorum:mechanical_hammer"
}
],
"rolls": 1.0
}
],
"random_sequence": "exdeorum:blocks/mechanical_hammer"
}

View File

@ -0,0 +1,27 @@
{
"type": "minecraft:crafting_shaped",
"category": "misc",
"key": {
"#": {
"item": "minecraft:iron_block"
},
"H": {
"item": "minecraft:hopper"
},
"I": {
"item": "minecraft:iron_ingot"
},
"T": {
"tag": "exdeorum:hammers"
}
},
"pattern": [
"III",
"ITI",
"#H#"
],
"result": {
"item": "exdeorum:mechanical_hammer"
},
"show_notification": true
}

View File

@ -0,0 +1,133 @@
/*
* 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.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
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 net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraftforge.items.ItemStackHandler;
import org.jetbrains.annotations.Nullable;
import thedarkcolour.exdeorum.blockentity.MechanicalHammerBlockEntity;
import thedarkcolour.exdeorum.blockentity.MechanicalSieveBlockEntity;
import thedarkcolour.exdeorum.config.EConfig;
import thedarkcolour.exdeorum.data.TranslationKeys;
import thedarkcolour.exdeorum.registry.EBlockEntities;
import java.util.List;
public class MechanicalHammerBlock extends EBlock {
public static final BooleanProperty RUNNING = BooleanProperty.create("running");
public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING;
public MechanicalHammerBlock(Properties properties) {
super(properties, EBlockEntities.MECHANICAL_HAMMER);
registerDefaultState(defaultBlockState().setValue(RUNNING, false));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(RUNNING, FACING);
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) {
return type == EBlockEntities.MECHANICAL_HAMMER.get() && !level.isClientSide ? (BlockEntityTicker<T>) new MechanicalHammerBlockEntity.ServerTicker<>() : null;
}
@Override
public void appendHoverText(ItemStack stack, @Nullable BlockGetter level, List<Component> tooltip, TooltipFlag flag) {
var nbt = BlockItem.getBlockEntityData(stack);
if (nbt != null) {
var inventoryNbt = nbt.getCompound("inventory");
var inventory = new ItemStackHandler();
inventory.deserializeNBT(inventoryNbt);
var hammer = inventory.getStackInSlot(MechanicalHammerBlockEntity.HAMMER_SLOT);
if (!hammer.isEmpty()) {
tooltip.add(Component.translatable(TranslationKeys.MECHANICAL_HAMMER_HAMMER_LABEL).withStyle(ChatFormatting.GRAY).append(Component.translatable(hammer.getDescriptionId())));
}
var energy = nbt.getInt("energy");
tooltip.add(Component.translatable(TranslationKeys.ENERGY).withStyle(ChatFormatting.GRAY).append(Component.translatable(TranslationKeys.FRACTION_DISPLAY, energy, EConfig.SERVER.mechanicalSieveEnergyStorage.get())).append(" FE"));
}
}
@Override
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 MechanicalHammerBlockEntity sieve) {
if (!sieve.inventory.getStackInSlot(MechanicalHammerBlockEntity.HAMMER_SLOT).isEmpty()) {
var stack = new ItemStack(this);
BlockItem.setBlockEntityData(stack, EBlockEntities.MECHANICAL_HAMMER.get(), sieve.saveWithoutMetadata());
var itemEntity = new ItemEntity(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, stack);
itemEntity.setDefaultPickUpDelay();
level.addFreshEntity(itemEntity);
}
}
}
super.playerWillDestroy(level, pos, pState, player);
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
var nbt = BlockItem.getBlockEntityData(context.getItemInHand());
var state = defaultBlockState();
if (nbt != null && nbt.contains("progress") && nbt.getInt("progress") != MechanicalHammerBlockEntity.NOT_RUNNING) {
state = state.setValue(RUNNING, true);
}
return state.setValue(FACING, context.getHorizontalDirection().getOpposite());
}
@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,174 @@
/*
* 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();
protected void noEnergyTick() {}
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();
}
} else {
machine.noEnergyTick();
}
}
}
}
}

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,263 @@
/*
* 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;
}
}
}
}
}

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(40000);
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;
@ -214,6 +215,7 @@ public class ExDeorumJeiPlugin implements IModPlugin {
registration.addRecipeCatalyst(new ItemStack(EItems.IRON_HAMMER.get()), HAMMER);
registration.addRecipeCatalyst(new ItemStack(EItems.DIAMOND_HAMMER.get()), HAMMER);
registration.addRecipeCatalyst(new ItemStack(EItems.NETHERITE_HAMMER.get()), HAMMER);
registration.addRecipeCatalyst(new ItemStack(EItems.MECHANICAL_HAMMER.get()), HAMMER);
}
@Override
@ -231,6 +233,7 @@ public class ExDeorumJeiPlugin implements IModPlugin {
registration.addItemStackInfo(new ItemStack(EItems.CRIMSON_NYLIUM_SPORES.get()), Component.translatable(TranslationKeys.CRIMSON_NYLIUM_SPORES_JEI_INFO));
registration.addItemStackInfo(new ItemStack(EItems.SCULK_CORE.get()), Component.translatable(TranslationKeys.SCULK_CORE_JEI_INFO));
registration.addItemStackInfo(new ItemStack(EItems.MECHANICAL_SIEVE.get()), Component.translatable(TranslationKeys.MECHANICAL_SIEVE_JEI_INFO));
registration.addItemStackInfo(new ItemStack(EItems.MECHANICAL_HAMMER.get()), Component.translatable(TranslationKeys.MECHANICAL_HAMMER_JEI_INFO));
var toRemove = new ArrayList<ItemStack>();
@ -322,6 +325,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

@ -65,6 +65,11 @@ class BlockLoot extends BlockLootSubProvider {
.setRolls(ConstantValue.exactly(1.0F))
.add(LootItem.lootTableItem(EItems.MECHANICAL_SIEVE.get())
.apply(MachineLootFunction.machineLoot())))));
add(EBlocks.MECHANICAL_HAMMER.get(), LootTable.lootTable()
.withPool(applyExplosionCondition(EItems.MECHANICAL_HAMMER.get(), LootPool.lootPool()
.setRolls(ConstantValue.exactly(1.0F))
.add(LootItem.lootTableItem(EItems.MECHANICAL_HAMMER.get())
.apply(MachineLootFunction.machineLoot())))));
}
@Override

View File

@ -31,6 +31,7 @@ class English {
english.add(TranslationKeys.VOID_WORLD_TYPE, "Void World");
english.add(TranslationKeys.FRACTION_DISPLAY, ": %s / %s");
english.add(TranslationKeys.MECHANICAL_SIEVE_MESH_LABEL, "Mesh: ");
english.add(TranslationKeys.MECHANICAL_HAMMER_HAMMER_LABEL, "Hammer: ");
english.add(TranslationKeys.ENERGY, "Energy");
english.add(TranslationKeys.ROOT_ADVANCEMENT_TITLE, "Don't Look Down...");
@ -55,6 +56,7 @@ class English {
english.add(TranslationKeys.CRIMSON_NYLIUM_SPORES_JEI_INFO, "Use on netherrack to turn it into a crimson nylium block.");
english.add(TranslationKeys.SCULK_CORE_JEI_INFO, "Use a sculk core on a Sculk Shrieker to enable it to spawn Wardens. Normally, Sculk Shriekers placed by players cannot spawn Wardens, so this item is useful for obtaining Sculk items in a SkyBlock world.");
english.add(TranslationKeys.MECHANICAL_SIEVE_JEI_INFO, "The Mechanical Sieve is a machine that, when supplied with a mesh and Forge Energy (FE), will sift blocks without a player having to do it themselves. It also supports three different modes of redstone control. Since Ex Deorum does not provide a way to generate FE, you will need another mod to provide power.");
english.add(TranslationKeys.MECHANICAL_HAMMER_JEI_INFO, "The Mechanical Hammer is a machine that, when supplied with Forge Energy (FE), will hammer blocks without a player having to do it themselves. It can operate without a hammer, but adding any hammer will double the speed, and efficiency enchantments on the hammer will further increase speed. It also supports three different modes of redstone control. Since Ex Deorum does not provide a way to generate FE, you will need another mod to provide power.");
english.add(TranslationKeys.BARREL_COMPOST_CATEGORY_TITLE, "Barrel Compost");
english.add(TranslationKeys.BARREL_COMPOST_RECIPE_VOLUME, "Compost: %s");
@ -79,6 +81,7 @@ 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.addBlock(EBlocks.VEXING_ARCHWOOD_CRUCIBLE, "Vexing Archwood Crucible");
english.addBlock(EBlocks.CASCADING_ARCHWOOD_CRUCIBLE, "Cascading Archwood Crucible");

View File

@ -27,6 +27,7 @@ public class TranslationKeys {
// ": %s / %s"
public static final String FRACTION_DISPLAY = "item." + ExDeorum.ID + ".watering_can_fluid_display";
public static final String MECHANICAL_SIEVE_MESH_LABEL = "item." + ExDeorum.ID + ".mechanical_sieve.mesh_label";
public static final String MECHANICAL_HAMMER_HAMMER_LABEL = "item." + ExDeorum.ID + ".mechanical_hammer.hammer_label";
public static final String ENERGY = "gui." + ExDeorum.ID + ".energy_label";
// Advancements
@ -53,6 +54,7 @@ public class TranslationKeys {
public static final String CRIMSON_NYLIUM_SPORES_JEI_INFO = "info." + ExDeorum.ID + ".crimson_nylium_spores";
public static final String SCULK_CORE_JEI_INFO = "info." + ExDeorum.ID + ".sculk_core";
public static final String MECHANICAL_SIEVE_JEI_INFO = "info." + ExDeorum.ID + ".mechanical_sieve";
public static final String MECHANICAL_HAMMER_JEI_INFO = "info." + ExDeorum.ID + ".mechanical_hammer";
// JEI recipe categories
public static final String BARREL_COMPOST_CATEGORY_TITLE = "gui." + ExDeorum.ID + ".category.barrel_compost";
@ -81,4 +83,5 @@ 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";
}

View File

@ -293,6 +293,16 @@ public class Recipes {
recipe.pattern("I I");
MKRecipeProvider.unlockedByHaving(recipe, Items.HOPPER);
});
recipes.shapedCrafting(RecipeCategory.MISC, EItems.MECHANICAL_HAMMER.get(), recipe -> {
recipe.define('#', Items.IRON_BLOCK);
recipe.define('H', Items.HOPPER);
recipe.define('T', EItemTags.HAMMERS);
recipe.define('I', Items.IRON_INGOT);
recipe.pattern("III");
recipe.pattern("ITI");
recipe.pattern("#H#");
MKRecipeProvider.unlockedByHaving(recipe, Items.HOPPER);
});
}
private static void modUShaped(MKRecipeProvider recipes, String modid, RegistryObject<? extends Item> sides, RegistryObject<? extends Item> middle, RegistryObject<? extends Item> result) {

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
public static final RegistryObject<MechanicalSieveBlock> MECHANICAL_SIEVE = BLOCKS.register("mechanical_sieve", () -> new MechanicalSieveBlock(of().mapColor(MapColor.METAL).requiresCorrectToolForDrops().strength(5f, 1200f)));
// Mechanical Hammer
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));
}

View File

@ -0,0 +1,34 @@
{
"variants": {
"facing=east,running=false": {
"model": "exdeorum:block/mechanical_hammer_off",
"y": 90
},
"facing=east,running=true": {
"model": "exdeorum:block/mechanical_hammer_on",
"y": 90
},
"facing=north,running=false": {
"model": "exdeorum:block/mechanical_hammer_off"
},
"facing=north,running=true": {
"model": "exdeorum:block/mechanical_hammer_on"
},
"facing=south,running=false": {
"model": "exdeorum:block/mechanical_hammer_off",
"y": 180
},
"facing=south,running=true": {
"model": "exdeorum:block/mechanical_hammer_on",
"y": 180
},
"facing=west,running=false": {
"model": "exdeorum:block/mechanical_hammer_off",
"y": 270
},
"facing=west,running=true": {
"model": "exdeorum:block/mechanical_hammer_on",
"y": 270
}
}
}

View File

@ -0,0 +1,9 @@
{
"parent": "minecraft:block/orientable_with_bottom",
"textures": {
"front": "exdeorum:block/mechanical_hammer_front",
"side": "exdeorum:block/mechanical_hammer_side",
"top": "exdeorum:block/mechanical_hammer_top",
"bottom": "exdeorum:block/mechanical_hammer_bottom"
}
}

View File

@ -0,0 +1,9 @@
{
"parent": "minecraft:block/orientable_with_bottom",
"textures": {
"front": "exdeorum:block/mechanical_hammer_front_on",
"side": "exdeorum:block/mechanical_hammer_side",
"top": "exdeorum:block/mechanical_hammer_top",
"bottom": "exdeorum:block/mechanical_hammer_bottom"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB