Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f25c894
initial go at abstract stove classes
Lordfirespeed Mar 28, 2026
c99dc2e
remove unused imports
Lordfirespeed Mar 28, 2026
41030be
remove impl-specific particle stuff
Lordfirespeed Mar 28, 2026
e06a85c
implement `Clearable` in the abstract class
Lordfirespeed Mar 28, 2026
a400951
remove unused `getGrillingArea`
Lordfirespeed Mar 28, 2026
58cb64b
stove classes extend corresponding abstract classes
Lordfirespeed Mar 28, 2026
f75c769
adjust default stove renderer to handle generic stove entity
Lordfirespeed Mar 28, 2026
0e065da
fix: only burn living entities
Lordfirespeed Mar 28, 2026
3e39712
fix: re-adjust StoveBlock for 1.21 after abstraction
Lordfirespeed Mar 28, 2026
f0d29a8
fix: re-adjust StoveBlockEntity for 1.21 after abstraction
Lordfirespeed Mar 28, 2026
182afd4
style: use early-return in `StoveBlock::animateTick`
Lordfirespeed Mar 28, 2026
dd2c49b
style: early-return to simplify `tryToPlaceFoodItem`
Lordfirespeed Apr 14, 2026
71dd4c5
fix: bring back the item place sound for the stove
Lordfirespeed Apr 14, 2026
01c95d3
add editorconfig rule matching java files which sets `indent_style` t…
Lordfirespeed Mar 30, 2026
43b350d
style: standardise indents to use tabs instead of spaces
Lordfirespeed Mar 30, 2026
2eb483a
style: invert `if` condition and remove braces
Lordfirespeed Mar 30, 2026
6403dcf
more tabs, not spaces
Lordfirespeed Mar 30, 2026
f221e6b
move suppress of deprecation warnings to `AbstractStoveBlock`
Lordfirespeed Apr 7, 2026
a3f775d
move `addSmokeParticles` from `private` to `public`
Lordfirespeed Apr 7, 2026
c741944
remove unused field `recipeType`
Lordfirespeed Apr 7, 2026
2e877ca
revert to using `ItemStackHandler`
Lordfirespeed Apr 7, 2026
f6a43c9
fix: `addSmokeParticles` needed adjusting for `ItemStackHandler`
Lordfirespeed Apr 7, 2026
433dcbd
fix: use `SOUND_EXTINGUISH_FIRE` const instead of literal `1009`
Lordfirespeed Apr 13, 2026
91c9940
fix: only trigger gameEvent once on extinguish
Lordfirespeed Apr 13, 2026
af15653
add `gameEvent` call when ignited
Lordfirespeed Apr 13, 2026
5ae040c
add `ignite` method to abstract stove entity, for subtypes to override
Lordfirespeed Apr 13, 2026
2b44e01
fix: standardise how sounds are played
Lordfirespeed Apr 13, 2026
17cf9ba
replace `isCreative` checks with `instabuild` ability checks
Lordfirespeed Apr 13, 2026
13fc0d1
ignition should probably also trigger a block update
Lordfirespeed Apr 13, 2026
a8e5263
delete empty `StoveRenderer`
Lordfirespeed Apr 14, 2026
4b64e09
style: stray space indentation
Lordfirespeed Apr 14, 2026
394cff0
Update item drop method in AbstractStoveBlockEntity
vectorwing Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# http://editorconfig.org
root = true

[*.java]
indent_style = tab

[*.json]
indent_style = space
indent_size = 2
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import vectorwing.farmersdelight.client.particle.SteamParticle;
import vectorwing.farmersdelight.client.recipebook.RecipeCategories;
import vectorwing.farmersdelight.client.renderer.*;
import vectorwing.farmersdelight.common.block.entity.StoveBlockEntity;
import vectorwing.farmersdelight.common.EnumParameters;
import vectorwing.farmersdelight.common.item.component.ItemStackWrapper;
import vectorwing.farmersdelight.common.registry.*;
Expand Down Expand Up @@ -77,7 +78,7 @@ public static void registerGuiLayers(RegisterGuiLayersEvent event) {
@SubscribeEvent
public static void onRegisterRenderers(EntityRenderersEvent.RegisterRenderers event) {
event.registerEntityRenderer(ModEntityTypes.ROTTEN_TOMATO.get(), ThrownItemRenderer::new);
event.registerBlockEntityRenderer(ModBlockEntityTypes.STOVE.get(), StoveRenderer::new);
event.registerBlockEntityRenderer(ModBlockEntityTypes.STOVE.get(), DefaultStoveRenderer<StoveBlockEntity>::new);
event.registerBlockEntityRenderer(ModBlockEntityTypes.CUTTING_BOARD.get(), CuttingBoardRenderer::new);
event.registerBlockEntityRenderer(ModBlockEntityTypes.CANVAS_SIGN.get(), CanvasSignRenderer::new);
event.registerBlockEntityRenderer(ModBlockEntityTypes.HANGING_CANVAS_SIGN.get(), HangingCanvasSignRenderer::new);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package vectorwing.farmersdelight.client.renderer;

import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.core.Direction;
import net.minecraft.core.NonNullList;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec2;
import vectorwing.farmersdelight.common.block.StoveBlock;
import vectorwing.farmersdelight.common.block.entity.AbstractStoveBlockEntity;

public class DefaultStoveRenderer<T extends AbstractStoveBlockEntity> implements BlockEntityRenderer<T>
{
private static final float SIZE = 0.375F;
private final ItemRenderer itemRenderer;

public DefaultStoveRenderer(BlockEntityRendererProvider.Context context) {
this.itemRenderer = context.getItemRenderer();
}

@Override
public void render(T stove, float partialTicks, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) {
Direction direction = stove.getBlockState().getValue(StoveBlock.FACING).getOpposite();

var items = stove.getItems();
int posLong = (int) stove.getBlockPos().asLong();

for (int i = 0; i < items.getSlots(); ++i) {
ItemStack stoveStack = items.getStackInSlot(i);
if (stoveStack.isEmpty()) continue;

poseStack.pushPose();

// Center item above the stove
poseStack.translate(0.5D, 1.02D, 0.5D);

// Rotate item to face the stove's front side
float f = -direction.toYRot();
poseStack.mulPose(Axis.YP.rotationDegrees(f));

// Rotate item flat on the stove. Use X and Y from now on
poseStack.mulPose(Axis.XP.rotationDegrees(90.0F));

// Neatly align items according to their index
Vec2 itemOffset = stove.getStoveItemOffset(i);
poseStack.translate(itemOffset.x, itemOffset.y, 0.0D);

// Resize the items
poseStack.scale(SIZE, SIZE, SIZE);

itemRenderer.renderStatic(stoveStack, ItemDisplayContext.FIXED, LevelRenderer.getLightColor(stove.getLevel(), stove.getBlockPos().above()), packedOverlay, poseStack, buffer, stove.getLevel(), posLong + i);
poseStack.popPose();
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package vectorwing.farmersdelight.common.block;

import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.Containers;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.FireChargeItem;
import net.minecraft.world.item.FlintAndSteelItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.crafting.AbstractCookingRecipe;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.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.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.pathfinder.PathType;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.neoforged.neoforge.common.ItemAbilities;
import net.neoforged.neoforge.common.Tags;
import vectorwing.farmersdelight.common.block.entity.AbstractStoveBlockEntity;
import vectorwing.farmersdelight.common.registry.ModDamageTypes;
import vectorwing.farmersdelight.common.tag.CommonTags;
import vectorwing.farmersdelight.common.utility.ItemUtils;
import vectorwing.farmersdelight.common.utility.MathUtils;

import javax.annotation.Nullable;
import java.util.Optional;

@SuppressWarnings("deprecation")
public abstract class AbstractStoveBlock extends BaseEntityBlock
{
public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING;
public static final BooleanProperty LIT = BlockStateProperties.LIT;

private static final VoxelShape GRILLING_AREA = Block.box(3.0F, 0.0F, 3.0F, 13.0F, 1.0F, 13.0F);

public AbstractStoveBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(
this.stateDefinition.any()
.setValue(FACING, Direction.NORTH)
.setValue(LIT, false)
);
}
@Override
public ItemInteractionResult useItemOn(ItemStack heldStack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (state.getValue(LIT)) {
var extinguishResult = tryToExtinguish(heldStack, state, level, pos, player, hand, hit);
if (extinguishResult != ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION) return extinguishResult;
} else {
var igniteResult = tryToIgnite(heldStack, state, level, pos, player, hand, hit);
if (igniteResult != ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION) return igniteResult;
}

return tryToPlaceFoodItem(heldStack, state, level, pos, player, hand, hit);
}

protected ItemInteractionResult tryToIgnite(ItemStack heldStack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
Item heldItem = heldStack.getItem();

if (heldItem instanceof FlintAndSteelItem) {
if (!level.isClientSide()) {
level.playSound(null, pos, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, MathUtils.RAND.nextFloat() * 0.4F + 0.8F);
}
ignite(player, level, pos, state);
heldStack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand));
return ItemInteractionResult.SUCCESS;
}

if (heldItem instanceof FireChargeItem) {
if (!level.isClientSide()) {
level.playSound(null, pos, SoundEvents.FIRECHARGE_USE, SoundSource.BLOCKS, 1.0F, (MathUtils.RAND.nextFloat() - MathUtils.RAND.nextFloat()) * 0.2F + 1.0F);
}
ignite(player, level, pos, state);
if (!player.getAbilities().instabuild) {
heldStack.shrink(1);
}
return ItemInteractionResult.SUCCESS;
}

return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
}

protected ItemInteractionResult tryToExtinguish(ItemStack heldStack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (heldStack.canPerformAction(ItemAbilities.SHOVEL_DIG)) {
if (!level.isClientSide()) {
level.levelEvent(null, LevelEvent.SOUND_EXTINGUISH_FIRE, pos, 0);
}
extinguish(player, level, pos, state);
heldStack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand));
return ItemInteractionResult.sidedSuccess(level.isClientSide());
}

if (heldStack.is(Tags.Items.BUCKETS_WATER)) {
if (!level.isClientSide()) {
level.playSound(null, pos, SoundEvents.GENERIC_EXTINGUISH_FIRE, SoundSource.BLOCKS, 1.0F, 1.0F);
}
extinguish(player, level, pos, state);
if (!player.getAbilities().instabuild) player.setItemInHand(hand, heldStack.getCraftingRemainingItem());
return ItemInteractionResult.sidedSuccess(level.isClientSide());
}

return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
}

protected ItemInteractionResult tryToPlaceFoodItem(ItemStack heldStack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (isStoveTopCovered(level, pos, state)) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
if (!(level.getBlockEntity(pos) instanceof AbstractStoveBlockEntity stoveEntity)) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;

var maybeRecipe = stoveEntity.getCookingRecipe(heldStack);
if (maybeRecipe.isEmpty()) return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
if (level.isClientSide) return ItemInteractionResult.CONSUME;
boolean placeFoodSuccess = stoveEntity.placeFood(player, player.getAbilities().instabuild ? heldStack.copy() : heldStack, maybeRecipe.get());
if (!placeFoodSuccess) return ItemInteractionResult.CONSUME;
level.playSound(null, pos, SoundEvents.LANTERN_PLACE, SoundSource.BLOCKS, 0.5F, 1.0F);
return ItemInteractionResult.SUCCESS;
}

public void ignite(@Nullable Entity entity, LevelAccessor level, BlockPos pos, BlockState state) {
if (level.getBlockEntity(pos) instanceof AbstractStoveBlockEntity stoveEntity) {
stoveEntity.ignite();
}
if (level.isClientSide()) return;

var newState = state.setValue(LIT, true);
level.setBlock(pos, newState, 11);
level.gameEvent(entity, GameEvent.BLOCK_CHANGE, pos);
}

public void extinguish(@Nullable Entity entity, LevelAccessor level, BlockPos pos, BlockState state) {
if (level.getBlockEntity(pos) instanceof AbstractStoveBlockEntity stoveEntity) {
stoveEntity.extinguish();
}
if (level.isClientSide()) return;

var newState = state.setValue(LIT, false);
level.setBlock(pos, newState, 11);
level.gameEvent(entity, GameEvent.BLOCK_CHANGE, pos);
}

@Override
public RenderShape getRenderShape(BlockState pState) {
return RenderShape.MODEL;
}

@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState()
.setValue(FACING, context.getHorizontalDirection().getOpposite())
.setValue(LIT, true);
}

@Override
public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
burnEntitySteppingOnStove(level, pos, state, entity);
super.stepOn(level, pos, state, entity);
}

protected void burnEntitySteppingOnStove(Level level, BlockPos pos, BlockState state, Entity entity) {
if (!state.getValue(LIT)) return;
if (!entity.getBoundingBox().intersects(GRILLING_AREA.bounds().move(pos.above()))) return;
if (entity.isSteppingCarefully()) return;
if (!(entity instanceof LivingEntity)) return;
entity.hurt(ModDamageTypes.getSimpleDamageSource(level, ModDamageTypes.STOVE_BURN), 1.0F);
}

@Override
public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
if (state.is(newState.getBlock())) return;
if (level.getBlockEntity(pos) instanceof AbstractStoveBlockEntity stoveEntity) {
ItemUtils.dropItems(level, pos, stoveEntity.getItems());
}
super.onRemove(state, level, pos, newState, isMoving);
}

/**
* Checks if the state is a Stove, and if the grilling area is being obstructed by the block above.
*/
public static boolean isStoveTopCovered(Level level, BlockPos pos, BlockState stoveState) {
if (!(stoveState.getBlock() instanceof StoveBlock)) return false;
BlockPos abovePos = pos.above();
BlockState aboveState = level.getBlockState(abovePos);
return Shapes.joinIsNotEmpty(GRILLING_AREA, aboveState.getShape(level, abovePos), BooleanOp.AND);
}

@Override
protected void createBlockStateDefinition(final StateDefinition.Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(FACING, LIT);
}

@Nullable
protected static <T extends BlockEntity> BlockEntityTicker<T> createStoveTicker(Level level, BlockEntityType<T> serverType, BlockEntityType<? extends AbstractStoveBlockEntity> clientType) {
if (level.isClientSide) return null;
return createTickerHelper(serverType, clientType, AbstractStoveBlockEntity::serverTick);
}

@Nullable
@Override
public PathType getBlockPathType(BlockState state, BlockGetter level, BlockPos pos, @Nullable Mob entity) {
return state.getValue(LIT) ? PathType.DAMAGE_FIRE : null;
}

@Override
public BlockState rotate(BlockState state, Rotation rotation) {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)));
}

@Override
public BlockState mirror(BlockState state, Mirror mirror) {
return state.rotate(mirror.getRotation(state.getValue(FACING)));
}
}
Loading