From f8b7ffbd60b04aaefe30ec69d13eda33a9947a3b Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:45:24 +0100 Subject: [PATCH 01/25] Maybe fix package-info annotations --- .../vectorwing/farmersdelight/client/particle/package-info.java | 2 +- .../vectorwing/farmersdelight/client/renderer/package-info.java | 2 +- .../common/block/entity/container/package-info.java | 2 +- .../common/block/entity/inventory/package-info.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/particle/package-info.java b/src/main/java/vectorwing/farmersdelight/client/particle/package-info.java index 93c060f8d..06c80d60d 100644 --- a/src/main/java/vectorwing/farmersdelight/client/particle/package-info.java +++ b/src/main/java/vectorwing/farmersdelight/client/particle/package-info.java @@ -2,6 +2,6 @@ @MethodsReturnNonnullByDefault package vectorwing.farmersdelight.client.particle; -import net.minecraft.MethodsReturnNonnullByDefault; +import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/vectorwing/farmersdelight/client/renderer/package-info.java b/src/main/java/vectorwing/farmersdelight/client/renderer/package-info.java index 3f4ba16bd..6a9a26ba7 100644 --- a/src/main/java/vectorwing/farmersdelight/client/renderer/package-info.java +++ b/src/main/java/vectorwing/farmersdelight/client/renderer/package-info.java @@ -2,6 +2,6 @@ @MethodsReturnNonnullByDefault package vectorwing.farmersdelight.client.renderer; -import net.minecraft.MethodsReturnNonnullByDefault; +import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/vectorwing/farmersdelight/common/block/entity/container/package-info.java b/src/main/java/vectorwing/farmersdelight/common/block/entity/container/package-info.java index 7a73eb5b0..b0227a466 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/entity/container/package-info.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/entity/container/package-info.java @@ -2,6 +2,6 @@ @MethodsReturnNonnullByDefault package vectorwing.farmersdelight.common.block.entity.container; -import net.minecraft.MethodsReturnNonnullByDefault; +import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/vectorwing/farmersdelight/common/block/entity/inventory/package-info.java b/src/main/java/vectorwing/farmersdelight/common/block/entity/inventory/package-info.java index fa6ff70cc..708d44e6b 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/entity/inventory/package-info.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/entity/inventory/package-info.java @@ -2,6 +2,6 @@ @MethodsReturnNonnullByDefault package vectorwing.farmersdelight.common.block.entity.inventory; -import net.minecraft.MethodsReturnNonnullByDefault; +import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault; From f35a5720c6b24471467fdc7aa8b5fe883a005ac8 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:47:56 +0100 Subject: [PATCH 02/25] Replace annotation again --- .../block/entity/dispenser/CuttingBoardDispenseBehavior.java | 2 +- .../common/crafting/ingredient/ItemAbilityIngredient.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/common/block/entity/dispenser/CuttingBoardDispenseBehavior.java b/src/main/java/vectorwing/farmersdelight/common/block/entity/dispenser/CuttingBoardDispenseBehavior.java index 552a10bc7..7c7e691d4 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/entity/dispenser/CuttingBoardDispenseBehavior.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/entity/dispenser/CuttingBoardDispenseBehavior.java @@ -1,6 +1,6 @@ package vectorwing.farmersdelight.common.block.entity.dispenser; -import net.minecraft.MethodsReturnNonnullByDefault; +import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; import net.minecraft.core.dispenser.BlockSource; import net.minecraft.core.dispenser.DispenseItemBehavior; diff --git a/src/main/java/vectorwing/farmersdelight/common/crafting/ingredient/ItemAbilityIngredient.java b/src/main/java/vectorwing/farmersdelight/common/crafting/ingredient/ItemAbilityIngredient.java index d12c6aabf..7cc1b436d 100644 --- a/src/main/java/vectorwing/farmersdelight/common/crafting/ingredient/ItemAbilityIngredient.java +++ b/src/main/java/vectorwing/farmersdelight/common/crafting/ingredient/ItemAbilityIngredient.java @@ -1,8 +1,8 @@ package vectorwing.farmersdelight.common.crafting.ingredient; +import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; From 18c834aa61b82f0aeabdf15409c0bfb2a35d3f93 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:55:04 +0100 Subject: [PATCH 03/25] Fix block loot --- .../farmersdelight/data/loot/FDBlockLoot.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/data/loot/FDBlockLoot.java b/src/main/java/vectorwing/farmersdelight/data/loot/FDBlockLoot.java index 171c01330..6ef94acc7 100644 --- a/src/main/java/vectorwing/farmersdelight/data/loot/FDBlockLoot.java +++ b/src/main/java/vectorwing/farmersdelight/data/loot/FDBlockLoot.java @@ -1,9 +1,9 @@ package vectorwing.farmersdelight.data.loot; -import net.minecraft.advancements.critereon.BlockPredicate; -import net.minecraft.advancements.critereon.ItemPredicate; -import net.minecraft.advancements.critereon.LocationPredicate; -import net.minecraft.advancements.critereon.StatePropertiesPredicate; +import net.minecraft.advancements.criterion.BlockPredicate; +import net.minecraft.advancements.criterion.ItemPredicate; +import net.minecraft.advancements.criterion.LocationPredicate; +import net.minecraft.advancements.criterion.StatePropertiesPredicate; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponents; @@ -27,6 +27,7 @@ import net.minecraft.world.level.storage.loot.functions.ApplyExplosionDecay; import net.minecraft.world.level.storage.loot.functions.CopyComponentsFunction; import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.level.storage.loot.predicates.*; import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator; @@ -53,12 +54,14 @@ public FDBlockLoot(HolderLookup.Provider holder) { @Override protected void generate() { HolderLookup.RegistryLookup registryLookup = this.registries.lookupOrThrow(Registries.ENCHANTMENT); + HolderLookup.RegistryLookup itemLookup = this.registries.lookupOrThrow(Registries.ITEM); + HolderLookup.RegistryLookup blockLookup = this.registries.lookupOrThrow(Registries.BLOCK); dropSelf(ModBlocks.STOVE.get()); dropNamedContainer(ModBlocks.WOODEN_BASKET.get()); dropNamedContainer(ModBlocks.BAMBOO_BASKET.get()); add(ModBlocks.COOKING_POT.get(), (block) -> LootTable.lootTable().withPool(this.applyExplosionCondition(block, LootPool.lootPool().setRolls(ConstantValue.exactly(1.0F)).add(LootItem.lootTableItem(block) - .apply(CopyComponentsFunction.copyComponents(CopyComponentsFunction.Source.BLOCK_ENTITY) + .apply(CopyComponentsFunction.copyComponentsFromBlockEntity(LootContextParams.BLOCK_ENTITY) .include(DataComponents.CUSTOM_NAME) .include(ModDataComponents.MEAL.get()) .include(ModDataComponents.CONTAINER.get()) @@ -77,7 +80,7 @@ protected void generate() { LootItem.lootTableItem(ModItems.RICE.get()) .when(LootItemBlockStatePropertyCondition.hasBlockStateProperties(block) .setProperties(StatePropertiesPredicate.Builder.properties().hasProperty(RicePaniclesBlock.RICE_AGE, 3))) - .when(MatchTool.toolMatches(ItemPredicate.Builder.item().of(ModTags.Items.KNIVES))), + .when(MatchTool.toolMatches(ItemPredicate.Builder.item().of(itemLookup, ModTags.Items.KNIVES))), LootItem.lootTableItem(ModItems.RICE_PANICLE.get()) .when(LootItemBlockStatePropertyCondition.hasBlockStateProperties(block) .setProperties(StatePropertiesPredicate.Builder.properties().hasProperty(RicePaniclesBlock.RICE_AGE, 3)))))))); @@ -152,7 +155,7 @@ protected void generate() { .setProperties(StatePropertiesPredicate.Builder.properties() .hasProperty(DoublePlantBlock.HALF, DoubleBlockHalf.LOWER))) .when(LocationCheck.checkLocation(LocationPredicate.Builder.location() - .setBlock(BlockPredicate.Builder.block().of(block) + .setBlock(BlockPredicate.Builder.block().of(blockLookup, block) .setProperties(StatePropertiesPredicate.Builder.properties() .hasProperty(DoublePlantBlock.HALF, DoubleBlockHalf.UPPER))), new BlockPos(0, 1, 0)))) .withPool(LootPool.lootPool() @@ -165,7 +168,7 @@ protected void generate() { .setProperties(StatePropertiesPredicate.Builder.properties() .hasProperty(DoublePlantBlock.HALF, DoubleBlockHalf.UPPER))) .when(LocationCheck.checkLocation(LocationPredicate.Builder.location() - .setBlock(BlockPredicate.Builder.block().of(block) + .setBlock(BlockPredicate.Builder.block().of(blockLookup, block) .setProperties(StatePropertiesPredicate.Builder.properties() .hasProperty(DoublePlantBlock.HALF, DoubleBlockHalf.LOWER))), new BlockPos(0, -1, 0))))); From 9c6035083214a5182f25b714b1f7973eb15cffd5 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:08:07 +0100 Subject: [PATCH 04/25] Canvas sign rendering --- .../client/renderer/CanvasSignRenderer.java | 223 +++++++++--------- .../renderer/HangingCanvasSignRenderer.java | 184 +++++++++++---- .../common/registry/ModAtlases.java | 34 +-- 3 files changed, 269 insertions(+), 172 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/renderer/CanvasSignRenderer.java b/src/main/java/vectorwing/farmersdelight/client/renderer/CanvasSignRenderer.java index e2a3e6076..ecd99a001 100644 --- a/src/main/java/vectorwing/farmersdelight/client/renderer/CanvasSignRenderer.java +++ b/src/main/java/vectorwing/farmersdelight/client/renderer/CanvasSignRenderer.java @@ -1,183 +1,176 @@ package vectorwing.farmersdelight.client.renderer; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import com.mojang.math.Axis; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.model.Model; -import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.blockentity.*; +import net.minecraft.client.renderer.blockentity.state.SignRenderState; +import net.minecraft.client.renderer.blockentity.state.StandingSignRenderState; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.client.resources.model.sprite.SpriteGetter; +import net.minecraft.client.resources.model.sprite.SpriteId; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.ARGB; import net.minecraft.util.FormattedCharSequence; import net.minecraft.util.Mth; +import net.minecraft.util.Unit; import net.minecraft.world.entity.Entity; import net.minecraft.world.item.DyeColor; +import net.minecraft.world.level.block.PlainSignBlock; import net.minecraft.world.level.block.SignBlock; import net.minecraft.world.level.block.StandingSignBlock; +import net.minecraft.world.level.block.WallSignBlock; import net.minecraft.world.level.block.entity.SignBlockEntity; import net.minecraft.world.level.block.entity.SignText; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.WoodType; +import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import vectorwing.farmersdelight.common.block.entity.CanvasSignBlockEntity; import vectorwing.farmersdelight.common.block.state.CanvasSign; import vectorwing.farmersdelight.common.registry.ModAtlases; import javax.annotation.Nullable; import java.util.List; -public class CanvasSignRenderer extends SignRenderer +// TODO this is gross and duplicates a bunch of code, but it works for now +public class CanvasSignRenderer implements BlockEntityRenderer { - public static final Vec3 TEXT_OFFSET = new Vec3(0.0D, (double) 0.33333334F, (double) 0.046666667F); private static final int OUTLINE_RENDER_DISTANCE = Mth.square(16); - private final SignModel signModel; + private final Model.Simple groundModel; + private final Model.Simple wallModel; + private final SpriteGetter sprites; private final Font font; public CanvasSignRenderer(BlockEntityRendererProvider.Context context) { - super(context); - - this.signModel = new SignRenderer.SignModel(context.bakeLayer(ModelLayers.createSignModelName(WoodType.SPRUCE))); - this.font = context.getFont(); + this.groundModel = StandingSignRenderer.createSignModel(context.entityModelSet(), WoodType.SPRUCE, PlainSignBlock.Attachment.GROUND); + this.wallModel = StandingSignRenderer.createSignModel(context.entityModelSet(), WoodType.SPRUCE, PlainSignBlock.Attachment.WALL); + this.font = context.font(); + this.sprites = context.sprites(); } @Override - public void render(SignBlockEntity sign, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay) { - BlockState state = sign.getBlockState(); - SignBlock block = (SignBlock) state.getBlock(); - SignRenderer.SignModel model = signModel; - model.stick.visible = block instanceof StandingSignBlock; - - DyeColor dye = null; - if (block instanceof CanvasSign canvasSign) { - dye = canvasSign.getBackgroundColor(); - } + public CanvasSignRenderState createRenderState() { + return new CanvasSignRenderState(); + } - renderSignWithText(sign, poseStack, bufferSource, packedLight, packedOverlay, state, block, dye, model); + @Override + public void submit(CanvasSignRenderState state, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, CameraRenderState camera) { + this.submitSignWithText(state, poseStack, state.breakProgress, submitNodeCollector); } - protected void renderSignWithText(SignBlockEntity sign, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, BlockState state, SignBlock block, @Nullable DyeColor dye, Model model) { + private void submitSignWithText(CanvasSignRenderState state, PoseStack poseStack, ModelFeatureRenderer.CrumblingOverlay breakProgress, SubmitNodeCollector submitNodeCollector) { + Model.Simple bodyModel = state.attachmentType == PlainSignBlock.Attachment.GROUND ? groundModel : wallModel; poseStack.pushPose(); - translateSign(poseStack, -block.getYRotationDegrees(state), state); - renderSign(poseStack, bufferSource, packedLight, packedOverlay, dye, model); - renderSignText(sign.getBlockPos(), sign.getFrontText(), poseStack, bufferSource, packedLight, sign.getTextLineHeight(), sign.getMaxTextLineWidth(), true); - renderSignText(sign.getBlockPos(), sign.getBackText(), poseStack, bufferSource, packedLight, sign.getTextLineHeight(), sign.getMaxTextLineWidth(), false); + poseStack.mulPose(state.transformations.body()); + submitNodeCollector.submitModel(bodyModel, Unit.INSTANCE, poseStack, state.lightCoords, OverlayTexture.NO_OVERLAY, -1, ModAtlases.getCanvasSignSprite(state.dye), this.sprites, 0, breakProgress); poseStack.popPose(); - } - - protected void translateSign(PoseStack poseStack, float angle, BlockState state) { - poseStack.translate(0.5F, 0.75F * this.getSignModelRenderScale(), 0.5F); - poseStack.mulPose(Axis.YP.rotationDegrees(angle)); - if (!(state.getBlock() instanceof StandingSignBlock)) { - poseStack.translate(0.0F, -0.3125F, -0.4375F); + if (state.frontText != null) { + poseStack.pushPose(); + poseStack.mulPose(state.transformations.frontText()); + this.submitSignText(state, poseStack, submitNodeCollector, state.frontText); + poseStack.popPose(); } - } - protected void renderSign(PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay, @Nullable DyeColor dye, Model model) { - poseStack.pushPose(); - float rootScale = getSignModelRenderScale(); - poseStack.scale(rootScale, -rootScale, -rootScale); - Material material = getCanvasSignMaterial(dye); - VertexConsumer vertexConsumer = material.buffer(bufferSource, model::renderType); - this.renderSignModel(poseStack, packedLight, packedOverlay, model, vertexConsumer); - poseStack.popPose(); - } + if (state.backText != null) { + poseStack.pushPose(); + poseStack.mulPose(state.transformations.backText()); + this.submitSignText(state, poseStack, submitNodeCollector, state.backText); + poseStack.popPose(); + } - protected void renderSignModel(PoseStack poseStack, int packedLight, int packedOverlay, Model model, VertexConsumer vertexConsumer) { - SignRenderer.SignModel signModel = (SignRenderer.SignModel) model; - signModel.root.render(poseStack, vertexConsumer, packedLight, packedOverlay); } - protected void renderSignText(BlockPos pos, SignText text, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int textLineHeight, int maxTextLineWidth, boolean isFrontText) { - poseStack.pushPose(); - translateSignText(poseStack, isFrontText, this.getTextOffset()); - - FormattedCharSequence[] formattedCharSequenceList = text.getRenderMessages(Minecraft.getInstance().isTextFilteringEnabled(), (component) -> { - List list = this.font.split(component, maxTextLineWidth); - return list.isEmpty() ? FormattedCharSequence.EMPTY : list.get(0); + private void submitSignText(CanvasSignRenderState state, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, SignText signText) { + int signMidpoint = 4 * state.textLineHeight / 2; + FormattedCharSequence[] formattedLines = signText.getRenderMessages(state.isTextFilteringEnabled, (input) -> { + List components = this.font.split(input, state.maxTextLineWidth); + return components.isEmpty() ? FormattedCharSequence.EMPTY : (FormattedCharSequence)components.get(0); }); - int darkColor; - int baseColor; - boolean hasOutline; - int light; - if (text.hasGlowingText()) { - darkColor = getDarkColor(text, true); - baseColor = text.getColor().getTextColor(); - hasOutline = isOutlineVisible(pos, baseColor); - light = 15728880; + int textColor; + boolean drawOutline; + int lightVal; + if (signText.hasGlowingText()) { + darkColor = getDarkColor(signText, true); + textColor = signText.getColor().getTextColor(); + drawOutline = textColor == DyeColor.BLACK.getTextColor() || state.drawOutline; + lightVal = 15728880; } else { - darkColor = getDarkColor(text, false); - baseColor = darkColor; - hasOutline = false; - light = packedLight; + darkColor = getDarkColor(signText, false); + textColor = darkColor; + drawOutline = false; + lightVal = state.lightCoords; } - int verticalOffset = 2 * textLineHeight + this.getCustomVerticalOffset(); - - for (int i = 0; i < 4; ++i) { - FormattedCharSequence formattedCharSequence = formattedCharSequenceList[i]; - float x = (float) (-this.font.width(formattedCharSequence) / 2); - float y = i * textLineHeight - verticalOffset; - if (hasOutline) { - this.font.drawInBatch8xOutline(formattedCharSequence, x, y, baseColor, darkColor, poseStack.last().pose(), bufferSource, light); - } else { - this.font.drawInBatch(formattedCharSequence, x, y, baseColor, false, poseStack.last().pose(), bufferSource, Font.DisplayMode.POLYGON_OFFSET, 0, light); - } + for(int i = 0; i < 4; ++i) { + FormattedCharSequence actualLine = formattedLines[i]; + float x1 = (float)(-this.font.width(actualLine) / 2); + submitNodeCollector.submitText(poseStack, x1, (float)(i * state.textLineHeight - signMidpoint), actualLine, false, Font.DisplayMode.POLYGON_OFFSET, lightVal, textColor, 0, drawOutline ? darkColor : 0); } - - poseStack.popPose(); } - private void translateSignText(PoseStack poseStack, boolean isFrontText, Vec3 pos) { - if (!isFrontText) { - poseStack.mulPose(Axis.YP.rotationDegrees(180.0F)); - } - - float textScale = 0.015625F * this.getSignTextRenderScale(); - poseStack.translate(pos.x, pos.y, pos.z); - poseStack.scale(textScale, -textScale, textScale); - } - - public static boolean isOutlineVisible(BlockPos pos, int textColor) { - if (textColor == DyeColor.BLACK.getTextColor()) { + private static boolean isOutlineVisible(BlockPos pos) { + Minecraft minecraft = Minecraft.getInstance(); + LocalPlayer player = minecraft.player; + if (player != null && minecraft.options.getCameraType().isFirstPerson() && player.isScoping()) { return true; } else { - Minecraft minecraft = Minecraft.getInstance(); - LocalPlayer localPlayer = minecraft.player; - if (localPlayer != null && minecraft.options.getCameraType().isFirstPerson() && localPlayer.isScoping()) { - return true; - } else { - Entity entity = minecraft.getCameraEntity(); - return entity != null && entity.distanceToSqr(Vec3.atCenterOf(pos)) < (double) OUTLINE_RENDER_DISTANCE; - } + Entity camera = minecraft.getCameraEntity(); + return camera != null && camera.distanceToSqr(Vec3.atCenterOf(pos)) < (double)OUTLINE_RENDER_DISTANCE; } } protected static int getDarkColor(SignText text, boolean isOutlineVisible) { int textColor = text.getColor().getTextColor(); - if (textColor == DyeColor.BLACK.getTextColor() && text.hasGlowingText()) { - return -988212; + float brightness = isOutlineVisible ? 0.4f : 0.6f; + return textColor == DyeColor.BLACK.getTextColor() && text.hasGlowingText() ? -988212 : ARGB.scaleRGB(textColor, brightness); + } + + @Override + public void extractRenderState(CanvasSignBlockEntity blockEntity, CanvasSignRenderState state, float partialTicks, Vec3 cameraPosition, ModelFeatureRenderer.@org.jspecify.annotations.Nullable CrumblingOverlay breakProgress) { + BlockEntityRenderer.super.extractRenderState(blockEntity, state, partialTicks, cameraPosition, breakProgress); + state.maxTextLineWidth = blockEntity.getMaxTextLineWidth(); + state.textLineHeight = blockEntity.getTextLineHeight(); + state.frontText = blockEntity.getFrontText(); + state.backText = blockEntity.getBackText(); + state.isTextFilteringEnabled = Minecraft.getInstance().isTextFilteringEnabled(); + state.drawOutline = isOutlineVisible(blockEntity.getBlockPos()); + state.woodType = SignBlock.getWoodType(blockEntity.getBlockState().getBlock()); + + BlockState blockState = blockEntity.getBlockState(); + state.attachmentType = PlainSignBlock.getAttachmentPoint(blockState); + if (blockState.getBlock() instanceof WallSignBlock) { + state.transformations = StandingSignRenderer.TRANSFORMATIONS.wallTransformation(blockState.getValue(WallSignBlock.FACING)); } else { - double brightness = isOutlineVisible ? 0.4D : 0.6D; - int red = (int) ((double) FastColor.ARGB32.red(textColor) * brightness); - int green = (int) ((double) FastColor.ARGB32.green(textColor) * brightness); - int blue = (int) ((double) FastColor.ARGB32.blue(textColor) * brightness); - return FastColor.ARGB32.color(0, red, green, blue); + state.transformations = StandingSignRenderer.TRANSFORMATIONS.freeTransformations(blockState.getValue(StandingSignBlock.ROTATION)); } - } - Vec3 getTextOffset() { - return TEXT_OFFSET; + state.dye = ((CanvasSign)blockState.getBlock()).getBackgroundColor(); } - public int getCustomVerticalOffset() { - return -1; + @Override + public AABB getRenderBoundingBox(CanvasSignBlockEntity blockEntity) { + if (blockEntity.getBlockState().getBlock() instanceof StandingSignBlock) { + BlockPos pos = blockEntity.getBlockPos(); + return new AABB(pos.getX(), pos.getY(), pos.getZ(), (double)pos.getX() + (double)1.0F, (double)pos.getY() + (double)1.125F, (double)pos.getZ() + (double)1.0F); + } else { + return BlockEntityRenderer.super.getRenderBoundingBox(blockEntity); + } } - public Material getCanvasSignMaterial(@Nullable DyeColor dyeColor) { - return ModAtlases.getCanvasSignMaterial(dyeColor); + @OnlyIn(Dist.CLIENT) + public static class CanvasSignRenderState extends StandingSignRenderState + { + @Nullable public DyeColor dye; } } diff --git a/src/main/java/vectorwing/farmersdelight/client/renderer/HangingCanvasSignRenderer.java b/src/main/java/vectorwing/farmersdelight/client/renderer/HangingCanvasSignRenderer.java index bd9c1031f..d661781d8 100644 --- a/src/main/java/vectorwing/farmersdelight/client/renderer/HangingCanvasSignRenderer.java +++ b/src/main/java/vectorwing/farmersdelight/client/renderer/HangingCanvasSignRenderer.java @@ -1,83 +1,183 @@ package vectorwing.farmersdelight.client.renderer; import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import com.mojang.math.Axis; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; import net.minecraft.client.model.Model; import net.minecraft.client.model.geom.ModelLayers; -import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; import net.minecraft.client.renderer.blockentity.HangingSignRenderer; -import net.minecraft.client.resources.model.Material; +import net.minecraft.client.renderer.blockentity.state.HangingSignRenderState; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.client.resources.model.sprite.SpriteGetter; +import net.minecraft.core.BlockPos; +import net.minecraft.util.ARGB; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.util.Mth; +import net.minecraft.util.Unit; +import net.minecraft.world.entity.Entity; import net.minecraft.world.item.DyeColor; -import net.minecraft.world.level.block.SignBlock; -import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.entity.SignText; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.WoodType; +import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; +import vectorwing.farmersdelight.common.block.entity.HangingCanvasSignBlockEntity; import vectorwing.farmersdelight.common.block.state.CanvasSign; import vectorwing.farmersdelight.common.registry.ModAtlases; import javax.annotation.Nullable; +import java.util.List; -public class HangingCanvasSignRenderer extends CanvasSignRenderer +// TODO this is gross and duplicates a bunch of code, but it works for now +public class HangingCanvasSignRenderer implements BlockEntityRenderer { - private static final Vec3 TEXT_OFFSET = new Vec3(0.0D, (double) -0.32F, (double) 0.073F); - private final HangingSignRenderer.HangingSignModel signModel; + private static final int OUTLINE_RENDER_DISTANCE = Mth.square(16); + + private final Model.Simple wallModel; + private final Model.Simple ceilingModel; + private final Model.Simple middleModel; + private final SpriteGetter sprites; + private final Font font; public HangingCanvasSignRenderer(BlockEntityRendererProvider.Context context) { - super(context); - this.signModel = new HangingSignRenderer.HangingSignModel(context.bakeLayer(ModelLayers.createHangingSignModelName(WoodType.SPRUCE))); - } + this.wallModel = HangingSignRenderer.createSignModel(context.entityModelSet(), WoodType.SPRUCE, HangingSignBlock.Attachment.WALL); + this.ceilingModel = HangingSignRenderer.createSignModel(context.entityModelSet(), WoodType.SPRUCE, HangingSignBlock.Attachment.CEILING); + this.middleModel = HangingSignRenderer.createSignModel(context.entityModelSet(), WoodType.SPRUCE, HangingSignBlock.Attachment.CEILING_MIDDLE); - public float getSignModelRenderScale() { - return 1.0F; + this.font = context.font(); + this.sprites = context.sprites(); } - public float getSignTextRenderScale() { - return 0.8F; + @Override + public HangingCanvasSignRenderState createRenderState() { + return new HangingCanvasSignRenderState(); } @Override - public void render(SignBlockEntity sign, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay) { - BlockState state = sign.getBlockState(); - SignBlock block = (SignBlock) state.getBlock(); - HangingSignRenderer.HangingSignModel model = signModel; - model.evaluateVisibleParts(state); - - DyeColor dye = null; - if (block instanceof CanvasSign canvasSign) { - dye = canvasSign.getBackgroundColor(); + public void submit(HangingCanvasSignRenderState state, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, CameraRenderState camera) { + this.submitSignWithText(state, poseStack, state.breakProgress, submitNodeCollector); + } + + private void submitSignWithText(HangingCanvasSignRenderState state, PoseStack poseStack, ModelFeatureRenderer.CrumblingOverlay breakProgress, SubmitNodeCollector submitNodeCollector) { + Model.Simple bodyModel = getSignModel(state.attachmentType); + poseStack.pushPose(); + poseStack.mulPose(state.transformations.body()); + submitNodeCollector.submitModel(bodyModel, Unit.INSTANCE, poseStack, state.lightCoords, OverlayTexture.NO_OVERLAY, -1, ModAtlases.getHangingCanvasSignSprite(state.dye), this.sprites, 0, breakProgress); + poseStack.popPose(); + if (state.frontText != null) { + poseStack.pushPose(); + poseStack.mulPose(state.transformations.frontText()); + this.submitSignText(state, poseStack, submitNodeCollector, state.frontText); + poseStack.popPose(); + } + + if (state.backText != null) { + poseStack.pushPose(); + poseStack.mulPose(state.transformations.backText()); + this.submitSignText(state, poseStack, submitNodeCollector, state.backText); + poseStack.popPose(); } - renderSignWithText(sign, poseStack, bufferSource, packedLight, packedOverlay, state, block, dye, model); } - @Override - protected void translateSign(PoseStack poseStack, float angle, BlockState state) { - poseStack.translate(0.5D, 0.9375D, 0.5D); - poseStack.mulPose(Axis.YP.rotationDegrees(angle)); - poseStack.translate(0.0F, -0.3125F, 0.0F); + private void submitSignText(HangingCanvasSignRenderState state, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, SignText signText) { + int signMidpoint = 4 * state.textLineHeight / 2; + FormattedCharSequence[] formattedLines = signText.getRenderMessages(state.isTextFilteringEnabled, (input) -> { + List components = this.font.split(input, state.maxTextLineWidth); + return components.isEmpty() ? FormattedCharSequence.EMPTY : components.get(0); + }); + int darkColor; + int textColor; + boolean drawOutline; + int lightVal; + if (signText.hasGlowingText()) { + darkColor = getDarkColor(signText, true); + textColor = signText.getColor().getTextColor(); + drawOutline = textColor == DyeColor.BLACK.getTextColor() || state.drawOutline; + lightVal = 15728880; + } else { + darkColor = getDarkColor(signText, false); + textColor = darkColor; + drawOutline = false; + lightVal = state.lightCoords; + } + + for(int i = 0; i < 4; ++i) { + FormattedCharSequence actualLine = formattedLines[i]; + float x1 = (float)(-this.font.width(actualLine) / 2); + submitNodeCollector.submitText(poseStack, x1, (float)(i * state.textLineHeight - signMidpoint), actualLine, false, Font.DisplayMode.POLYGON_OFFSET, lightVal, textColor, 0, drawOutline ? darkColor : 0); + } } - @Override - protected void renderSignModel(PoseStack poseStack, int packedLight, int packedOverlay, Model model, VertexConsumer vertexConsumer) { - HangingSignRenderer.HangingSignModel hangingSignModel = (HangingSignRenderer.HangingSignModel) model; - hangingSignModel.root.render(poseStack, vertexConsumer, packedLight, packedOverlay); + private Model.Simple getSignModel(HangingSignBlock.Attachment attachment) { + return switch(attachment) { + case WALL -> wallModel; + case CEILING -> ceilingModel; + case CEILING_MIDDLE -> middleModel; + }; } - @Override - public Material getCanvasSignMaterial(@Nullable DyeColor dyeColor) { - return ModAtlases.getHangingCanvasSignMaterial(dyeColor); + private static boolean isOutlineVisible(BlockPos pos) { + Minecraft minecraft = Minecraft.getInstance(); + LocalPlayer player = minecraft.player; + if (player != null && minecraft.options.getCameraType().isFirstPerson() && player.isScoping()) { + return true; + } else { + Entity camera = minecraft.getCameraEntity(); + return camera != null && camera.distanceToSqr(Vec3.atCenterOf(pos)) < (double)OUTLINE_RENDER_DISTANCE; + } + } + + protected static int getDarkColor(SignText text, boolean isOutlineVisible) { + int textColor = text.getColor().getTextColor(); + float brightness = isOutlineVisible ? 0.4f : 0.6f; + return textColor == DyeColor.BLACK.getTextColor() && text.hasGlowingText() ? -988212 : ARGB.scaleRGB(textColor, brightness); } @Override - public int getCustomVerticalOffset() { - return 0; + public void extractRenderState(HangingCanvasSignBlockEntity blockEntity, HangingCanvasSignRenderState state, float partialTicks, Vec3 cameraPosition, ModelFeatureRenderer.@org.jspecify.annotations.Nullable CrumblingOverlay breakProgress) { + BlockEntityRenderer.super.extractRenderState(blockEntity, state, partialTicks, cameraPosition, breakProgress); + state.maxTextLineWidth = blockEntity.getMaxTextLineWidth(); + state.textLineHeight = blockEntity.getTextLineHeight(); + state.frontText = blockEntity.getFrontText(); + state.backText = blockEntity.getBackText(); + state.isTextFilteringEnabled = Minecraft.getInstance().isTextFilteringEnabled(); + state.drawOutline = isOutlineVisible(blockEntity.getBlockPos()); + state.woodType = SignBlock.getWoodType(blockEntity.getBlockState().getBlock()); + + BlockState blockState = blockEntity.getBlockState(); + state.attachmentType = HangingSignBlock.getAttachmentPoint(blockState); + if (blockState.getBlock() instanceof WallSignBlock) { + state.transformations = HangingSignRenderer.TRANSFORMATIONS.wallTransformation(blockState.getValue(WallSignBlock.FACING)); + } else { + state.transformations = HangingSignRenderer.TRANSFORMATIONS.freeTransformations(blockState.getValue(StandingSignBlock.ROTATION)); + } + + state.dye = ((CanvasSign)blockState.getBlock()).getBackgroundColor(); } @Override - Vec3 getTextOffset() { - return TEXT_OFFSET; + public AABB getRenderBoundingBox(HangingCanvasSignBlockEntity blockEntity) { + if (blockEntity.getBlockState().getBlock() instanceof StandingSignBlock) { + BlockPos pos = blockEntity.getBlockPos(); + return new AABB(pos.getX(), pos.getY(), pos.getZ(), (double)pos.getX() + (double)1.0F, (double)pos.getY() + (double)1.125F, (double)pos.getZ() + (double)1.0F); + } else { + return BlockEntityRenderer.super.getRenderBoundingBox(blockEntity); + } + } + + @OnlyIn(Dist.CLIENT) + public static class HangingCanvasSignRenderState extends HangingSignRenderState + { + @Nullable public DyeColor dye; } } diff --git a/src/main/java/vectorwing/farmersdelight/common/registry/ModAtlases.java b/src/main/java/vectorwing/farmersdelight/common/registry/ModAtlases.java index 1b183c38d..69371111d 100644 --- a/src/main/java/vectorwing/farmersdelight/common/registry/ModAtlases.java +++ b/src/main/java/vectorwing/farmersdelight/common/registry/ModAtlases.java @@ -1,7 +1,8 @@ package vectorwing.farmersdelight.common.registry; import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.client.renderer.SpriteMapper; +import net.minecraft.client.resources.model.sprite.SpriteId; import net.minecraft.resources.Identifier; import net.minecraft.world.item.DyeColor; import vectorwing.farmersdelight.FarmersDelight; @@ -14,27 +15,30 @@ public class ModAtlases { - public static final Material BLANK_CANVAS_SIGN_MATERIAL = new Material(Sheets.SIGN_SHEET, Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "entity/signs/canvas")); - public static final Material BLANK_HANGING_CANVAS_SIGN_MATERIAL = new Material(Sheets.SIGN_SHEET, Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "entity/signs/hanging/canvas")); + private static final SpriteMapper CANVAS_SIGN_MAPPER = new SpriteMapper(Sheets.SIGN_SHEET, "entity/signs"); + private static final SpriteMapper HANGING_SIGN_MAPPER = new SpriteMapper(Sheets.SIGN_SHEET, "entity/signs/hanging"); - public static final Map DYED_CANVAS_SIGN_MATERIALS = - Arrays.stream(DyeColor.values()).collect(Collectors.toMap(Function.identity(), ModAtlases::createCanvasSignMaterial)); - public static final Map DYED_HANGING_CANVAS_SIGN_MATERIALS = - Arrays.stream(DyeColor.values()).collect(Collectors.toMap(Function.identity(), ModAtlases::createHangingCanvasSignMaterial)); + public static final SpriteId BLANK_CANVAS_SIGN_SPRITE = CANVAS_SIGN_MAPPER.apply(Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "canvas")); + public static final SpriteId BLANK_HANGING_CANVAS_SIGN_SPRITE = HANGING_SIGN_MAPPER.apply(Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "canvas")); - public static Material createCanvasSignMaterial(DyeColor dyeType) { - return new Material(Sheets.SIGN_SHEET, Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "entity/signs/canvas_" + dyeType.getName())); + public static final Map DYED_CANVAS_SIGN_SPRITES = + Arrays.stream(DyeColor.values()).collect(Collectors.toMap(Function.identity(), ModAtlases::createCanvasSignSprite)); + public static final Map DYED_HANGING_CANVAS_SIGN_SPRITES = + Arrays.stream(DyeColor.values()).collect(Collectors.toMap(Function.identity(), ModAtlases::createHangingCanvasSignSprite)); + + public static SpriteId createCanvasSignSprite(DyeColor dyeType) { + return CANVAS_SIGN_MAPPER.apply(Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "canvas_" + dyeType.getName())); } - public static Material createHangingCanvasSignMaterial(DyeColor dyeType) { - return new Material(Sheets.SIGN_SHEET, Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "entity/signs/hanging/canvas_" + dyeType.getName())); + public static SpriteId createHangingCanvasSignSprite(DyeColor dyeType) { + return HANGING_SIGN_MAPPER.apply(Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "canvas_" + dyeType.getName())); } - public static Material getCanvasSignMaterial(@Nullable DyeColor dyeColor) { - return dyeColor != null ? ModAtlases.DYED_CANVAS_SIGN_MATERIALS.get(dyeColor) : ModAtlases.BLANK_CANVAS_SIGN_MATERIAL; + public static SpriteId getCanvasSignSprite(@Nullable DyeColor dyeColor) { + return dyeColor != null ? ModAtlases.DYED_CANVAS_SIGN_SPRITES.get(dyeColor) : ModAtlases.BLANK_CANVAS_SIGN_SPRITE; } - public static Material getHangingCanvasSignMaterial(@Nullable DyeColor dyeColor) { - return dyeColor != null ? ModAtlases.DYED_HANGING_CANVAS_SIGN_MATERIALS.get(dyeColor) : ModAtlases.BLANK_HANGING_CANVAS_SIGN_MATERIAL; + public static SpriteId getHangingCanvasSignSprite(@Nullable DyeColor dyeColor) { + return dyeColor != null ? ModAtlases.DYED_HANGING_CANVAS_SIGN_SPRITES.get(dyeColor) : ModAtlases.BLANK_HANGING_CANVAS_SIGN_SPRITE; } } From e871ed89a92a25a3db5c2db270cdf5aff0538a0c Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Thu, 4 Jun 2026 13:29:20 +0100 Subject: [PATCH 05/25] Fix farmland mixin --- .../common/mixin/KeepRichSoilUntrampledMixin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/common/mixin/KeepRichSoilUntrampledMixin.java b/src/main/java/vectorwing/farmersdelight/common/mixin/KeepRichSoilUntrampledMixin.java index 9f0ca885c..b56f6d27d 100644 --- a/src/main/java/vectorwing/farmersdelight/common/mixin/KeepRichSoilUntrampledMixin.java +++ b/src/main/java/vectorwing/farmersdelight/common/mixin/KeepRichSoilUntrampledMixin.java @@ -3,7 +3,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.FarmBlock; +import net.minecraft.world.level.block.FarmlandBlock; import net.minecraft.world.level.block.state.BlockState; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -11,7 +11,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import vectorwing.farmersdelight.common.block.RichSoilFarmlandBlock; -@Mixin(FarmBlock.class) +@Mixin(FarmlandBlock.class) public class KeepRichSoilUntrampledMixin { @Inject(at = @At(value = "HEAD"), method = "turnToDirt", cancellable = true) private static void turnToDirt(Entity entity, BlockState state, Level level, BlockPos pos, CallbackInfo ci) { From d3a1c11d09b231d20c022e17ac1396803c2455bf Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:54:39 +0100 Subject: [PATCH 06/25] Sign editing compiles Hanging signs should be fine but I think standing signs are broken still. --- .../client/gui/CanvasSignEditScreen.java | 27 +++++++++---------- .../gui/HangingCanvasSignEditScreen.java | 23 +++++++--------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/gui/CanvasSignEditScreen.java b/src/main/java/vectorwing/farmersdelight/client/gui/CanvasSignEditScreen.java index c075ca35c..5439a973f 100644 --- a/src/main/java/vectorwing/farmersdelight/client/gui/CanvasSignEditScreen.java +++ b/src/main/java/vectorwing/farmersdelight/client/gui/CanvasSignEditScreen.java @@ -4,22 +4,27 @@ import com.mojang.math.Axis; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.screens.inventory.SignEditScreen; +import net.minecraft.client.model.Model; +import net.minecraft.client.renderer.blockentity.StandingSignRenderer; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.resources.model.sprite.Material; import net.minecraft.world.item.DyeColor; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.PlainSignBlock; import net.minecraft.world.level.block.StandingSignBlock; import net.minecraft.world.level.block.entity.SignBlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.WoodType; import vectorwing.farmersdelight.common.block.state.CanvasSign; import vectorwing.farmersdelight.common.registry.ModAtlases; import javax.annotation.Nullable; +// TODO i'm pretty sure this doesn't display the right graphic. For now, it compiles public class CanvasSignEditScreen extends SignEditScreen { @Nullable - protected SignRenderer.SignModel signModel; + protected Model.Simple signModel; @Nullable protected DyeColor dye; protected final boolean isFrontText; @@ -36,22 +41,16 @@ public CanvasSignEditScreen(SignBlockEntity signBlockEntity, boolean isFront, bo @Override protected void init() { super.init(); - this.signModel = SignRenderer.createSignModel(this.minecraft.getEntityModels(), this.woodType); + PlainSignBlock.Attachment attachment = PlainSignBlock.getAttachmentPoint(this.sign.getBlockState()); + this.signModel = StandingSignRenderer.createSignModel(this.minecraft.getEntityModels(), WoodType.SPRUCE, attachment); } - @Override - protected void renderSignBackground(GuiGraphicsExtractor guiGraphics, BlockState state) { + protected void extractSignBackground(GuiGraphicsExtractor graphics) { if (this.signModel != null) { - boolean flag = state.getBlock() instanceof StandingSignBlock; - guiGraphics.pose().translate(0.0F, 31.0F, 0.0F); - if (!isFrontText) { - guiGraphics.pose().mulPose(Axis.YP.rotationDegrees(180)); - } - guiGraphics.pose().scale(MAGIC_SCALE_NUMBER, MAGIC_SCALE_NUMBER, -MAGIC_SCALE_NUMBER); - Material material = ModAtlases.getCanvasSignMaterial(dye); - VertexConsumer vertexconsumer = material.buffer(guiGraphics.bufferSource(), this.signModel::renderType); - this.signModel.stick.visible = flag; - this.signModel.root.render(guiGraphics.pose(), vertexconsumer, 15728880, OverlayTexture.NO_OVERLAY); + int centerX = this.width / 2; + int x0 = centerX - 48; + int x1 = centerX + 48; + graphics.sign(this.signModel, MAGIC_SCALE_NUMBER, this.woodType, x0, 66, x1, 168); } } } diff --git a/src/main/java/vectorwing/farmersdelight/client/gui/HangingCanvasSignEditScreen.java b/src/main/java/vectorwing/farmersdelight/client/gui/HangingCanvasSignEditScreen.java index aeb3dd7ad..8e7f874f2 100644 --- a/src/main/java/vectorwing/farmersdelight/client/gui/HangingCanvasSignEditScreen.java +++ b/src/main/java/vectorwing/farmersdelight/client/gui/HangingCanvasSignEditScreen.java @@ -1,18 +1,18 @@ package vectorwing.farmersdelight.client.gui; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.inventory.AbstractSignEditScreen; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.screens.inventory.HangingSignEditScreen; +import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; import net.minecraft.world.item.DyeColor; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.SignBlockEntity; -import net.minecraft.world.level.block.state.BlockState; import org.joml.Vector3f; import vectorwing.farmersdelight.FarmersDelight; import vectorwing.farmersdelight.common.block.state.CanvasSign; -public class HangingCanvasSignEditScreen extends AbstractSignEditScreen +public class HangingCanvasSignEditScreen extends HangingSignEditScreen { private static final Vector3f TEXT_SCALE = new Vector3f(0.9F, 0.9F, 0.9F); @@ -20,7 +20,7 @@ public class HangingCanvasSignEditScreen extends AbstractSignEditScreen private final Identifier texture; public HangingCanvasSignEditScreen(SignBlockEntity signBlockEntity, boolean isFrontText, boolean isTextFilteringEnabled) { - super(signBlockEntity, isFrontText, isTextFilteringEnabled, Component.translatable("hanging_sign.edit")); + super(signBlockEntity, isFrontText, isTextFilteringEnabled); Block block = signBlockEntity.getBlockState().getBlock(); if (block instanceof CanvasSign canvasSign) { this.dye = canvasSign.getBackgroundColor(); @@ -30,15 +30,10 @@ public HangingCanvasSignEditScreen(SignBlockEntity signBlockEntity, boolean isFr } @Override - protected void offsetSign(GuiGraphics gui, BlockState state) { - gui.pose().translate((float) this.width / 2.0F, 125.0F, 50.0F); - } - - @Override - protected void renderSignBackground(GuiGraphics gui, BlockState p_250054_) { - gui.pose().translate(0.0F, -13.0F, 0.0F); - gui.pose().scale(4.5F, 4.5F, 1.0F); - gui.blit(this.texture, -8, -8, 0.0F, 0.0F, 16, 16, 16, 16); + protected void extractSignBackground(GuiGraphicsExtractor graphics) { + graphics.pose().translate(0.0F, -13.0F); + graphics.pose().scale(4.5F, 4.5F); + graphics.blit(RenderPipelines.GUI_TEXTURED, this.texture, -8, -8, 0.0F, 0.0F, 16, 16, 16, 16); } @Override From 44b08b20053426399ccdb89784129b63879b6c97 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:05:08 +0100 Subject: [PATCH 07/25] Properly send FlipSkilletPayload packet --- .../farmersdelight/client/event/KeybindEvents.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/event/KeybindEvents.java b/src/main/java/vectorwing/farmersdelight/client/event/KeybindEvents.java index c8dbfa311..262898457 100644 --- a/src/main/java/vectorwing/farmersdelight/client/event/KeybindEvents.java +++ b/src/main/java/vectorwing/farmersdelight/client/event/KeybindEvents.java @@ -1,18 +1,20 @@ package vectorwing.farmersdelight.client.event; import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.client.event.ClientTickEvent; -import net.neoforged.neoforge.network.PacketDistributor; import vectorwing.farmersdelight.FarmersDelight; import vectorwing.farmersdelight.common.item.SkilletItem; import vectorwing.farmersdelight.common.network.payload.FlipSkilletPayload; import vectorwing.farmersdelight.common.registry.ModDataComponents; +import java.util.Objects; + @EventBusSubscriber(modid = FarmersDelight.MODID, value = Dist.CLIENT) public class KeybindEvents { @@ -24,7 +26,8 @@ public static void preClientTick(ClientTickEvent.Pre event) { // Run this on pre ItemStack useItem = player.getUseItem(); if (useItem.getItem() instanceof SkilletItem && !useItem.has(ModDataComponents.SKILLET_FLIP_TIMESTAMP.get())) { while (mc.options.keyAttack.consumeClick()) { - PacketDistributor.sendToServer(FlipSkilletPayload.INSTANCE); + ClientPacketListener listener = Objects.requireNonNull(Minecraft.getInstance().getConnection()); + listener.send(FlipSkilletPayload.INSTANCE); } } } From afb4506035cc4c7d6b31b1fcf2be1d697599c547 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:27:33 +0100 Subject: [PATCH 08/25] Fix SkilletRenderer --- .../client/renderer/SkilletRenderer.java | 70 ++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletRenderer.java b/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletRenderer.java index 0a0d27e74..e86c94f41 100644 --- a/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletRenderer.java +++ b/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletRenderer.java @@ -3,40 +3,47 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Axis; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; +import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import net.minecraft.client.renderer.item.ItemModelResolver; +import net.minecraft.client.renderer.item.ItemStackRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.core.Direction; import net.minecraft.util.Mth; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; -import net.neoforged.neoforge.items.IItemHandler; +import net.minecraft.world.phys.Vec3; +import org.jspecify.annotations.Nullable; import vectorwing.farmersdelight.common.block.StoveBlock; import vectorwing.farmersdelight.common.block.entity.SkilletBlockEntity; import java.util.Random; -public class SkilletRenderer implements BlockEntityRenderer +public class SkilletRenderer implements BlockEntityRenderer { private final Random random = new Random(); + private final ItemModelResolver itemModelResolver; public SkilletRenderer(BlockEntityRendererProvider.Context context) { + itemModelResolver = context.itemModelResolver(); } @Override - public void render(SkilletBlockEntity skillet, float partialTicks, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) { - Direction direction = skillet.getBlockState().getValue(StoveBlock.FACING); - IItemHandler inventory = skillet.getInventory(); - int posLong = (int) skillet.getBlockPos().asLong(); - - ItemStack stack = inventory.getStackInSlot(0); - int seed = stack.isEmpty() ? 187 : Item.getId(stack.getItem()) + stack.getDamageValue(); - this.random.setSeed(seed); - - if (!stack.isEmpty()) { - int itemRenderCount = this.getModelCount(stack); - for (int i = 0; i < itemRenderCount; i++) { + public SkilletRenderState createRenderState() { + return new SkilletRenderState(); + } + + @Override + public void submit(SkilletRenderState state, PoseStack poseStack, SubmitNodeCollector collector, CameraRenderState cameraRenderState) { + this.random.setSeed(state.seed); + + if (!state.contents.isEmpty()) { + for (int i = 0; i < state.modelCount; i++) { poseStack.pushPose(); // Stack up items in the skillet, with a slight offset per item @@ -45,7 +52,7 @@ public void render(SkilletBlockEntity skillet, float partialTicks, PoseStack pos poseStack.translate(0.5D + xOffset, 0.1D + 0.03 * (i + 1), 0.5D + zOffset); // Rotate item to face the skillet's front side - float degrees = -direction.toYRot(); + float degrees = -state.direction.toYRot(); poseStack.mulPose(Axis.YP.rotationDegrees(degrees)); // Rotate item flat on the skillet. Use X and Y from now on @@ -54,8 +61,8 @@ public void render(SkilletBlockEntity skillet, float partialTicks, PoseStack pos // Resize the items poseStack.scale(0.5F, 0.5F, 0.5F); - if (skillet.getLevel() != null) - Minecraft.getInstance().getItemRenderer().renderStatic(stack, ItemDisplayContext.FIXED, packedLight, packedOverlay, poseStack, buffer, skillet.getLevel(), posLong); + state.contents.submit(poseStack, collector, state.lightCoords, OverlayTexture.NO_OVERLAY, 0); + poseStack.popPose(); } } @@ -70,4 +77,31 @@ protected int getModelCount(ItemStack stack) { return modelCount; } + + @Override + public void extractRenderState(SkilletBlockEntity skillet, SkilletRenderState state, float partialTicks, Vec3 cameraPosition, ModelFeatureRenderer.@Nullable CrumblingOverlay breakProgress) { + BlockEntityRenderer.super.extractRenderState(skillet, state, partialTicks, cameraPosition, breakProgress); + state.direction = skillet.getBlockState().getValue(StoveBlock.FACING); + + ItemStack inventory = skillet.getInventory().getStackInSlot(0); + state.contents = new ItemStackRenderState(); + state.seed = state.contents.isEmpty() ? 187 : Item.getId(inventory.getItem()) + inventory.getDamageValue();; + this.itemModelResolver.updateForTopItem( + state.contents, + inventory, + ItemDisplayContext.FIXED, + skillet.getLevel(), + null, + state.seed + ); + + state.modelCount = getModelCount(inventory); + } + + public static class SkilletRenderState extends BlockEntityRenderState { + public Direction direction; + public ItemStackRenderState contents; + public int seed; + public int modelCount; + } } From 6d2140cb1453657decaf6db298103e7b3661c87c Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:32:04 +0100 Subject: [PATCH 09/25] Fixed cutting and cooking recipes --- .../common/crafting/CookingPotRecipe.java | 125 ++++++++-------- .../common/crafting/CuttingBoardRecipe.java | 134 ++++++++---------- .../common/registry/ModRecipeSerializers.java | 18 ++- 3 files changed, 136 insertions(+), 141 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/common/crafting/CookingPotRecipe.java b/src/main/java/vectorwing/farmersdelight/common/crafting/CookingPotRecipe.java index 2fcd8b505..6bed3dece 100644 --- a/src/main/java/vectorwing/farmersdelight/common/crafting/CookingPotRecipe.java +++ b/src/main/java/vectorwing/farmersdelight/common/crafting/CookingPotRecipe.java @@ -3,7 +3,6 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import net.minecraft.core.HolderLookup; import net.minecraft.core.NonNullList; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; @@ -11,17 +10,35 @@ import net.minecraft.world.item.crafting.*; import net.minecraft.world.level.Level; import net.neoforged.neoforge.common.util.RecipeMatcher; -import net.neoforged.neoforge.items.wrapper.RecipeWrapper; import vectorwing.farmersdelight.client.recipebook.CookingPotRecipeBookTab; -import vectorwing.farmersdelight.common.registry.ModItems; import vectorwing.farmersdelight.common.registry.ModRecipeSerializers; import vectorwing.farmersdelight.common.registry.ModRecipeTypes; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; import java.util.Optional; public class CookingPotRecipe implements Recipe { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group( + Codec.STRING.optionalFieldOf("group", "").forGetter(CookingPotRecipe::group), + CookingPotRecipeBookTab.CODEC.optionalFieldOf("recipe_book_tab", CookingPotRecipeBookTab.MISC).forGetter(CookingPotRecipe::getRecipeBookTab), + Ingredient.CODEC.listOf(1, CookingPotRecipe.INPUT_SLOTS).fieldOf("ingredients").xmap(ingredients -> { + NonNullList nonNullList = NonNullList.create(); + nonNullList.addAll(ingredients); + return nonNullList; + }, ingredients -> ingredients).forGetter(CookingPotRecipe::getIngredients), + ItemStack.CODEC.validate(ItemStack::validateStrict).fieldOf("result").forGetter(r -> r.output), + ItemStack.CODEC.validate(ItemStack::validateStrict).optionalFieldOf("container", ItemStack.EMPTY).forGetter(CookingPotRecipe::getContainerOverride), + Codec.FLOAT.optionalFieldOf("experience", 0.0F).forGetter(CookingPotRecipe::getExperience), + Codec.INT.optionalFieldOf("cookingtime", 200).forGetter(CookingPotRecipe::getCookTime) + ).apply(inst, CookingPotRecipe::new)); + + public static final StreamCodec STREAM_CODEC = + StreamCodec.of(CodecHelpers::toNetwork, CodecHelpers::fromNetwork); + public static final int INPUT_SLOTS = 6; private final String group; @@ -33,6 +50,8 @@ public class CookingPotRecipe implements Recipe private final float experience; private final int cookTime; + private PlacementInfo placementInfo; + public CookingPotRecipe(String group, @Nullable CookingPotRecipeBookTab tab, NonNullList inputItems, ItemStack output, ItemStack container, float experience, int cookTime) { this.group = group; this.tab = tab; @@ -41,8 +60,8 @@ public CookingPotRecipe(String group, @Nullable CookingPotRecipeBookTab tab, Non if (!container.isEmpty()) { this.container = container; - } else if (!output.getCraftingRemainingItem().isEmpty()) { - this.container = output.getCraftingRemainingItem(); + } else if (output.getCraftingRemainder() != null && output.getCraftingRemainder().count() != 0) { + this.container = output.getCraftingRemainder().create(); } else { this.container = ItemStack.EMPTY; } @@ -50,11 +69,22 @@ public CookingPotRecipe(String group, @Nullable CookingPotRecipeBookTab tab, Non this.containerOverride = container; this.experience = experience; this.cookTime = cookTime; - } - @Override - public String getGroup() { - return this.group; + Optional emptyIngredient = Optional.of(Ingredient.of()); + LinkedList> placementIngredients = new LinkedList<>(); + // Add the actual ingredients + for (Ingredient ingredient : inputItems) { + placementIngredients.add(Optional.of(ingredient)); + } + // Fill the remaining empty slots + for (int i = 0; i < INPUT_SLOTS - inputItems.size(); i++) { + placementIngredients.add(emptyIngredient); + } + placementIngredients.add(Optional.empty()); // Meal display + placementIngredients.add(this.container == ItemStack.EMPTY ? emptyIngredient : Optional.of(Ingredient.of(this.container.getItem()))); // Container + placementIngredients.add(Optional.empty()); // Output + + this.placementInfo = PlacementInfo.createFromOptionals(placementIngredients); } @Nullable @@ -62,16 +92,10 @@ public CookingPotRecipeBookTab getRecipeBookTab() { return this.tab; } - @Override public NonNullList getIngredients() { return this.inputItems; } - @Override - public ItemStack getResultItem(HolderLookup.Provider provider) { - return this.output; - } - public ItemStack getOutputContainer() { return this.container; } @@ -80,11 +104,6 @@ public ItemStack getContainerOverride() { return this.containerOverride; } - @Override - public ItemStack assemble(RecipeWrapper inv, HolderLookup.Provider provider) { - return this.output.copy(); - } - public float getExperience() { return this.experience; } @@ -94,7 +113,7 @@ public int getCookTime() { } @Override - public boolean matches(RecipeWrapper inv, Level level) { + public boolean matches(RecipeInput inv, Level level) { java.util.List inputs = new java.util.ArrayList<>(); int i = 0; @@ -109,23 +128,38 @@ public boolean matches(RecipeWrapper inv, Level level) { } @Override - public boolean canCraftInDimensions(int width, int height) { - return width * height >= this.inputItems.size(); + public ItemStack assemble(RecipeInput recipeInput) { + return this.output.copy(); } @Override - public RecipeSerializer getSerializer() { + public boolean showNotification() { + return false; + } + + @Override + public String group() { + return this.group; + } + + @Override + public RecipeSerializer> getSerializer() { return ModRecipeSerializers.COOKING.get(); } @Override - public RecipeType getType() { + public RecipeType> getType() { return ModRecipeTypes.COOKING.get(); } @Override - public ItemStack getToastSymbol() { - return new ItemStack(ModItems.COOKING_POT.get()); + public PlacementInfo placementInfo() { + return placementInfo; + } + + @Override + public RecipeBookCategory recipeBookCategory() { + return RecipeBookCategories.CRAFTING_MISC; // Never read for our purposes, AFAIK? } @Override @@ -137,7 +171,7 @@ public boolean equals(Object o) { if (Float.compare(that.getExperience(), getExperience()) != 0) return false; if (getCookTime() != that.getCookTime()) return false; - if (!getGroup().equals(that.getGroup())) return false; + if (!group().equals(that.group())) return false; if (tab != that.tab) return false; if (!inputItems.equals(that.inputItems)) return false; if (!output.equals(that.output)) return false; @@ -146,7 +180,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = getGroup().hashCode(); + int result = group().hashCode(); result = 31 * result + (getRecipeBookTab() != null ? getRecipeBookTab().hashCode() : 0); result = 31 * result + inputItems.hashCode(); result = 31 * result + output.hashCode(); @@ -156,42 +190,13 @@ public int hashCode() { return result; } - public static class Serializer implements RecipeSerializer + private static class CodecHelpers { - private static final MapCodec CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group( - Codec.STRING.optionalFieldOf("group", "").forGetter(CookingPotRecipe::getGroup), - CookingPotRecipeBookTab.CODEC.optionalFieldOf("recipe_book_tab", CookingPotRecipeBookTab.MISC).forGetter(CookingPotRecipe::getRecipeBookTab), - Ingredient.LIST_CODEC_NONEMPTY.fieldOf("ingredients").xmap(ingredients -> { - NonNullList nonNullList = NonNullList.create(); - nonNullList.addAll(ingredients); - return nonNullList; - }, ingredients -> ingredients).forGetter(CookingPotRecipe::getIngredients), - ItemStack.STRICT_CODEC.fieldOf("result").forGetter(r -> r.output), - ItemStack.STRICT_CODEC.optionalFieldOf("container", ItemStack.EMPTY).forGetter(CookingPotRecipe::getContainerOverride), - Codec.FLOAT.optionalFieldOf("experience", 0.0F).forGetter(CookingPotRecipe::getExperience), - Codec.INT.optionalFieldOf("cookingtime", 200).forGetter(CookingPotRecipe::getCookTime) - ).apply(inst, CookingPotRecipe::new)); - - public static final StreamCodec STREAM_CODEC = StreamCodec.of(CookingPotRecipe.Serializer::toNetwork, CookingPotRecipe.Serializer::fromNetwork); - - public Serializer() { - } - - @Override - public MapCodec codec() { - return CODEC; - } - - @Override - public StreamCodec streamCodec() { - return STREAM_CODEC; - } - private static CookingPotRecipe fromNetwork(RegistryFriendlyByteBuf buffer) { String group = buffer.readUtf(); CookingPotRecipeBookTab tab = CookingPotRecipeBookTab.findByName(buffer.readUtf()); int i = buffer.readVarInt(); - NonNullList inputItems = NonNullList.withSize(i, Ingredient.EMPTY); + NonNullList inputItems = NonNullList.withSize(i, Ingredient.of()); inputItems.replaceAll(ignored -> Ingredient.CONTENTS_STREAM_CODEC.decode(buffer)); diff --git a/src/main/java/vectorwing/farmersdelight/common/crafting/CuttingBoardRecipe.java b/src/main/java/vectorwing/farmersdelight/common/crafting/CuttingBoardRecipe.java index 214d771db..c6970d9ef 100644 --- a/src/main/java/vectorwing/farmersdelight/common/crafting/CuttingBoardRecipe.java +++ b/src/main/java/vectorwing/farmersdelight/common/crafting/CuttingBoardRecipe.java @@ -5,7 +5,6 @@ import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.Holder; -import net.minecraft.core.HolderLookup; import net.minecraft.core.NonNullList; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; @@ -15,12 +14,8 @@ import net.minecraft.sounds.SoundEvent; import net.minecraft.util.RandomSource; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.Ingredient; -import net.minecraft.world.item.crafting.Recipe; -import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.item.crafting.*; import net.minecraft.world.level.Level; -import net.neoforged.neoforge.items.wrapper.RecipeWrapper; import vectorwing.farmersdelight.common.crafting.ingredient.ChanceResult; import vectorwing.farmersdelight.common.registry.ModRecipeSerializers; import vectorwing.farmersdelight.common.registry.ModRecipeTypes; @@ -33,6 +28,43 @@ public class CuttingBoardRecipe implements Recipe { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( + inst -> inst.group(Codec.STRING.optionalFieldOf("group", "").forGetter(CuttingBoardRecipe::group), + Ingredient.CODEC.listOf(1, 1).fieldOf("ingredients").flatXmap(ingredients -> { + if (ingredients.isEmpty()) { + return DataResult.error(() -> "No ingredients for cutting recipe"); + } + if (ingredients.size() > 1) { + return DataResult.error( + () -> "Too many ingredients for cutting recipe! Please define only one ingredient"); + } + NonNullList nonNullList = NonNullList.create(); + nonNullList.add(ingredients.get(0)); + return DataResult.success(ingredients.get(0)); + }, ingredient -> { + NonNullList nonNullList = NonNullList.create(); + nonNullList.add(ingredient); + return DataResult.success(nonNullList); + }).forGetter(cuttingBoardRecipe -> cuttingBoardRecipe.input), + Ingredient.CODEC.fieldOf("tool").forGetter(CuttingBoardRecipe::getTool), + Codec.list(ChanceResult.CODEC).fieldOf("result").flatXmap(chanceResults -> { + if (chanceResults.size() > 4) { + return DataResult.error( + () -> "Too many results for cutting recipe! The maximum quantity of unique results is " + + CuttingBoardRecipe.MAX_RESULTS); + } + NonNullList nonNullList = NonNullList.create(); + nonNullList.addAll(chanceResults); + return DataResult.success(nonNullList); + }, DataResult::success).forGetter(CuttingBoardRecipe::getRollableResults), + SoundEvent.DIRECT_CODEC.optionalFieldOf("sound").forGetter(CuttingBoardRecipe::getSoundEvent)) + .apply(inst, CuttingBoardRecipe::new)); + + public static final StreamCodec STREAM_CODEC = + StreamCodec.of(CodecHelpers::toNetwork, CodecHelpers::fromNetwork); + + public static final RecipeSerializer SERIALIZER = new RecipeSerializer<>(CODEC, STREAM_CODEC); + public static final int MAX_RESULTS = 4; private final String group; @@ -55,7 +87,7 @@ public boolean matches(CuttingBoardRecipeInput input, Level level) { } @Override - public ItemStack assemble(CuttingBoardRecipeInput inv, HolderLookup.Provider provider) { + public ItemStack assemble(CuttingBoardRecipeInput cuttingBoardRecipeInput) { return this.results.getFirst().stack().copy(); } @@ -65,11 +97,15 @@ public boolean isSpecial() { } @Override - public String getGroup() { - return this.group; + public boolean showNotification() { + return false; } @Override + public String group() { + return this.group; + } + public NonNullList getIngredients() { NonNullList nonnulllist = NonNullList.create(); nonnulllist.add(this.input); @@ -80,11 +116,6 @@ public Ingredient getTool() { return this.tool; } - @Override - public ItemStack getResultItem(HolderLookup.Provider provider) { - return this.results.getFirst().stack(); - } - public List getResults() { return getRollableResults().stream() .map(ChanceResult::stack) @@ -95,7 +126,7 @@ public NonNullList getRollableResults() { return this.results; } - public List rollResults(RandomSource random, int fortuneLevel, RecipeWrapper inventory) { + public List rollResults(RandomSource random, int fortuneLevel, RecipeInput inventory) { List results = new ArrayList<>(); NonNullList rollableResults = getRollableResults(); for (ChanceResult output : rollableResults) { @@ -110,23 +141,24 @@ public Optional getSoundEvent() { return this.soundEvent; } - protected int getMaxInputCount() { - return 1; + @Override + public RecipeSerializer> getSerializer() { + return ModRecipeSerializers.CUTTING.get(); } @Override - public boolean canCraftInDimensions(int width, int height) { - return width * height >= this.getMaxInputCount(); + public RecipeType> getType() { + return ModRecipeTypes.CUTTING.get(); } @Override - public RecipeSerializer getSerializer() { - return ModRecipeSerializers.CUTTING.get(); + public PlacementInfo placementInfo() { + return PlacementInfo.NOT_PLACEABLE; // Never relevant for us? } @Override - public RecipeType getType() { - return ModRecipeTypes.CUTTING.get(); + public RecipeBookCategory recipeBookCategory() { + return RecipeBookCategories.CRAFTING_MISC; // Never read for our purposes, AFAIK? } @Override @@ -136,7 +168,7 @@ public boolean equals(Object o) { CuttingBoardRecipe that = (CuttingBoardRecipe) o; - if (!getGroup().equals(that.getGroup())) return false; + if (!group().equals(that.group())) return false; if (!input.equals(that.input)) return false; if (!getTool().equals(that.getTool())) return false; if (!getResults().equals(that.getResults())) return false; @@ -145,7 +177,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = (getGroup() != null ? getGroup().hashCode() : 0); + int result = (group() != null ? group().hashCode() : 0); result = 31 * result + input.hashCode(); result = 31 * result + getTool().hashCode(); result = 31 * result + getResults().hashCode(); @@ -153,46 +185,8 @@ public int hashCode() { return result; } - public static class Serializer implements RecipeSerializer + private static class CodecHelpers { - public static final StreamCodec STREAM_CODEC = - StreamCodec.of(CuttingBoardRecipe.Serializer::toNetwork, CuttingBoardRecipe.Serializer::fromNetwork); - - private static final MapCodec CODEC = RecordCodecBuilder.mapCodec( - inst -> inst.group(Codec.STRING.optionalFieldOf("group", "").forGetter(CuttingBoardRecipe::getGroup), - Ingredient.LIST_CODEC_NONEMPTY.fieldOf("ingredients").flatXmap(ingredients -> { - if (ingredients.isEmpty()) { - return DataResult.error(() -> "No ingredients for cutting recipe"); - } - if (ingredients.size() > 1) { - return DataResult.error( - () -> "Too many ingredients for cutting recipe! Please define only one ingredient"); - } - NonNullList nonNullList = NonNullList.create(); - nonNullList.add(ingredients.get(0)); - return DataResult.success(ingredients.get(0)); - }, ingredient -> { - NonNullList nonNullList = NonNullList.create(); - nonNullList.add(ingredient); - return DataResult.success(nonNullList); - }).forGetter(cuttingBoardRecipe -> cuttingBoardRecipe.input), - Ingredient.CODEC.fieldOf("tool").forGetter(CuttingBoardRecipe::getTool), - Codec.list(ChanceResult.CODEC).fieldOf("result").flatXmap(chanceResults -> { - if (chanceResults.size() > 4) { - return DataResult.error( - () -> "Too many results for cutting recipe! The maximum quantity of unique results is " - + MAX_RESULTS); - } - NonNullList nonNullList = NonNullList.create(); - nonNullList.addAll(chanceResults); - return DataResult.success(nonNullList); - }, DataResult::success).forGetter(CuttingBoardRecipe::getRollableResults), - SoundEvent.DIRECT_CODEC.optionalFieldOf("sound").forGetter(CuttingBoardRecipe::getSoundEvent)) - .apply(inst, CuttingBoardRecipe::new)); - - public Serializer() { - } - public static CuttingBoardRecipe fromNetwork(RegistryFriendlyByteBuf buffer) { String group = buffer.readUtf(32767); Ingredient inputItem = Ingredient.CONTENTS_STREAM_CODEC.decode(buffer); @@ -203,7 +197,7 @@ public static CuttingBoardRecipe fromNetwork(RegistryFriendlyByteBuf buffer) { results.replaceAll(ignored -> ChanceResult.read(buffer)); Optional soundEvent = Optional.empty(); if (buffer.readBoolean()) { - Optional> holder = BuiltInRegistries.SOUND_EVENT.getHolder(buffer.readResourceKey(Registries.SOUND_EVENT)); + Optional> holder = BuiltInRegistries.SOUND_EVENT.get(buffer.readResourceKey(Registries.SOUND_EVENT)); if (holder.isPresent() && holder.get().isBound()) { soundEvent = Optional.of(holder.get().value()); } @@ -230,15 +224,5 @@ public static void toNetwork(RegistryFriendlyByteBuf buffer, CuttingBoardRecipe buffer.writeBoolean(false); } } - - @Override - public MapCodec codec() { - return CODEC; - } - - @Override - public StreamCodec streamCodec() { - return STREAM_CODEC; - } } } diff --git a/src/main/java/vectorwing/farmersdelight/common/registry/ModRecipeSerializers.java b/src/main/java/vectorwing/farmersdelight/common/registry/ModRecipeSerializers.java index 428caad43..06e2ca50a 100644 --- a/src/main/java/vectorwing/farmersdelight/common/registry/ModRecipeSerializers.java +++ b/src/main/java/vectorwing/farmersdelight/common/registry/ModRecipeSerializers.java @@ -1,13 +1,15 @@ package vectorwing.farmersdelight.common.registry; +import com.mojang.serialization.MapCodec; import net.minecraft.core.registries.Registries; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeInput; import net.minecraft.world.item.crafting.RecipeSerializer; import net.neoforged.neoforge.registries.DeferredRegister; import vectorwing.farmersdelight.FarmersDelight; -import vectorwing.farmersdelight.common.crafting.CookingPotRecipe; -import vectorwing.farmersdelight.common.crafting.CuttingBoardRecipe; -import vectorwing.farmersdelight.common.crafting.DoughRecipe; -import vectorwing.farmersdelight.common.crafting.FoodServingRecipe; +import vectorwing.farmersdelight.common.crafting.*; import java.util.function.Supplier; @@ -15,11 +17,15 @@ public class ModRecipeSerializers { public static final DeferredRegister> RECIPE_SERIALIZERS = DeferredRegister.create(Registries.RECIPE_SERIALIZER, FarmersDelight.MODID); - public static final Supplier> COOKING = RECIPE_SERIALIZERS.register("cooking", CookingPotRecipe.Serializer::new); - public static final Supplier> CUTTING = RECIPE_SERIALIZERS.register("cutting", CuttingBoardRecipe.Serializer::new); + public static final Supplier>> COOKING = RECIPE_SERIALIZERS.register("cooking", fromCodecs(CookingPotRecipe.CODEC, CookingPotRecipe.STREAM_CODEC)); + public static final Supplier>> CUTTING = RECIPE_SERIALIZERS.register("cutting", fromCodecs(CuttingBoardRecipe.CODEC, CuttingBoardRecipe.STREAM_CODEC)); public static final Supplier> FOOD_SERVING = RECIPE_SERIALIZERS.register("food_serving", () -> FoodServingRecipe.SERIALIZER); public static final Supplier> DOUGH = RECIPE_SERIALIZERS.register("dough", () -> DoughRecipe.SERIALIZER); + + private static > Supplier> fromCodecs(MapCodec codec, StreamCodec streamCodec) { + return () -> new RecipeSerializer(codec, streamCodec); + } } From e70ffc9ab102c354b42bc87db8798823899876fb Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Fri, 5 Jun 2026 16:40:26 +0100 Subject: [PATCH 10/25] Stove and cutting board renderers --- .../client/renderer/CuttingBoardRenderer.java | 125 +++++++++++++----- .../client/renderer/DefaultStoveRenderer.java | 82 +++++++++--- 2 files changed, 153 insertions(+), 54 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/renderer/CuttingBoardRenderer.java b/src/main/java/vectorwing/farmersdelight/client/renderer/CuttingBoardRenderer.java index f6193803b..453bb67c4 100644 --- a/src/main/java/vectorwing/farmersdelight/client/renderer/CuttingBoardRenderer.java +++ b/src/main/java/vectorwing/farmersdelight/client/renderer/CuttingBoardRenderer.java @@ -2,71 +2,74 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Axis; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; -import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import net.minecraft.client.renderer.item.ItemModelResolver; +import net.minecraft.client.renderer.item.ItemStackRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.core.Direction; import net.minecraft.util.Mth; import net.minecraft.world.item.*; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.Vec3; +import org.jspecify.annotations.Nullable; import vectorwing.farmersdelight.common.block.CuttingBoardBlock; import vectorwing.farmersdelight.common.block.entity.CuttingBoardBlockEntity; import vectorwing.farmersdelight.common.tag.ModTags; import java.util.Random; -public class CuttingBoardRenderer implements BlockEntityRenderer +public class CuttingBoardRenderer implements BlockEntityRenderer { private final Random random = new Random(); + private final ItemModelResolver itemModelResolver; public CuttingBoardRenderer(BlockEntityRendererProvider.Context context) { + this.itemModelResolver = context.itemModelResolver(); } @Override - public void render(CuttingBoardBlockEntity cuttingBoard, float partialTicks, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) { - ItemStack itemStack = cuttingBoard.getStoredItem(); - if (itemStack.isEmpty()) { + public CuttingBoardRenderState createRenderState() { + return new CuttingBoardRenderState(); + } + + @Override + public void submit(CuttingBoardRenderState state, PoseStack poseStack, SubmitNodeCollector collector, CameraRenderState cameraRenderState) { + if (state.itemRenderState == null) { return; } - Direction direction = cuttingBoard.getBlockState().getValue(CuttingBoardBlock.FACING).getOpposite(); - int posLong = (int) cuttingBoard.getBlockPos().asLong(); - int seed = itemStack.isEmpty() ? 187 : Item.getId(itemStack.getItem()) + itemStack.getDamageValue(); - this.random.setSeed(seed); + this.random.setSeed(state.seed); - ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); - int itemRenderCount = this.getModelCount(itemStack); - - for (int i = 0; i < itemRenderCount; i++) { + for (int i = 0; i < state.modelCount; i++) { poseStack.pushPose(); - poseStack.pushPose(); - boolean isBlockItem = itemRenderer.getModel(itemStack, cuttingBoard.getLevel(), null, 0).applyTransform(ItemDisplayContext.FIXED, poseStack, false).isGui3d(); - poseStack.popPose(); - - float xOffset = itemRenderCount == 1 ? 0 : (this.random.nextFloat() * 2.0F - 1.0F) * 0.15F * 0.5F; - float zOffset = itemRenderCount == 1 ? 0 : (this.random.nextFloat() * 2.0F - 1.0F) * 0.15F * 0.5F; + float xOffset = state.modelCount == 1 ? 0 : (this.random.nextFloat() * 2.0F - 1.0F) * 0.15F * 0.5F; + float zOffset = state.modelCount == 1 ? 0 : (this.random.nextFloat() * 2.0F - 1.0F) * 0.15F * 0.5F; - if (cuttingBoard.isItemCarvingBoard()) { - renderItemCarved(poseStack, direction, itemStack); - } else if (isBlockItem && !itemStack.is(ModTags.Items.FLAT_ON_CUTTING_BOARD)) { - renderBlock(poseStack, direction, xOffset, i, zOffset); + if (state.carvesBoard) { + renderItemCarved(poseStack, state); + } else if (state.isBlockItem && !state.rendersFlat) { + renderBlock(poseStack, state, xOffset, i, zOffset); } else { - renderItemLayingDown(poseStack, direction, xOffset, i, zOffset); + renderItemLayingDown(poseStack, state, xOffset, i, zOffset); } - Minecraft.getInstance().getItemRenderer().renderStatic(itemStack, ItemDisplayContext.FIXED, packedLight, packedOverlay, poseStack, buffer, cuttingBoard.getLevel(), posLong); + state.itemRenderState.submit(poseStack, collector, state.lightCoords, OverlayTexture.NO_OVERLAY, 0); poseStack.popPose(); } } - public void renderItemLayingDown(PoseStack matrixStackIn, Direction direction, float xOffset, int yIndex, float zOffset) { + public void renderItemLayingDown(PoseStack matrixStackIn, CuttingBoardRenderState state, float xOffset, int yIndex, float zOffset) { // Center item above the cutting board matrixStackIn.translate(0.5D + xOffset, 0.08D + 0.03 * (yIndex + 1), 0.5D + zOffset); // Rotate item to face the cutting board's front side - float f = -direction.toYRot(); + float f = -state.direction.toYRot(); matrixStackIn.mulPose(Axis.YP.rotationDegrees(f)); // Rotate item flat on the cutting board. Use X and Y from now on @@ -76,32 +79,31 @@ public void renderItemLayingDown(PoseStack matrixStackIn, Direction direction, f matrixStackIn.scale(0.6F, 0.6F, 0.6F); } - public void renderBlock(PoseStack matrixStackIn, Direction direction, float xOffset, int yIndex, float zOffset) { + public void renderBlock(PoseStack matrixStackIn, CuttingBoardRenderState state, float xOffset, int yIndex, float zOffset) { // Center block above the cutting board matrixStackIn.translate(0.5D + xOffset, 0.27D + 0.03 * (yIndex + 1), 0.5D + zOffset); // Rotate block to face the cutting board's front side - float f = -direction.toYRot(); + float f = -state.direction.toYRot(); matrixStackIn.mulPose(Axis.YP.rotationDegrees(f)); // Resize the block matrixStackIn.scale(0.8F, 0.8F, 0.8F); } - public void renderItemCarved(PoseStack matrixStackIn, Direction direction, ItemStack itemStack) { + public void renderItemCarved(PoseStack matrixStackIn, CuttingBoardRenderState state) { // Center item above the cutting board matrixStackIn.translate(0.5D, 0.23D, 0.5D); // Rotate item to face the cutting board's front side - float f = -direction.toYRot() + 180; + float f = -state.direction.toYRot() + 180; matrixStackIn.mulPose(Axis.YP.rotationDegrees(f)); // Rotate item to be carved on the surface, A little less so for hoes and pickaxes. - Item toolItem = itemStack.getItem(); float poseAngle; - if (toolItem instanceof PickaxeItem || toolItem instanceof HoeItem) { + if (state.isPickaxe || state.isHoe) { poseAngle = 225.0F; - } else if (toolItem instanceof TridentItem) { + } else if (state.isTrident) { poseAngle = 135.0F; } else { poseAngle = 180.0F; @@ -121,4 +123,55 @@ protected int getModelCount(ItemStack stack) { return modelCount; } + + @Override + public void extractRenderState(CuttingBoardBlockEntity cuttingBoard, CuttingBoardRenderState state, float partialTicks, Vec3 cameraPosition, ModelFeatureRenderer.@Nullable CrumblingOverlay breakProgress) { + BlockEntityRenderer.super.extractRenderState(cuttingBoard, state, partialTicks, cameraPosition, breakProgress); + state.direction = cuttingBoard.getBlockState().getValue(CuttingBoardBlock.FACING).getOpposite(); + + ItemStack stack = cuttingBoard.getStoredItem(); + if (stack.isEmpty()) { + state.itemRenderState = null; + } else { + state.itemRenderState = new ItemStackRenderState(); + this.itemModelResolver.updateForTopItem( + state.itemRenderState, + stack, + ItemDisplayContext.FIXED, + cuttingBoard.getLevel(), + null, + (int) cuttingBoard.getBlockPos().asLong() + ); + + + // TODO this is a very hacky check for pickaxes. + Item toolItem = stack.getItem(); + state.isPickaxe = toolItem.isCorrectToolForDrops(toolItem.getDefaultInstance(), Blocks.COBBLESTONE.defaultBlockState()); + state.isHoe = toolItem instanceof HoeItem; + state.isTrident = toolItem instanceof TridentItem; + + state.rendersFlat = stack.is(ModTags.Items.FLAT_ON_CUTTING_BOARD); + // TODO this will not catch every block. I'm not familiar enough with the new way of doing things + // to know if there's a better way to catch this. + state.isBlockItem = toolItem instanceof BlockItem; + } + + state.seed = stack.isEmpty() ? 187 : Item.getId(stack.getItem()) + stack.getDamageValue(); + state.modelCount = this.getModelCount(stack); + + state.carvesBoard = cuttingBoard.isItemCarvingBoard(); + } + + public static class CuttingBoardRenderState extends BlockEntityRenderState { + public Direction direction; + public ItemStackRenderState itemRenderState; + public int seed; + public int modelCount; + public boolean rendersFlat = false; + public boolean carvesBoard; + public boolean isPickaxe = false; + public boolean isHoe = false; + public boolean isTrident = false; + public boolean isBlockItem = false; + } } diff --git a/src/main/java/vectorwing/farmersdelight/client/renderer/DefaultStoveRenderer.java b/src/main/java/vectorwing/farmersdelight/client/renderer/DefaultStoveRenderer.java index 1e0920377..4172e0d5a 100644 --- a/src/main/java/vectorwing/farmersdelight/client/renderer/DefaultStoveRenderer.java +++ b/src/main/java/vectorwing/farmersdelight/client/renderer/DefaultStoveRenderer.java @@ -2,39 +2,44 @@ 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.SubmitNodeCollector; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; -import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import net.minecraft.client.renderer.item.ItemModelResolver; +import net.minecraft.client.renderer.item.ItemStackRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; 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 net.minecraft.world.phys.Vec3; +import org.jspecify.annotations.Nullable; import vectorwing.farmersdelight.common.block.StoveBlock; import vectorwing.farmersdelight.common.block.entity.AbstractStoveBlockEntity; -public class DefaultStoveRenderer implements BlockEntityRenderer +public class DefaultStoveRenderer implements BlockEntityRenderer { private static final float SIZE = 0.375F; - private final ItemRenderer itemRenderer; + private final ItemModelResolver itemModelResolver; public DefaultStoveRenderer(BlockEntityRendererProvider.Context context) { - this.itemRenderer = context.getItemRenderer(); + this.itemModelResolver = context.itemModelResolver(); } @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(); + public AbstractStoveRenderState createRenderState() { + return new AbstractStoveRenderState(); + } - for (int i = 0; i < items.getSlots(); ++i) { - ItemStack stoveStack = items.getStackInSlot(i); - if (stoveStack.isEmpty()) continue; + @Override + public void submit(AbstractStoveRenderState state, PoseStack poseStack, SubmitNodeCollector collector, CameraRenderState cameraRenderState) { + for (int i = 0; i < state.slotCount; ++i) { + ItemStackRenderState stackRenderState = state.itemRenderStates[i]; + if (stackRenderState == null) continue; poseStack.pushPose(); @@ -42,21 +47,62 @@ public void render(T stove, float partialTicks, PoseStack poseStack, MultiBuffer poseStack.translate(0.5D, 1.02D, 0.5D); // Rotate item to face the stove's front side - float f = -direction.toYRot(); + float f = -state.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); + Vec2 itemOffset = state.offsets[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); + state.itemRenderStates[i].submit(poseStack, collector, state.lightCoords, OverlayTexture.NO_OVERLAY, 0); poseStack.popPose(); } } + + @Override + public void extractRenderState(T stove, AbstractStoveRenderState state, float partialTicks, Vec3 cameraPosition, ModelFeatureRenderer.@Nullable CrumblingOverlay breakProgress) { + BlockEntityRenderer.super.extractRenderState(stove, state, partialTicks, cameraPosition, breakProgress); + state.direction = stove.getBlockState().getValue(StoveBlock.FACING).getOpposite(); + + // base extractRenderState gets the light coords for the stove's position, but we use the position above the stove + state.lightCoords = LevelRenderer.getLightCoords(stove.getLevel(), stove.getBlockPos().above()); + + var items = stove.getItems(); + state.slotCount = items.getSlots(); + state.offsets = new Vec2[state.slotCount]; + state.itemRenderStates = new ItemStackRenderState[state.slotCount]; + + for (int i = 0; i < state.slotCount; i++) { + ItemStack stack = items.getStackInSlot(i); + if (stack.isEmpty()) { + state.itemRenderStates[i] = null; + state.offsets[i] = null; + continue; + } + + state.itemRenderStates[i] = new ItemStackRenderState(); + this.itemModelResolver.updateForTopItem( + state.itemRenderStates[i], + stack, + ItemDisplayContext.FIXED, + stove.getLevel(), + null, + (int) stove.getBlockPos().asLong() + ); + state.offsets[i] = stove.getStoveItemOffset(i); + } + } + + public static class AbstractStoveRenderState extends BlockEntityRenderState { + public Direction direction; + public int slotCount; + public Vec2[] offsets; + public ItemStackRenderState[] itemRenderStates; + } } \ No newline at end of file From bafa9fb54470c8a45d5cd96c9a6457c223539cdc Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Fri, 5 Jun 2026 16:54:25 +0100 Subject: [PATCH 11/25] Fix most of CookingPotMenu --- .../entity/container/CookingPotMenu.java | 56 +++++++------------ 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/common/block/entity/container/CookingPotMenu.java b/src/main/java/vectorwing/farmersdelight/common/block/entity/container/CookingPotMenu.java index 064cea6a7..e5c5cdf3f 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/entity/container/CookingPotMenu.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/entity/container/CookingPotMenu.java @@ -1,12 +1,12 @@ package vectorwing.farmersdelight.common.block.entity.container; -import com.mojang.datafixers.util.Pair; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.entity.player.StackedContents; +import net.minecraft.world.entity.player.StackedItemContents; import net.minecraft.world.inventory.*; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.RecipeHolder; @@ -14,17 +14,15 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.neoforged.neoforge.items.ItemStackHandler; import net.neoforged.neoforge.items.SlotItemHandler; -import net.neoforged.neoforge.items.wrapper.RecipeWrapper; import vectorwing.farmersdelight.FarmersDelight; import vectorwing.farmersdelight.common.block.entity.CookingPotBlockEntity; -import vectorwing.farmersdelight.common.crafting.CookingPotRecipe; import vectorwing.farmersdelight.common.registry.ModBlocks; import vectorwing.farmersdelight.common.registry.ModMenuTypes; import vectorwing.farmersdelight.common.tag.ModTags; import java.util.Objects; -public class CookingPotMenu extends RecipeBookMenu +public class CookingPotMenu extends RecipeBookMenu { public static final Identifier EMPTY_CONTAINER_SLOT_BOWL = Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "item/empty_container_slot_bowl"); @@ -68,12 +66,9 @@ public CookingPotMenu(final int windowId, final Inventory playerInventory, final this.addSlot(new CookingPotMealSlot(inventory, 6, 124, 26)); // Bowl Input - this.addSlot(new SlotItemHandler(inventory, 7, 92, 55) - { - public Pair getNoItemIcon() { - return Pair.of(InventoryMenu.BLOCK_ATLAS, EMPTY_CONTAINER_SLOT_BOWL); - } - }); + Slot bowlSlot = new SlotItemHandler(inventory, 7, 92, 55); + bowlSlot.setBackground(EMPTY_CONTAINER_SLOT_BOWL); + this.addSlot(bowlSlot); // Bowl Output this.addSlot(new CookingPotResultSlot(playerInventory.player, blockEntity, inventory, 8, 124, 55)); @@ -160,52 +155,39 @@ public boolean isHeated() { return blockEntity.isHeated(); } - @Override - public void fillCraftSlotsStackedContents(StackedContents helper) { - for (int i = 0; i < inventory.getSlots(); i++) { - helper.accountSimpleStack(inventory.getStackInSlot(i)); - } - } - - @Override - public void clearCraftingContent() { - for (int i = 0; i < 6; i++) { - this.inventory.setStackInSlot(i, ItemStack.EMPTY); - } - } - @Override - public boolean recipeMatches(RecipeHolder recipe) { - return recipe.value().matches(new RecipeWrapper(inventory), level); - } - - @Override public int getResultSlotIndex() { return 7; } - @Override public int getGridWidth() { return 3; } - @Override public int getGridHeight() { return 2; } - @Override public int getSize() { return 7; } @Override - public RecipeBookType getRecipeBookType() { - return RecipeBookType.valueOf("FARMERSDELIGHT_COOKING"); + public PostPlaceAction handlePlacement(boolean useMaxItems, boolean allowDroppingItemsToClear, RecipeHolder recipe, ServerLevel level, Inventory inventory) { + //TODO this shouldn't be null but I'm handling it later + // worth referencing the implementation in AbstractCraftingMenu, i think. + return null; } @Override - public boolean shouldMoveToInventory(int slot) { - return slot < (getGridWidth() * getGridHeight()); + public void fillCraftSlotsStackedContents(StackedItemContents helper) { + for (int i = 0; i < inventory.getSlots(); i++) { + helper.accountSimpleStack(inventory.getStackInSlot(i)); + } + } + + @Override + public RecipeBookType getRecipeBookType() { + return RecipeBookType.valueOf("FARMERSDELIGHT_COOKING"); } } From 1465d58bc0a55491eb33b9021cdf817f25d531d9 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Fri, 5 Jun 2026 17:03:35 +0100 Subject: [PATCH 12/25] Mostly fix CookingPotTooltip --- .../client/gui/CookingPotTooltip.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/gui/CookingPotTooltip.java b/src/main/java/vectorwing/farmersdelight/client/gui/CookingPotTooltip.java index 5af3fd208..d72c6180e 100644 --- a/src/main/java/vectorwing/farmersdelight/client/gui/CookingPotTooltip.java +++ b/src/main/java/vectorwing/farmersdelight/client/gui/CookingPotTooltip.java @@ -1,15 +1,12 @@ package vectorwing.farmersdelight.client.gui; import net.minecraft.ChatFormatting; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; -import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.network.chat.MutableComponent; import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.ItemStack; -import org.joml.Matrix4f; import vectorwing.farmersdelight.common.utility.TextUtils; public class CookingPotTooltip implements ClientTooltipComponent @@ -17,7 +14,6 @@ public class CookingPotTooltip implements ClientTooltipComponent private static final int ITEM_SIZE = 16; private static final int MARGIN = 4; - private final int textSpacing = Minecraft.getInstance().font.lineHeight + 1; private final ItemStack mealStack; public CookingPotTooltip(CookingPotTooltipComponent tooltip) { @@ -25,8 +21,8 @@ public CookingPotTooltip(CookingPotTooltipComponent tooltip) { } @Override - public int getHeight() { - return mealStack.isEmpty() ? textSpacing : textSpacing + ITEM_SIZE; + public int getHeight(Font font) { + return mealStack.isEmpty() ? getLineHeight(font) : getLineHeight(font) + ITEM_SIZE; } @Override @@ -42,29 +38,34 @@ public int getWidth(Font font) { } @Override - public void renderImage(Font font, int mouseX, int mouseY, GuiGraphics gui) { + public void extractImage(Font font, int x, int y, int w, int h, GuiGraphicsExtractor gui) { if (mealStack.isEmpty()) return; - gui.renderItem(mealStack, mouseX, mouseY + textSpacing, 0); + gui.item(mealStack, x, y + getLineHeight(font), 0); } @Override - public void renderText(Font font, int x, int y, Matrix4f matrix4f, MultiBufferSource.BufferSource bufferSource) { + public void extractText(GuiGraphicsExtractor gui, Font font, int x, int y) { Integer color = ChatFormatting.GRAY.getColor(); int gray = color == null ? -1 : color; + // TODO this might not work as expected. I'm guessing with most of the methods. if (!mealStack.isEmpty()) { MutableComponent textServingsOf = mealStack.getCount() == 1 - ? TextUtils.tooltip("cooking_pot.single_serving") - : TextUtils.tooltip("cooking_pot.many_servings", mealStack.getCount()); + ? TextUtils.tooltip("cooking_pot.single_serving") + : TextUtils.tooltip("cooking_pot.many_servings", mealStack.getCount()); - font.drawInBatch(textServingsOf, (float) x, (float) y, gray, true, matrix4f, bufferSource, Font.DisplayMode.NORMAL, 0, 15728880); - font.drawInBatch(mealStack.getHoverName(), x + ITEM_SIZE + MARGIN, y + textSpacing + MARGIN, -1, true, matrix4f, bufferSource, Font.DisplayMode.NORMAL, 0, 15728880); + gui.text(font, textServingsOf, x, y, gray); + gui.text(font, mealStack.getHoverName(), x + ITEM_SIZE + MARGIN, y + getLineHeight(font) + MARGIN, -1); } else { MutableComponent textEmpty = TextUtils.tooltip("cooking_pot.empty"); - font.drawInBatch(textEmpty, x, y, gray, true, matrix4f, bufferSource, Font.DisplayMode.NORMAL, 0, 15728880); + gui.text(font, textEmpty, x, y, gray, true); } } + private int getLineHeight(Font font) { + return font.lineHeight + 1; + } + public record CookingPotTooltipComponent(ItemStack mealStack) implements TooltipComponent { } From 4273e23105d959a7111903ffb0db317cd7f64331 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Fri, 5 Jun 2026 19:26:49 +0100 Subject: [PATCH 13/25] Move HUDOverlays to new GUI system Why on earth does the client not know gamerule values? Transparency might not work properly. --- .../client/gui/HUDOverlays.java | 55 ++++++++++--------- .../common/event/CommonEvents.java | 34 ++++++++++++ .../common/network/ModNetworking.java | 6 ++ .../NaturalRegenerationGameRulePayload.java | 30 ++++++++++ 4 files changed, 99 insertions(+), 26 deletions(-) create mode 100644 src/main/java/vectorwing/farmersdelight/common/network/payload/NaturalRegenerationGameRulePayload.java diff --git a/src/main/java/vectorwing/farmersdelight/client/gui/HUDOverlays.java b/src/main/java/vectorwing/farmersdelight/client/gui/HUDOverlays.java index 2db8b39f5..f6f726f7f 100644 --- a/src/main/java/vectorwing/farmersdelight/client/gui/HUDOverlays.java +++ b/src/main/java/vectorwing/farmersdelight/client/gui/HUDOverlays.java @@ -1,10 +1,11 @@ package vectorwing.farmersdelight.client.gui; +import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.DeltaTracker; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.LayeredDraw; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; import net.minecraft.world.effect.MobEffects; @@ -12,12 +13,12 @@ import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.player.Player; import net.minecraft.world.food.FoodData; -import net.minecraft.world.level.GameRules; import net.neoforged.neoforge.client.event.RegisterGuiLayersEvent; +import net.neoforged.neoforge.client.gui.GuiLayer; import net.neoforged.neoforge.client.gui.VanillaGuiLayers; -import org.jetbrains.annotations.NotNull; import vectorwing.farmersdelight.FarmersDelight; import vectorwing.farmersdelight.common.Configuration; +import vectorwing.farmersdelight.common.network.payload.NaturalRegenerationGameRulePayload; import vectorwing.farmersdelight.common.registry.ModEffects; import java.util.Random; @@ -49,12 +50,12 @@ public static void register(RegisterGuiLayersEvent event) { event.registerAbove(VanillaGuiLayers.FOOD_LEVEL, NourishmentOverlay.ID, new NourishmentOverlay()); } - public static abstract class BaseOverlay implements LayeredDraw.Layer + public static abstract class BaseOverlay implements GuiLayer { - public abstract void render(Minecraft mc, Player player, GuiGraphics guiGraphics, int left, int right, int top, int guiTicks); + public abstract void render(Minecraft mc, Player player, GuiGraphicsExtractor guiGraphics, int left, int right, int top, int guiTicks); @Override - public final void render(@NotNull GuiGraphics guiGraphics, @NotNull DeltaTracker deltaTracker) { + public final void render(GuiGraphicsExtractor guiGraphics, DeltaTracker deltaTracker) { Minecraft minecraft = Minecraft.getInstance(); if (minecraft.player == null || !shouldRenderOverlay(minecraft, minecraft.player, guiGraphics, minecraft.gui.getGuiTicks())) return; @@ -66,7 +67,7 @@ public final void render(@NotNull GuiGraphics guiGraphics, @NotNull DeltaTracker render(minecraft, minecraft.player, guiGraphics, left, right, top, minecraft.gui.getGuiTicks()); } - public boolean shouldRenderOverlay(Minecraft minecraft, Player player, GuiGraphics guiGraphics, int guiTicks) { + public boolean shouldRenderOverlay(Minecraft minecraft, Player player, GuiGraphicsExtractor guiGraphics, int guiTicks) { return !minecraft.options.hideGui && minecraft.gameMode != null && minecraft.gameMode.canHurtPlayer(); } } @@ -76,11 +77,11 @@ public static class NourishmentOverlay extends BaseOverlay public static final Identifier ID = Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "nourishment"); @Override - public void render(Minecraft minecraft, Player player, GuiGraphics guiGraphics, int left, int right, int top, int guiTicks) { + public void render(Minecraft minecraft, Player player, GuiGraphicsExtractor guiGraphics, int left, int right, int top, int guiTicks) { FoodData stats = player.getFoodData(); boolean isPlayerHealingWithSaturation = - player.level().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION) + NaturalRegenerationGameRulePayload.NATURAL_REGENERATION && player.isHurt() && stats.getSaturationLevel() > 0.0; @@ -90,7 +91,7 @@ public void render(Minecraft minecraft, Player player, GuiGraphics guiGraphics, } @Override - public boolean shouldRenderOverlay(Minecraft mc, Player player, GuiGraphics guiGraphics, int guiTicks) { + public boolean shouldRenderOverlay(Minecraft mc, Player player, GuiGraphicsExtractor guiGraphics, int guiTicks) { if (!super.shouldRenderOverlay(mc, player, guiGraphics, guiTicks)) return false; @@ -103,7 +104,7 @@ public static class ComfortOverlay extends BaseOverlay public static final Identifier ID = Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "comfort"); @Override - public void render(Minecraft minecraft, Player player, GuiGraphics guiGraphics, int left, int right, int top, int guiTicks) { + public void render(Minecraft minecraft, Player player, GuiGraphicsExtractor guiGraphics, int left, int right, int top, int guiTicks) { FoodData stats = player.getFoodData(); boolean isPlayerEligibleForComfort = stats.getSaturationLevel() == 0.0F @@ -116,7 +117,7 @@ public void render(Minecraft minecraft, Player player, GuiGraphics guiGraphics, } @Override - public boolean shouldRenderOverlay(Minecraft mc, Player player, GuiGraphics guiGraphics, int guiTicks) { + public boolean shouldRenderOverlay(Minecraft mc, Player player, GuiGraphicsExtractor guiGraphics, int guiTicks) { if (!super.shouldRenderOverlay(mc, player, guiGraphics, guiTicks)) return false; @@ -124,14 +125,15 @@ public boolean shouldRenderOverlay(Minecraft mc, Player player, GuiGraphics guiG } } - public static void drawNourishmentOverlay(FoodData foodData, Minecraft minecraft, GuiGraphics graphics, int right, int top, boolean naturalHealing) { + // TODO this might not render transparently correctly. + public static void drawNourishmentOverlay(FoodData foodData, Minecraft minecraft, GuiGraphicsExtractor graphics, int right, int top, boolean naturalHealing) { float saturation = foodData.getSaturationLevel(); int foodLevel = foodData.getFoodLevel(); int ticks = minecraft.gui.getGuiTicks(); Random rand = new Random(); - rand.setSeed(ticks * 312871); + rand.setSeed((long) ticks * 312871); - RenderSystem.enableBlend(); + //RenderSystem.enableBlend(); for (int j = 0; j < 10; ++j) { int x = right - j * 8 - 9; @@ -142,25 +144,26 @@ public static void drawNourishmentOverlay(FoodData foodData, Minecraft minecraft } // Background texture - graphics.blit(MOD_ICONS_TEXTURE, x, y, 0, 0, 9, 9); + graphics.blit(RenderPipelines.GUI_TEXTURED, MOD_ICONS_TEXTURE, x, y, 0, 0, 9, 9, 9, 9, 256, 256); float effectiveHungerOfBar = (foodData.getFoodLevel()) / 2.0F - j; int naturalHealingOffset = naturalHealing ? 18 : 0; // Gilded hunger icons if (effectiveHungerOfBar >= 1) - graphics.blit(MOD_ICONS_TEXTURE, x, y, 18 + naturalHealingOffset, 0, 9, 9); + graphics.blit(RenderPipelines.GUI_TEXTURED, MOD_ICONS_TEXTURE, x, y, 18 + naturalHealingOffset, 0, 9, 9, 256, 256); else if (effectiveHungerOfBar >= .5) - graphics.blit(MOD_ICONS_TEXTURE, x, y, 9 + naturalHealingOffset, 0, 9, 9); + graphics.blit(RenderPipelines.GUI_TEXTURED, MOD_ICONS_TEXTURE, x, y, 9 + naturalHealingOffset, 0, 9, 9, 256, 256); } - RenderSystem.disableBlend(); + //RenderSystem.disableBlend(); } - public static void drawComfortOverlay(Player player, Minecraft minecraft, GuiGraphics graphics, int left, int top) { + // TODO this might not render transparently correctly. + public static void drawComfortOverlay(Player player, Minecraft minecraft, GuiGraphicsExtractor graphics, int left, int top) { int ticks = minecraft.gui.getGuiTicks(); Random rand = new Random(); - rand.setSeed((long) (ticks * 312871)); + rand.setSeed((long) ticks * 312871); int health = Mth.ceil(player.getHealth()); float absorb = Mth.ceil(player.getAbsorptionAmount()); @@ -177,7 +180,7 @@ public static void drawComfortOverlay(Player player, Minecraft minecraft, GuiGra int comfortHeartFrame = comfortSheen % 2; int[] textureWidth = {5, 9}; - RenderSystem.enableBlend(); + //RenderSystem.enableBlend(); int healthMaxSingleRow = Mth.ceil(Math.min(healthMax, 20) / 2.0F); int leftHeightOffset = ((healthRows - 1) * rowHeight); // This keeps the overlay on the bottommost row of hearts @@ -191,13 +194,13 @@ public static void drawComfortOverlay(Player player, Minecraft minecraft, GuiGra if (i == regen) y -= 2; if (column == comfortSheen / 2) { - graphics.blit(MOD_ICONS_TEXTURE, x, y, 0, 9, textureWidth[comfortHeartFrame], 9); + graphics.blit(RenderPipelines.GUI_TEXTURED,MOD_ICONS_TEXTURE, x, y, 0, 9, textureWidth[comfortHeartFrame], 9, 256, 256); } if (column == (comfortSheen / 2) - 1 && comfortHeartFrame == 0) { - graphics.blit(MOD_ICONS_TEXTURE, x + 5, y, 5, 9, 4, 9); + graphics.blit(RenderPipelines.GUI_TEXTURED,MOD_ICONS_TEXTURE, x + 5, y, 5, 9, 4, 9, 256, 256); } } - RenderSystem.disableBlend(); + //RenderSystem.disableBlend(); } } diff --git a/src/main/java/vectorwing/farmersdelight/common/event/CommonEvents.java b/src/main/java/vectorwing/farmersdelight/common/event/CommonEvents.java index bc7d989b8..9491e94ba 100644 --- a/src/main/java/vectorwing/farmersdelight/common/event/CommonEvents.java +++ b/src/main/java/vectorwing/farmersdelight/common/event/CommonEvents.java @@ -1,20 +1,29 @@ package vectorwing.farmersdelight.common.event; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.Item; import net.minecraft.world.item.Items; import net.minecraft.world.item.consume_effects.ApplyStatusEffectsConsumeEffect; +import net.minecraft.world.level.gamerules.GameRules; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.entity.living.LivingEntityUseItemEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; +import net.neoforged.neoforge.network.PacketDistributor; import vectorwing.farmersdelight.FarmersDelight; import vectorwing.farmersdelight.common.Configuration; import vectorwing.farmersdelight.common.FoodValues; +import vectorwing.farmersdelight.common.network.payload.NaturalRegenerationGameRulePayload; @EventBusSubscriber(modid = FarmersDelight.MODID) public class CommonEvents { + private static boolean NATURAL_REGENERATION = true; + @SubscribeEvent public static void handleVanillaSoupEffects(LivingEntityUseItemEvent.Finish event) { Item food = event.getItem().getItem(); @@ -34,4 +43,29 @@ public static void handleVanillaSoupEffects(LivingEntityUseItemEvent.Finish even } } } + + @SubscribeEvent + public void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { + sendPayload((ServerPlayer) event.getEntity(), new NaturalRegenerationGameRulePayload(NATURAL_REGENERATION)); + } + + @SubscribeEvent + public void onServerWorldTick(ServerTickEvent.Post event) { + boolean currentNaturalRegen = event.getServer().getGameRules().get(GameRules.NATURAL_HEALTH_REGENERATION); + if (NATURAL_REGENERATION != currentNaturalRegen) { + NATURAL_REGENERATION = currentNaturalRegen; + + NaturalRegenerationGameRulePayload payload = new NaturalRegenerationGameRulePayload(NATURAL_REGENERATION); + for (ServerPlayer player : event.getServer().getPlayerList().getPlayers()) { + sendPayload(player, payload); + } + } + } + + private static void sendPayload(ServerPlayer player, CustomPacketPayload payload) { + if (!player.connection.hasChannel(payload.type().id())) { + return; + } + PacketDistributor.sendToPlayer(player, payload); + } } diff --git a/src/main/java/vectorwing/farmersdelight/common/network/ModNetworking.java b/src/main/java/vectorwing/farmersdelight/common/network/ModNetworking.java index dc9accfa2..2c988a1be 100644 --- a/src/main/java/vectorwing/farmersdelight/common/network/ModNetworking.java +++ b/src/main/java/vectorwing/farmersdelight/common/network/ModNetworking.java @@ -10,6 +10,7 @@ import vectorwing.farmersdelight.FarmersDelight; import vectorwing.farmersdelight.common.item.SkilletItem; import vectorwing.farmersdelight.common.network.payload.FlipSkilletPayload; +import vectorwing.farmersdelight.common.network.payload.NaturalRegenerationGameRulePayload; import vectorwing.farmersdelight.common.network.payload.RichSoilBoostParticlesPayload; import vectorwing.farmersdelight.common.registry.ModDataComponents; @@ -21,6 +22,7 @@ public static void registerPayloadHandlers(RegisterPayloadHandlersEvent event) { final PayloadRegistrar registrar = event.registrar("1"); registrar.playToClient(RichSoilBoostParticlesPayload.TYPE, RichSoilBoostParticlesPayload.STREAM_CODEC, ClientPayloadHandler::handleRichSoilBoostParticles); registrar.playToServer(FlipSkilletPayload.TYPE, FlipSkilletPayload.STREAM_CODEC, ServerPayloadHandler::handleFlipSkillet); + registrar.playToClient(NaturalRegenerationGameRulePayload.TYPE, NaturalRegenerationGameRulePayload.STREAM_CODEC, ClientPayloadHandler::handleNaturalRegenerationPayload); } public static class ClientPayloadHandler @@ -28,6 +30,10 @@ public static class ClientPayloadHandler public static void handleRichSoilBoostParticles(RichSoilBoostParticlesPayload payload, IPayloadContext context) { BoneMealItem.addGrowthParticles(context.player().level(), payload.pos(), 15); } + + public static void handleNaturalRegenerationPayload(NaturalRegenerationGameRulePayload payload, IPayloadContext context) { + NaturalRegenerationGameRulePayload.NATURAL_REGENERATION = payload.value(); + } } public static class ServerPayloadHandler diff --git a/src/main/java/vectorwing/farmersdelight/common/network/payload/NaturalRegenerationGameRulePayload.java b/src/main/java/vectorwing/farmersdelight/common/network/payload/NaturalRegenerationGameRulePayload.java new file mode 100644 index 000000000..16b0e9b8a --- /dev/null +++ b/src/main/java/vectorwing/farmersdelight/common/network/payload/NaturalRegenerationGameRulePayload.java @@ -0,0 +1,30 @@ +package vectorwing.farmersdelight.common.network.payload; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.Identifier; +import org.jetbrains.annotations.NotNull; +import vectorwing.farmersdelight.FarmersDelight; + +/** + * Credits to squeek502 (AppleSkin) for the implementation reference! + * ... + */ +public record NaturalRegenerationGameRulePayload(boolean value) implements CustomPacketPayload +{ + public static boolean NATURAL_REGENERATION = true; + public static final Identifier ID = Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "natural_regeneration_gamerule_sync"); + public static final Type TYPE = new Type<>(ID); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, + NaturalRegenerationGameRulePayload::value, + NaturalRegenerationGameRulePayload::new + ); + + @Override + public @NotNull Type type() { + return TYPE; + } +} From d32e426640b5b5b77fd756337c25fe1f68ccbf9c Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:15:42 +0100 Subject: [PATCH 14/25] Fix CookingPotScreen --- .../client/gui/CookingPotScreen.java | 121 ++++++++---------- .../resources/META-INF/accesstransformer.cfg | 3 +- 2 files changed, 53 insertions(+), 71 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/gui/CookingPotScreen.java b/src/main/java/vectorwing/farmersdelight/client/gui/CookingPotScreen.java index f12d2e3e8..c14a1c070 100644 --- a/src/main/java/vectorwing/farmersdelight/client/gui/CookingPotScreen.java +++ b/src/main/java/vectorwing/farmersdelight/client/gui/CookingPotScreen.java @@ -1,44 +1,39 @@ package vectorwing.farmersdelight.client.gui; -import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.ChatFormatting; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.ImageButton; -import net.minecraft.client.gui.components.WidgetSprites; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent; -import net.minecraft.client.gui.screens.recipebook.RecipeUpdateListener; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.navigation.ScreenPosition; +import net.minecraft.client.gui.screens.inventory.AbstractRecipeBookScreen; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; import net.minecraft.resources.Identifier; import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.inventory.ContainerInput; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import vectorwing.farmersdelight.FarmersDelight; -import vectorwing.farmersdelight.common.Configuration; import vectorwing.farmersdelight.common.block.entity.container.CookingPotMenu; import vectorwing.farmersdelight.common.utility.TextUtils; -import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; import java.awt.*; import java.util.ArrayList; import java.util.List; @ParametersAreNonnullByDefault -public class CookingPotScreen extends AbstractContainerScreen implements RecipeUpdateListener +public class CookingPotScreen extends AbstractRecipeBookScreen { - private static final WidgetSprites RECIPE_BUTTON = new WidgetSprites(Identifier.withDefaultNamespace("recipe_book/button"), Identifier.withDefaultNamespace("recipe_book/button")); private static final Identifier BACKGROUND_TEXTURE = Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "textures/gui/cooking_pot.png"); private static final Rectangle HEAT_ICON = new Rectangle(47, 55, 17, 15); private static final Rectangle PROGRESS_ARROW = new Rectangle(89, 25, 0, 17); - private final CookingPotRecipeBookComponent recipeBookComponent = new CookingPotRecipeBookComponent(); private boolean widthTooNarrow; public CookingPotScreen(CookingPotMenu screenContainer, Inventory inv, Component titleIn) { - super(screenContainer, inv, titleIn); + super(screenContainer, new CookingPotRecipeBookComponent(screenContainer), inv, titleIn); } @Override @@ -46,96 +41,87 @@ public void init() { super.init(); this.widthTooNarrow = this.width < 379; this.titleLabelX = 28; - this.recipeBookComponent.init(this.width, this.height, this.minecraft, this.widthTooNarrow, this.menu); - this.leftPos = this.recipeBookComponent.updateScreenPosition(this.width, this.imageWidth); - if (Configuration.ENABLE_COOKING_POT_RECIPE_BOOK.get()) { - this.addRenderableWidget(new ImageButton(this.leftPos + 5, this.height / 2 - 49, 20, 18, RECIPE_BUTTON, (button) -> - { - this.recipeBookComponent.toggleVisibility(); - this.leftPos = this.recipeBookComponent.updateScreenPosition(this.width, this.imageWidth); - button.setPosition(this.leftPos + 5, this.height / 2 - 49); - })); - } else { - this.recipeBookComponent.hide(); - this.leftPos = this.recipeBookComponent.updateScreenPosition(this.width, this.imageWidth); - } - this.addWidget(this.recipeBookComponent); this.setInitialFocus(this.recipeBookComponent); } @Override - protected void containerTick() { + protected ScreenPosition getRecipeBookButtonPosition() { + return new ScreenPosition(this.leftPos + 5, this.height / 2 - 49); + } + + @Override + public void containerTick() { super.containerTick(); this.recipeBookComponent.tick(); } @Override - public void render(GuiGraphics gui, final int mouseX, final int mouseY, float partialTicks) { - if (this.recipeBookComponent.isVisible() && this.widthTooNarrow) { - this.renderBackground(gui, mouseX, mouseY, partialTicks); - this.recipeBookComponent.render(gui, mouseX, mouseY, partialTicks); - } else { - super.render(gui, mouseX, mouseY, partialTicks); - this.recipeBookComponent.render(gui, mouseX, mouseY, partialTicks); - this.recipeBookComponent.renderGhostRecipe(gui, this.leftPos, this.topPos, false, partialTicks); - } + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractRenderState(graphics, mouseX, mouseY, a); + } + @Override + public void extractContents(GuiGraphicsExtractor gui, int mouseX, int mouseY, float a) { + super.extractContents(gui, mouseX, mouseY, a); this.renderMealDisplayTooltip(gui, mouseX, mouseY); this.renderHeatIndicatorTooltip(gui, mouseX, mouseY); - this.recipeBookComponent.renderTooltip(gui, this.leftPos, this.topPos, mouseX, mouseY); } - private void renderHeatIndicatorTooltip(GuiGraphics gui, int mouseX, int mouseY) { + private void renderHeatIndicatorTooltip(GuiGraphicsExtractor gui, int mouseX, int mouseY) { if (this.isHovering(HEAT_ICON.x, HEAT_ICON.y, HEAT_ICON.width, HEAT_ICON.height, mouseX, mouseY)) { String key = "cooking_pot." + (this.menu.isHeated() ? "heated" : "not_heated"); - gui.renderTooltip(this.font, TextUtils.container(key), mouseX, mouseY); + ClientTooltipComponent tooltip = ClientTooltipComponent.create(TextUtils.container(key).getVisualOrderText()); + gui.tooltip(this.font, List.of(tooltip), mouseX, mouseY, DefaultTooltipPositioner.INSTANCE, null); } } - protected void renderMealDisplayTooltip(GuiGraphics gui, int mouseX, int mouseY) { + protected void renderMealDisplayTooltip(GuiGraphicsExtractor gui, int mouseX, int mouseY) { if (this.minecraft != null && this.minecraft.player != null && this.menu.getCarried().isEmpty() && this.hoveredSlot != null && this.hoveredSlot.hasItem()) { if (this.hoveredSlot.index == 6) { - List tooltip = new ArrayList<>(); + List tooltip = new ArrayList<>(); ItemStack mealStack = this.hoveredSlot.getItem(); - tooltip.add(((MutableComponent) mealStack.getItem().getDescription()).withStyle(mealStack.getRarity().getStyleModifier())); + tooltip.add(ClientTooltipComponent.create(Component.translatable(mealStack.getItem().getDescriptionId()).withStyle(mealStack.getRarity().getStyleModifier()).getVisualOrderText())); ItemStack containerStack = this.menu.blockEntity.getContainer(); if (!containerStack.isEmpty()) { - String container = !containerStack.isEmpty() ? containerStack.getItem().getDescription().getString() : ""; - tooltip.add(TextUtils.container("cooking_pot.served_on", container).withStyle(ChatFormatting.GRAY)); + String container = !containerStack.isEmpty() ? Component.translatable(containerStack.getItem().getDescriptionId()).getString() : ""; + tooltip.add(ClientTooltipComponent.create(TextUtils.container("cooking_pot.served_on", container).withStyle(ChatFormatting.GRAY).getVisualOrderText())); } - gui.renderComponentTooltip(font, tooltip, mouseX, mouseY); + gui.tooltip(font, tooltip, mouseX, mouseY, DefaultTooltipPositioner.INSTANCE, null); } else { - gui.renderTooltip(font, this.hoveredSlot.getItem(), mouseX, mouseY); + gui.tooltip(font, List.of(), mouseX, mouseY, DefaultTooltipPositioner.INSTANCE, null, this.hoveredSlot.getItem()); } } } @Override - protected void renderLabels(GuiGraphics gui, int mouseX, int mouseY) { - super.renderLabels(gui, mouseX, mouseY); - gui.drawString(this.font, this.playerInventoryTitle, 8, (this.imageHeight - 96 + 2), 4210752, false); + protected void extractLabels(GuiGraphicsExtractor gui, int mouseX, int mouseY) { + super.extractLabels(gui, mouseX, mouseY); + gui.text(this.font, this.playerInventoryTitle, 8, (this.imageHeight - 96 + 2), 4210752, false); } + @Override - protected void renderBg(GuiGraphics gui, float partialTicks, int mouseX, int mouseY) { + public void extractBackground(GuiGraphicsExtractor gui, int mouseX, int mouseY, float a) { // Render UI background - RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + + //TODO i don't know the equivalent of this + //RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); if (this.minecraft == null) return; - gui.blit(BACKGROUND_TEXTURE, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight); + gui.blit(RenderPipelines.GUI_TEXTURED, BACKGROUND_TEXTURE, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight, 256, 256); // Render heat icon if (this.menu.isHeated()) { - gui.blit(BACKGROUND_TEXTURE, this.leftPos + HEAT_ICON.x, this.topPos + HEAT_ICON.y, 176, 0, HEAT_ICON.width, HEAT_ICON.height); + gui.blit(RenderPipelines.GUI_TEXTURED, BACKGROUND_TEXTURE, this.leftPos + HEAT_ICON.x, this.topPos + HEAT_ICON.y, 176, 0, HEAT_ICON.width, HEAT_ICON.height, 256, 256); } // Render progress arrow int l = this.menu.getCookProgressionScaled(); - gui.blit(BACKGROUND_TEXTURE, this.leftPos + PROGRESS_ARROW.x, this.topPos + PROGRESS_ARROW.y, 176, 15, l + 1, PROGRESS_ARROW.height); + gui.blit(RenderPipelines.GUI_TEXTURED, BACKGROUND_TEXTURE, this.leftPos + PROGRESS_ARROW.x, this.topPos + PROGRESS_ARROW.y, 176, 15, l + 1, PROGRESS_ARROW.height, 256, 256); } @Override @@ -144,23 +130,24 @@ protected boolean isHovering(int x, int y, int width, int height, double mouseX, } @Override - public boolean mouseClicked(double mouseX, double mouseY, int buttonId) { - if (this.recipeBookComponent.mouseClicked(mouseX, mouseY, buttonId)) { + public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { + + if (this.recipeBookComponent.mouseClicked(event, doubleClick)) { this.setFocused(this.recipeBookComponent); return true; } - return this.widthTooNarrow && this.recipeBookComponent.isVisible() || super.mouseClicked(mouseX, mouseY, buttonId); + return this.widthTooNarrow && this.recipeBookComponent.isVisible() || super.mouseClicked(event, doubleClick); } @Override - protected boolean hasClickedOutside(double mouseX, double mouseY, int x, int y, int buttonIdx) { + protected boolean hasClickedOutside(double mouseX, double mouseY, int x, int y) { boolean flag = mouseX < (double) x || mouseY < (double) y || mouseX >= (double) (x + this.imageWidth) || mouseY >= (double) (y + this.imageHeight); - return flag && this.recipeBookComponent.hasClickedOutside(mouseX, mouseY, this.leftPos, this.topPos, this.imageWidth, this.imageHeight, buttonIdx); + return flag && this.recipeBookComponent.hasClickedOutside(mouseX, mouseY, this.leftPos, this.topPos, this.imageWidth, this.imageHeight); } @Override - protected void slotClicked(Slot slot, int mouseX, int mouseY, ClickType clickType) { - super.slotClicked(slot, mouseX, mouseY, clickType); + protected void slotClicked(Slot slot, int slotId, int buttonNum, ContainerInput containerInput) { + super.slotClicked(slot, slotId, buttonNum, containerInput); this.recipeBookComponent.slotClicked(slot); } @@ -168,10 +155,4 @@ protected void slotClicked(Slot slot, int mouseX, int mouseY, ClickType clickTyp public void recipesUpdated() { this.recipeBookComponent.recipesUpdated(); } - - @Override - @Nonnull - public RecipeBookComponent getRecipeBookComponent() { - return this.recipeBookComponent; - } } diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 56172b5eb..931ba160a 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -11,4 +11,5 @@ public net.minecraft.world.item.enchantment.Enchantment modifyDamageFilteredValu public net.minecraft.world.item.enchantment.EnchantmentHelper$EnchantmentVisitor public-f net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList list # list public net.minecraft.util.datafix.fixes.ItemStackComponentizationFix$ItemStackData -public net.minecraft.world.food.FoodData exhaustionLevel \ No newline at end of file +public net.minecraft.world.food.FoodData exhaustionLevel +public-f net.minecraft.client.gui.screens.inventory.AbstractRecipeBookScreen recipeBookComponent \ No newline at end of file From 3431f3aa8c22bfd2eb8310df8e974f6c8722e510 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:45:50 +0100 Subject: [PATCH 15/25] Properly use RecipeCategories --- .../farmersdelight/FarmersDelight.java | 1 + .../client/event/ClientSetupEvents.java | 4 +- .../client/recipebook/RecipeCategories.java | 35 ----------- .../common/crafting/CookingPotRecipe.java | 9 ++- .../common/registry/ModRecipeCategories.java | 62 +++++++++++++++++++ 5 files changed, 71 insertions(+), 40 deletions(-) delete mode 100644 src/main/java/vectorwing/farmersdelight/client/recipebook/RecipeCategories.java create mode 100644 src/main/java/vectorwing/farmersdelight/common/registry/ModRecipeCategories.java diff --git a/src/main/java/vectorwing/farmersdelight/FarmersDelight.java b/src/main/java/vectorwing/farmersdelight/FarmersDelight.java index a4289b27f..35f8bc5da 100644 --- a/src/main/java/vectorwing/farmersdelight/FarmersDelight.java +++ b/src/main/java/vectorwing/farmersdelight/FarmersDelight.java @@ -59,6 +59,7 @@ public FarmersDelight(IEventBus modEventBus, ModContainer modContainer) { ModIngredientTypes.INGREDIENT_TYPES.register(modEventBus); ModAdvancements.TRIGGERS.register(modEventBus); ModConsumeEffectTypes.CONSUME_EFFECTS.register(modEventBus); + ModRecipeCategories.RECIPE_BOOK_CATEGORIES.register(modEventBus); RegistryAliases.addRegistryAliases(); diff --git a/src/main/java/vectorwing/farmersdelight/client/event/ClientSetupEvents.java b/src/main/java/vectorwing/farmersdelight/client/event/ClientSetupEvents.java index bd27a0f2d..6ed82587d 100644 --- a/src/main/java/vectorwing/farmersdelight/client/event/ClientSetupEvents.java +++ b/src/main/java/vectorwing/farmersdelight/client/event/ClientSetupEvents.java @@ -23,7 +23,7 @@ import vectorwing.farmersdelight.client.particle.SparkleParticle; import vectorwing.farmersdelight.client.particle.StarParticle; import vectorwing.farmersdelight.client.particle.SteamParticle; -import vectorwing.farmersdelight.client.recipebook.RecipeCategories; +import vectorwing.farmersdelight.common.registry.ModRecipeCategories; import vectorwing.farmersdelight.client.renderer.*; import vectorwing.farmersdelight.common.EnumParameters; import vectorwing.farmersdelight.common.block.entity.StoveBlockEntity; @@ -59,7 +59,7 @@ public static void registerClientExtensions(RegisterClientExtensionsEvent event) @SubscribeEvent public static void registerRecipeBookCategories(RegisterRecipeBookSearchCategoriesEvent event) { - RecipeCategories.init(event); + ModRecipeCategories.init(event); } @SubscribeEvent diff --git a/src/main/java/vectorwing/farmersdelight/client/recipebook/RecipeCategories.java b/src/main/java/vectorwing/farmersdelight/client/recipebook/RecipeCategories.java deleted file mode 100644 index 674ef2631..000000000 --- a/src/main/java/vectorwing/farmersdelight/client/recipebook/RecipeCategories.java +++ /dev/null @@ -1,35 +0,0 @@ -package vectorwing.farmersdelight.client.recipebook; - -import com.google.common.collect.ImmutableList; -import net.minecraft.world.inventory.RecipeBookType; -import net.minecraft.world.item.crafting.RecipeBookCategories; -import net.neoforged.neoforge.client.event.RegisterRecipeBookSearchCategoriesEvent; -import vectorwing.farmersdelight.common.crafting.CookingPotRecipe; -import vectorwing.farmersdelight.common.registry.ModRecipeTypes; - -public class RecipeCategories -{ - public static RecipeBookCategories COOKING_SEARCH = RecipeBookCategories.valueOf("FARMERSDELIGHT_COOKING_SEARCH"); - public static RecipeBookCategories COOKING_MEALS = RecipeBookCategories.valueOf("FARMERSDELIGHT_COOKING_MEALS"); - public static RecipeBookCategories COOKING_DRINKS = RecipeBookCategories.valueOf("FARMERSDELIGHT_COOKING_DRINKS"); - public static RecipeBookCategories COOKING_MISC = RecipeBookCategories.valueOf("FARMERSDELIGHT_COOKING_MISC"); - - public static void init(RegisterRecipeBookSearchCategoriesEvent event) { - event.registerBookCategories(RecipeBookType.valueOf("FARMERSDELIGHT_COOKING"), ImmutableList.of(COOKING_SEARCH, COOKING_MEALS, COOKING_DRINKS, COOKING_MISC)); - event.registerAggregateCategory(COOKING_SEARCH, ImmutableList.of(COOKING_MEALS, COOKING_DRINKS, COOKING_MISC)); - event.registerRecipeCategoryFinder(ModRecipeTypes.COOKING.get(), recipe -> - { - if (recipe.value() instanceof CookingPotRecipe cookingRecipe) { - CookingPotRecipeBookTab tab = cookingRecipe.getRecipeBookTab(); - if (tab != null) { - return switch (tab) { - case MEALS -> COOKING_MEALS; - case DRINKS -> COOKING_DRINKS; - case MISC -> COOKING_MISC; - }; - } - } - return COOKING_MISC; - }); - } -} diff --git a/src/main/java/vectorwing/farmersdelight/common/crafting/CookingPotRecipe.java b/src/main/java/vectorwing/farmersdelight/common/crafting/CookingPotRecipe.java index 6bed3dece..c7fbd9f6f 100644 --- a/src/main/java/vectorwing/farmersdelight/common/crafting/CookingPotRecipe.java +++ b/src/main/java/vectorwing/farmersdelight/common/crafting/CookingPotRecipe.java @@ -11,13 +11,12 @@ import net.minecraft.world.level.Level; import net.neoforged.neoforge.common.util.RecipeMatcher; import vectorwing.farmersdelight.client.recipebook.CookingPotRecipeBookTab; +import vectorwing.farmersdelight.common.registry.ModRecipeCategories; import vectorwing.farmersdelight.common.registry.ModRecipeSerializers; import vectorwing.farmersdelight.common.registry.ModRecipeTypes; import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.LinkedList; -import java.util.List; import java.util.Optional; public class CookingPotRecipe implements Recipe @@ -159,7 +158,11 @@ public PlacementInfo placementInfo() { @Override public RecipeBookCategory recipeBookCategory() { - return RecipeBookCategories.CRAFTING_MISC; // Never read for our purposes, AFAIK? + return switch(tab) { + case MEALS -> ModRecipeCategories.COOKING_MEALS.get(); + case DRINKS -> ModRecipeCategories.COOKING_DRINKS.get(); + case MISC -> ModRecipeCategories.COOKING_MISC.get(); + }; } @Override diff --git a/src/main/java/vectorwing/farmersdelight/common/registry/ModRecipeCategories.java b/src/main/java/vectorwing/farmersdelight/common/registry/ModRecipeCategories.java new file mode 100644 index 000000000..14ce5278d --- /dev/null +++ b/src/main/java/vectorwing/farmersdelight/common/registry/ModRecipeCategories.java @@ -0,0 +1,62 @@ +package vectorwing.farmersdelight.common.registry; + +import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent; +import net.minecraft.core.registries.Registries; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.ExtendedRecipeBookCategory; +import net.minecraft.world.item.crafting.RecipeBookCategory; +import net.neoforged.neoforge.client.event.RegisterRecipeBookSearchCategoriesEvent; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; +import vectorwing.farmersdelight.FarmersDelight; + +import java.util.List; +import java.util.Optional; + +public class ModRecipeCategories +{ + public static DeferredRegister RECIPE_BOOK_CATEGORIES = DeferredRegister.create(Registries.RECIPE_BOOK_CATEGORY, FarmersDelight.MODID); + + public static DeferredHolder COOKING_SEARCH = register("FARMERSDELIGHT_COOKING_SEARCH"); + public static DeferredHolder COOKING_MEALS = register("FARMERSDELIGHT_COOKING_MEALS"); + public static DeferredHolder COOKING_DRINKS = register("FARMERSDELIGHT_COOKING_DRINKS"); + public static DeferredHolder COOKING_MISC = register("FARMERSDELIGHT_COOKING_MISC"); + + private static DeferredHolder register(String id) { + return RECIPE_BOOK_CATEGORIES.register(id, RecipeBookCategory::new); + } + + // We can't do this as a static field, or it'll try to access the holders before they're bound. + // Makes more sense to put it here rather than in CookingPotRecipeBookComponent + public static List createCookingPotTabInfo() { + return List.of( + new RecipeBookComponent.TabInfo(Items.COMPASS.getDefaultInstance(), Optional.empty(), COOKING_SEARCH.get()), + new RecipeBookComponent.TabInfo(ModItems.VEGETABLE_NOODLES.get().getDefaultInstance(), Optional.empty(), COOKING_MEALS.get()), + new RecipeBookComponent.TabInfo(ModItems.APPLE_CIDER.get().getDefaultInstance(), Optional.empty(), COOKING_DRINKS.get()), + new RecipeBookComponent.TabInfo(ModItems.DUMPLINGS.get().getDefaultInstance(), Optional.of(ModItems.TOMATO_SAUCE.get().getDefaultInstance()), COOKING_MISC.get()) + ); + } + + + public static void init(RegisterRecipeBookSearchCategoriesEvent event) { + event.register(COOKING_SEARCH.get(), COOKING_MEALS.get(), COOKING_DRINKS.get(), COOKING_MISC.get()); + + //TODO lacking equivalents of this. different event? + //event.registerBookCategories(RecipeBookType.valueOf("FARMERSDELIGHT_COOKING"), ImmutableList.of(COOKING_SEARCH, COOKING_MEALS, COOKING_DRINKS, COOKING_MISC)); +// event.registerRecipeCategoryFinder(ModRecipeTypes.COOKING.get(), recipe -> +// { +// if (recipe.value() instanceof CookingPotRecipe cookingRecipe) { +// CookingPotRecipeBookTab tab = cookingRecipe.getRecipeBookTab(); +// if (tab != null) { +// return switch (tab) { +// case MEALS -> COOKING_MEALS; +// case DRINKS -> COOKING_DRINKS; +// case MISC -> COOKING_MISC; +// }; +// } +// } +// return COOKING_MISC; +// }); + } +} From 32dbdfa667f9c76dd80924605a00dbd2f607880f Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Fri, 5 Jun 2026 21:29:14 +0100 Subject: [PATCH 16/25] CuttingBoardBlockEntity May be buggy clientside. --- .../common/block/CuttingBoardBlock.java | 2 +- .../block/entity/CuttingBoardBlockEntity.java | 89 +++++++++++-------- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/common/block/CuttingBoardBlock.java b/src/main/java/vectorwing/farmersdelight/common/block/CuttingBoardBlock.java index 514dbf737..cf430d547 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/CuttingBoardBlock.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/CuttingBoardBlock.java @@ -92,7 +92,7 @@ public InteractionResult useItemOn(ItemStack stack, BlockState state, Level leve return InteractionResult.SUCCESS; } else { if (cuttingBoard.processStoredItemUsingTool(mainHandStack, player)) { - return InteractionResult.SUCCESS; + return InteractionResult.SUCCESS_SERVER; } } return InteractionResult.CONSUME; diff --git a/src/main/java/vectorwing/farmersdelight/common/block/entity/CuttingBoardBlockEntity.java b/src/main/java/vectorwing/farmersdelight/common/block/entity/CuttingBoardBlockEntity.java index 60510895e..c65f324e9 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/entity/CuttingBoardBlockEntity.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/entity/CuttingBoardBlockEntity.java @@ -2,10 +2,9 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.component.DataComponents; import net.minecraft.core.particles.ItemParticleOption; import net.minecraft.core.particles.ParticleTypes; -import net.minecraft.core.HolderLookup; -import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerLevel; @@ -15,24 +14,26 @@ import net.minecraft.sounds.SoundSource; import net.minecraft.stats.Stats; import net.minecraft.world.Clearable; -import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.*; import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.item.crafting.RecipeInput; import net.minecraft.world.item.crafting.RecipeManager; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.SoundType; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.capabilities.Capabilities; import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; import net.neoforged.neoforge.common.Tags; -import net.neoforged.neoforge.items.IItemHandler; -import net.neoforged.neoforge.items.ItemStackHandler; -import net.neoforged.neoforge.items.wrapper.RecipeWrapper; +import net.neoforged.neoforge.transfer.item.ItemResource; +import net.neoforged.neoforge.transfer.item.ItemStacksResourceHandler; +import net.neoforged.neoforge.transfer.transaction.Transaction; import vectorwing.farmersdelight.FarmersDelight; import vectorwing.farmersdelight.common.block.CuttingBoardBlock; import vectorwing.farmersdelight.common.crafting.CuttingBoardRecipe; @@ -52,7 +53,7 @@ @EventBusSubscriber(modid = FarmersDelight.MODID) public class CuttingBoardBlockEntity extends SyncedBlockEntity implements Clearable { - private final ItemStackHandler inventory; + private final ItemStacksResourceHandler inventory; private final RecipeManager.CachedCheck quickCheck; private Identifier lastRecipeID; private boolean isItemCarvingBoard; @@ -67,26 +68,27 @@ public CuttingBoardBlockEntity(BlockPos pos, BlockState state) { @SubscribeEvent public static void registerCapabilities(RegisterCapabilitiesEvent event) { event.registerBlockEntity( - Capabilities.ItemHandler.BLOCK, + Capabilities.Item.BLOCK, ModBlockEntityTypes.CUTTING_BOARD.get(), (be, context) -> be.getInventory() ); } @Override - public void loadAdditional(CompoundTag compound, HolderLookup.Provider registries) { - super.loadAdditional(compound, registries); - isItemCarvingBoard = compound.getBoolean("IsItemCarved"); - inventory.deserializeNBT(registries, compound.getCompound("Inventory")); + protected void loadAdditional(ValueInput input) { + super.loadAdditional(input); + isItemCarvingBoard = input.getBooleanOr("IsItemCarved", false); + inventory.deserialize(input); } @Override - public void saveAdditional(CompoundTag compound, HolderLookup.Provider registries) { - super.saveAdditional(compound, registries); - compound.put("Inventory", inventory.serializeNBT(registries)); - compound.putBoolean("IsItemCarved", isItemCarvingBoard); + protected void saveAdditional(ValueOutput output) { + super.saveAdditional(output); + output.putBoolean("IsItemCarved", isItemCarvingBoard); + inventory.serialize(output); } + // TODO this might be weird on client public boolean processStoredItemUsingTool(ItemStack toolStack, @Nullable Player player) { if (level == null) return false; @@ -95,7 +97,7 @@ public boolean processStoredItemUsingTool(ItemStack toolStack, @Nullable Player Optional> matchingRecipe = getMatchingRecipe(toolStack, player); matchingRecipe.ifPresent(recipe -> { - List results = recipe.value().rollResults(level.getRandom(), EnchantmentHelper.getTagEnchantmentLevel(level.holder(Enchantments.FORTUNE).get(), toolStack), new RecipeWrapper(inventory)); + List results = recipe.value().rollResults(level.getRandom(), EnchantmentHelper.getTagEnchantmentLevel(level.holder(Enchantments.FORTUNE).get(), toolStack), new CuttingBoardRecipeInput(inventory.getResource(0).toStack(), toolStack)); for (ItemStack resultStack : results) { Direction direction = getBlockState().getValue(CuttingBoardBlock.FACING).getCounterClockWise(); ItemUtils.spawnItemEntity(level, resultStack.copy(), @@ -113,13 +115,17 @@ public boolean processStoredItemUsingTool(ItemStack toolStack, @Nullable Player spawnCuttingParticles(serverLevel, getBlockPos(), getStoredItem()); } playProcessingSound(recipe.value().getSoundEvent().orElse(null), toolStack, getStoredItem()); - inventory.extractItem(0, 1, false); + + Transaction transaction = Transaction.openRoot(); + inventory.extract(inventory.getResource(0), 1, transaction); + transaction.commit(); + if (player instanceof ServerPlayer) { ModAdvancements.USE_CUTTING_BOARD.get().trigger((ServerPlayer) player); if (!getStoredItem().isEmpty()) { - player.displayClientMessage(TextUtils.block("cutting_board.remaining_items", getStoredItem().getCount()), true); + player.sendOverlayMessage(TextUtils.block("cutting_board.remaining_items", getStoredItem().getCount())); } else { - player.displayClientMessage(Component.empty(), true); + player.sendOverlayMessage(Component.empty()); } } }); @@ -129,23 +135,24 @@ public boolean processStoredItemUsingTool(ItemStack toolStack, @Nullable Player private Optional> getMatchingRecipe(ItemStack toolStack, @Nullable Player player) { if (level == null) return Optional.empty(); + if (!(level instanceof ServerLevel)) return Optional.empty(); - Optional> recipe = quickCheck.getRecipeFor(new CuttingBoardRecipeInput(getStoredItem(), toolStack), level); + Optional> recipe = quickCheck.getRecipeFor(new CuttingBoardRecipeInput(getStoredItem(), toolStack), (ServerLevel) level); if (recipe.isPresent()) { if (recipe.get().value().getTool().test(toolStack)) { return recipe; } else if (player != null) { - player.displayClientMessage(TextUtils.block("cutting_board.invalid_item"), true); + player.sendOverlayMessage(TextUtils.block("cutting_board.invalid_item")); } } else if (player != null) { - player.displayClientMessage(TextUtils.block("cutting_board.invalid_tool"), true); + player.sendOverlayMessage(TextUtils.block("cutting_board.invalid_tool")); } return Optional.empty(); } public void spawnCuttingParticles(ServerLevel level, BlockPos pos, ItemStack stack) { - level.sendParticles(new ItemParticleOption(ParticleTypes.ITEM, stack), pos.getX() + 0.5, pos.getY() + 0.2, pos.getZ() + 0.5, 5, 0.1, 0.1, 0.1, 0.05D); + level.sendParticles(new ItemParticleOption(ParticleTypes.ITEM, stack.getItem()), pos.getX() + 0.5, pos.getY() + 0.2, pos.getZ() + 0.5, 5, 0.1, 0.1, 0.1, 0.05D); } public void playProcessingSound(@Nullable SoundEvent sound, ItemStack tool, ItemStack boardItem) { @@ -173,23 +180,35 @@ public boolean canAddItem(ItemStack addedStack) { if (isItemCarvingBoard || addedStack.isEmpty()) { return false; } - return inventory.insertItem(0, addedStack.copy(), true).getCount() != addedStack.getCount(); + Transaction transaction = Transaction.openRoot(); + int amount = inventory.insert(ItemResource.of(addedStack.copy()), 0, transaction); + transaction.close(); + return amount != addedStack.getCount(); } public ItemStack addItem(ItemStack addedStack) { if (!isItemCarvingBoard) { - return inventory.insertItem(0, addedStack.copy(), false); + Transaction transaction = Transaction.openRoot(); + int inserted = inventory.insert(ItemResource.of(addedStack.copy()), 0, transaction); + transaction.commit(); + return addedStack.copyWithCount(addedStack.count() - inserted); } return addedStack; } public ItemStack removeItem() { isItemCarvingBoard = false; - return inventory.extractItem(0, getMaxStackSize(), false); + + Transaction transaction = Transaction.openRoot(); + ItemResource type = inventory.getResource(0); + int amount = inventory.extract(type, getMaxStackSize(), transaction); + transaction.commit(); + + return type.toStack(amount); } public boolean carveToolOnBoard(ItemStack toolStack) { - if (toolStack.getItem() instanceof TieredItem || toolStack.getItem() instanceof TridentItem || toolStack.getItem() instanceof ShearsItem) { + if (toolStack.has(DataComponents.TOOL) || toolStack.getItem() instanceof TridentItem || toolStack.getItem() instanceof ShearsItem) { if (addItem(toolStack) == ItemStack.EMPTY) { isItemCarvingBoard = true; return true; @@ -198,20 +217,20 @@ public boolean carveToolOnBoard(ItemStack toolStack) { return false; } - public IItemHandler getInventory() { + public ItemStacksResourceHandler getInventory() { return inventory; } public ItemStack getStoredItem() { - return inventory.getStackInSlot(0); + return inventory.getResource(0).toStack(); } public int getMaxStackSize() { - return inventory.getSlotLimit(0); + return inventory.getCapacityAsInt(0, inventory.getResource(0)); } public boolean isEmpty() { - return inventory.getStackInSlot(0).isEmpty(); + return inventory.getAmountAsInt(0) == 0; } public boolean isItemCarvingBoard() { @@ -223,11 +242,11 @@ public void setRemoved() { super.setRemoved(); } - private ItemStackHandler createHandler() { - return new ItemStackHandler() + private ItemStacksResourceHandler createHandler() { + return new ItemStacksResourceHandler(1) { @Override - protected void onContentsChanged(int slot) { + protected void onContentsChanged(int index, ItemStack previousContents) { inventoryChanged(); } }; From cf4c196dd4ef7c5de8e878246f385194f0957fe2 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Fri, 5 Jun 2026 22:09:59 +0100 Subject: [PATCH 17/25] AbstractStoveBlockEntity Breaks CookingPotMenu as it was working off of ItemStackHandler --- .../client/renderer/DefaultStoveRenderer.java | 4 +- .../common/block/AbstractStoveBlock.java | 3 +- .../entity/AbstractStoveBlockEntity.java | 94 +++++++++++-------- .../common/block/entity/StoveBlockEntity.java | 4 +- .../entity/container/CookingPotMenu.java | 8 +- 5 files changed, 66 insertions(+), 47 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/renderer/DefaultStoveRenderer.java b/src/main/java/vectorwing/farmersdelight/client/renderer/DefaultStoveRenderer.java index 4172e0d5a..103755809 100644 --- a/src/main/java/vectorwing/farmersdelight/client/renderer/DefaultStoveRenderer.java +++ b/src/main/java/vectorwing/farmersdelight/client/renderer/DefaultStoveRenderer.java @@ -74,12 +74,12 @@ public void extractRenderState(T stove, AbstractStoveRenderState state, float pa state.lightCoords = LevelRenderer.getLightCoords(stove.getLevel(), stove.getBlockPos().above()); var items = stove.getItems(); - state.slotCount = items.getSlots(); + state.slotCount = items.size(); state.offsets = new Vec2[state.slotCount]; state.itemRenderStates = new ItemStackRenderState[state.slotCount]; for (int i = 0; i < state.slotCount; i++) { - ItemStack stack = items.getStackInSlot(i); + ItemStack stack = items.getResource(i).toStack(items.getAmountAsInt(i)); if (stack.isEmpty()) { state.itemRenderStates[i] = null; state.offsets[i] = null; diff --git a/src/main/java/vectorwing/farmersdelight/common/block/AbstractStoveBlock.java b/src/main/java/vectorwing/farmersdelight/common/block/AbstractStoveBlock.java index 38319482d..7254abd9f 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/AbstractStoveBlock.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/AbstractStoveBlock.java @@ -125,6 +125,7 @@ protected InteractionResult tryToExtinguish(ItemStack heldStack, BlockState stat protected InteractionResult tryToPlaceFoodItem(ItemStack heldStack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { if (isStoveTopCovered(level, pos, state)) return InteractionResult.PASS; if (!(level.getBlockEntity(pos) instanceof AbstractStoveBlockEntity stoveEntity)) return InteractionResult.PASS; + if (!(level instanceof ServerLevel)) return InteractionResult.PASS; var maybeRecipe = stoveEntity.getCookingRecipe(heldStack); if (maybeRecipe.isEmpty()) return InteractionResult.PASS; @@ -132,7 +133,7 @@ protected InteractionResult tryToPlaceFoodItem(ItemStack heldStack, BlockState s boolean placeFoodSuccess = stoveEntity.placeFood(player, player.getAbilities().instabuild ? heldStack.copy() : heldStack, maybeRecipe.get()); if (!placeFoodSuccess) return InteractionResult.CONSUME; level.playSound(null, pos, SoundEvents.LANTERN_PLACE, SoundSource.BLOCKS, 0.5F, 1.0F); - return InteractionResult.SUCCESS; + return InteractionResult.SUCCESS_SERVER; } public void ignite(@Nullable Entity entity, LevelAccessor level, BlockPos pos, BlockState state) { diff --git a/src/main/java/vectorwing/farmersdelight/common/block/entity/AbstractStoveBlockEntity.java b/src/main/java/vectorwing/farmersdelight/common/block/entity/AbstractStoveBlockEntity.java index 4aeaedb0e..7837710ca 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/entity/AbstractStoveBlockEntity.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/entity/AbstractStoveBlockEntity.java @@ -1,13 +1,14 @@ package vectorwing.farmersdelight.common.block.entity; +import com.mojang.logging.LogUtils; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; +import net.minecraft.util.ProblemReporter; import net.minecraft.world.Clearable; -import net.minecraft.world.Container; -import net.minecraft.world.SimpleContainer; import net.minecraft.world.entity.Entity; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.*; @@ -17,8 +18,13 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.storage.TagValueOutput; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.phys.Vec2; -import net.neoforged.neoforge.items.ItemStackHandler; +import net.neoforged.neoforge.transfer.item.ItemResource; +import net.neoforged.neoforge.transfer.item.ItemStacksResourceHandler; +import vectorwing.farmersdelight.FarmersDelight; import vectorwing.farmersdelight.common.block.AbstractStoveBlock; import vectorwing.farmersdelight.common.utility.ItemUtils; @@ -29,7 +35,7 @@ public abstract class AbstractStoveBlockEntity extends BlockEntity implements Clearable { - private final ItemStackHandler items; + private final ItemStacksResourceHandler items; private final int[] cookingProgress; private final int[] cookingTime; private final RecipeManager.CachedCheck quickRecipeLookup; @@ -48,36 +54,35 @@ protected AbstractStoveBlockEntity(BlockEntityType blockEntityType, BlockPos public abstract Vec2 getStoveItemOffset(int index); - public ItemStackHandler getItems() { + public ItemStacksResourceHandler getItems() { return this.items; } @Override - public void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) { - super.loadAdditional(tag, registries); + protected void loadAdditional(ValueInput input) { + super.loadAdditional(input); - CompoundTag inventoryTag; - if (tag.contains("Inventory")) inventoryTag = tag.getCompound("Inventory"); - else inventoryTag = tag; - items.deserializeNBT(registries, inventoryTag); + items.deserialize(input); - if (tag.contains("CookingTimes", 11)) { - int[] arrayCookingTimes = tag.getIntArray("CookingTimes"); + Optional maybeCookingTimes = input.getIntArray("CookingTimes"); + if (maybeCookingTimes.isPresent()) { + int[] arrayCookingTimes = maybeCookingTimes.get(); System.arraycopy(arrayCookingTimes, 0, this.cookingProgress, 0, Math.min(this.cookingTime.length, arrayCookingTimes.length)); } - if (tag.contains("CookingTotalTimes", 11)) { - int[] arrayCookingTimesTotal = tag.getIntArray("CookingTotalTimes"); + Optional maybeCookingTimesTotal = input.getIntArray("CookingTotalTimes"); + if (maybeCookingTimesTotal.isPresent()) { + int[] arrayCookingTimesTotal = maybeCookingTimesTotal.get(); System.arraycopy(arrayCookingTimesTotal, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, arrayCookingTimesTotal.length)); } } @Override - public void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { - super.saveAdditional(tag, registries); - tag.put("Inventory", items.serializeNBT(registries)); - tag.putIntArray("CookingTimes", this.cookingProgress); - tag.putIntArray("CookingTotalTimes", this.cookingTime); + protected void saveAdditional(ValueOutput output) { + super.saveAdditional(output); + items.serialize(output); + output.putIntArray("CookingTimes", this.cookingProgress); + output.putIntArray("CookingTotalTimes", this.cookingTime); } @Override @@ -88,7 +93,16 @@ public ClientboundBlockEntityDataPacket getUpdatePacket() { @Override public CompoundTag getUpdateTag(HolderLookup.Provider registries) { CompoundTag tag = super.getUpdateTag(registries); - tag.put("Inventory", items.serializeNBT(registries)); + + ProblemReporter.Collector reporter = new ProblemReporter.Collector(problemPath()); + TagValueOutput valueOutput = TagValueOutput.createWithContext(reporter, registries); + items.serialize(valueOutput); + tag.put("inventory", valueOutput.buildResult()); + + if (!reporter.isEmpty()) { + FarmersDelight.LOGGER.warn("[{}] Serialization errors:\n{}", FarmersDelight.LOGGER.getName(), reporter.getTreeReport()); + } + return tag; } @@ -109,10 +123,11 @@ public static void serverTick(Level level, BlockPos pos, BlockState state, Abstr private void cookAndOutputItems() { assert this.level != null; + assert this.level instanceof ServerLevel; boolean didChange = false; - for (int i = 0; i < items.getSlots(); ++i) { - ItemStack ingredient = this.items.getStackInSlot(i); + for (int i = 0; i < this.items.size(); ++i) { + ItemStack ingredient = this.items.getResource(i).toStack(this.items.getAmountAsInt(i)); if (ingredient.isEmpty()) continue; didChange = true; @@ -120,15 +135,15 @@ private void cookAndOutputItems() { if (cookingProgress[i] < cookingTime[i]) continue; var input = new SingleRecipeInput(ingredient); - ItemStack result = this.quickRecipeLookup.getRecipeFor(input, this.level) - .map((recipe) -> recipe.value().assemble(input, this.level.registryAccess())) + ItemStack result = this.quickRecipeLookup.getRecipeFor(input, (ServerLevel) this.level) + .map((recipe) -> recipe.value().assemble(input)) .orElse(ingredient); if (!result.isItemEnabled(this.level.enabledFeatures())) continue; ItemUtils.spawnItemEntity(level, result.copy(), worldPosition.getX() + 0.5, worldPosition.getY() + 1.0, worldPosition.getZ() + 0.5, level.getRandom().nextGaussian() * (double) 0.01F, 0.1F, level.getRandom().nextGaussian() * (double) 0.01F); - this.items.setStackInSlot(i, ItemStack.EMPTY); + this.items.set(i, ItemResource.EMPTY, 0); var state = this.getBlockState(); this.level.sendBlockUpdated(this.worldPosition, state, state, Block.UPDATE_ALL); this.level.gameEvent(GameEvent.BLOCK_CHANGE, this.worldPosition, GameEvent.Context.of(state)); @@ -140,7 +155,7 @@ private void coolItems() { assert this.level != null; boolean didChange = false; - for (int i = 0; i < this.items.getSlots(); ++i) { + for (int i = 0; i < this.items.size(); ++i) { int thisItemCookingProgress = this.cookingProgress[i]; if (thisItemCookingProgress <= 0) continue; didChange = true; @@ -151,12 +166,15 @@ private void coolItems() { public Optional> getCookingRecipe(ItemStack itemStack) { assert this.level != null; - return this.quickRecipeLookup.getRecipeFor(new SingleRecipeInput(itemStack), this.level); + if (!(level instanceof ServerLevel)) { + return Optional.empty(); + } + return this.quickRecipeLookup.getRecipeFor(new SingleRecipeInput(itemStack), (ServerLevel) this.level); } public int getNextEmptySlot() { - return IntStream.range(0, this.items.getSlots()) - .filter((i) -> this.items.getStackInSlot(i).isEmpty()) + return IntStream.range(0, this.items.size()) + .filter((i) -> this.items.getAmountAsInt(i) == 0) .findFirst() .orElse(-1); } @@ -166,11 +184,12 @@ public boolean placeFood(@Nullable Entity entity, ItemStack foodStackToPlace, Re int emptySlotIndex = getNextEmptySlot(); if (emptySlotIndex < 0) return false; - assert this.items.getStackInSlot(emptySlotIndex).isEmpty(); + assert this.items.getResource(emptySlotIndex).isEmpty(); - this.cookingTime[emptySlotIndex] = recipe.value().getCookingTime(); + this.cookingTime[emptySlotIndex] = recipe.value().cookingTime(); this.cookingProgress[emptySlotIndex] = 0; - this.items.setStackInSlot(emptySlotIndex, foodStackToPlace.split(1)); + ItemStack split = foodStackToPlace.split(1); + this.items.set(emptySlotIndex, ItemResource.of(split), split.count()); var state = this.getBlockState(); this.level.sendBlockUpdated(this.worldPosition, state, state, Block.UPDATE_ALL); this.level.gameEvent(GameEvent.BLOCK_CHANGE, this.worldPosition, GameEvent.Context.of(entity, state)); @@ -184,8 +203,7 @@ public boolean shouldDropItems() { } public Stream streamItems() { - return IntStream.range(0, this.items.getSlots()) - .mapToObj(this.items::getStackInSlot); + return this.items.copyToList().stream(); } public boolean isEmpty() { @@ -220,11 +238,11 @@ public void clearContent() { streamItems().forEach((stack) -> stack.setCount(0)); } - private static ItemStackHandler createHandler(int slotCount) { - return new ItemStackHandler(slotCount) + private static ItemStacksResourceHandler createHandler(int slotCount) { + return new ItemStacksResourceHandler(slotCount) { @Override - public int getSlotLimit(int slot) { + public int getCapacity(int slot, ItemResource resource) { return 1; } }; diff --git a/src/main/java/vectorwing/farmersdelight/common/block/entity/StoveBlockEntity.java b/src/main/java/vectorwing/farmersdelight/common/block/entity/StoveBlockEntity.java index 46d3a824b..87d9c224b 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/entity/StoveBlockEntity.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/entity/StoveBlockEntity.java @@ -25,8 +25,8 @@ public void addSmokeParticles() { assert this.level != null; var items = this.getItems(); - for (int i = 0; i < items.getSlots(); ++i) { - if (items.getStackInSlot(i).isEmpty()) continue; + for (int i = 0; i < getInventorySlotCount(); ++i) { + if (items.getResource(i).isEmpty()) continue; if (level.getRandom().nextFloat() >= 0.2F) continue; Vec2 itemOffset = this.getStoveItemOffset(i); Direction direction = this.getBlockState().getValue(AbstractStoveBlock.FACING); diff --git a/src/main/java/vectorwing/farmersdelight/common/block/entity/container/CookingPotMenu.java b/src/main/java/vectorwing/farmersdelight/common/block/entity/container/CookingPotMenu.java index e5c5cdf3f..c42492de2 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/entity/container/CookingPotMenu.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/entity/container/CookingPotMenu.java @@ -12,8 +12,8 @@ import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; -import net.neoforged.neoforge.items.ItemStackHandler; import net.neoforged.neoforge.items.SlotItemHandler; +import net.neoforged.neoforge.transfer.item.ItemStacksResourceHandler; import vectorwing.farmersdelight.FarmersDelight; import vectorwing.farmersdelight.common.block.entity.CookingPotBlockEntity; import vectorwing.farmersdelight.common.registry.ModBlocks; @@ -31,7 +31,7 @@ public class CookingPotMenu extends RecipeBookMenu public static final int INDEX_OUTPUT = 8; public final CookingPotBlockEntity blockEntity; - public final ItemStackHandler inventory; + public final ItemStacksResourceHandler inventory; private final ContainerData cookingPotData; private final ContainerLevelAccess canInteractWithCallable; protected final Level level; @@ -181,8 +181,8 @@ public PostPlaceAction handlePlacement(boolean useMaxItems, boolean allowDroppin @Override public void fillCraftSlotsStackedContents(StackedItemContents helper) { - for (int i = 0; i < inventory.getSlots(); i++) { - helper.accountSimpleStack(inventory.getStackInSlot(i)); + for (int i = 0; i < inventory.size(); i++) { + helper.accountSimpleStack(inventory.getResource(i).toStack(inventory.getAmountAsInt(i))); } } From 21b07a929c668b013a9b5d99b76aa5a302f9b013 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Sun, 7 Jun 2026 16:11:40 +0100 Subject: [PATCH 18/25] Mostly fix skillet item rendering --- .../client/event/ClientSetupEvents.java | 28 ++-- .../client/renderer/SkilletItemRenderer.java | 143 +++++++++++++----- 2 files changed, 123 insertions(+), 48 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/event/ClientSetupEvents.java b/src/main/java/vectorwing/farmersdelight/client/event/ClientSetupEvents.java index 6ed82587d..d6a973102 100644 --- a/src/main/java/vectorwing/farmersdelight/client/event/ClientSetupEvents.java +++ b/src/main/java/vectorwing/farmersdelight/client/event/ClientSetupEvents.java @@ -2,6 +2,7 @@ import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.renderer.entity.ThrownItemRenderer; +import net.minecraft.client.renderer.special.SpecialModelRenderer; import net.minecraft.resources.Identifier; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.LivingEntity; @@ -33,23 +34,18 @@ @EventBusSubscriber(modid = FarmersDelight.MODID, value = Dist.CLIENT) public class ClientSetupEvents { - public static void init(final FMLClientSetupEvent event) { - event.enqueueWork(() -> ItemProperties.register(ModItems.SKILLET.get(), Identifier.withDefaultNamespace("cooking"), - (stack, world, entity, s) -> stack.getOrDefault(ModDataComponents.SKILLET_INGREDIENT, ItemStackWrapper.EMPTY).getStack().isEmpty() ? 0 : 1) - ); - } + // TODO unnecessary? data component covers this. +// public static void init(final FMLClientSetupEvent event) { +// +// event.enqueueWork(() -> ItemProperties.register(ModItems.SKILLET.get(), Identifier.withDefaultNamespace("cooking"), +// (stack, world, entity, s) -> stack.getOrDefault(ModDataComponents.SKILLET_INGREDIENT, ItemStackWrapper.EMPTY).getStack().isEmpty() ? 0 : 1) +// ); +// } @SubscribeEvent public static void registerClientExtensions(RegisterClientExtensionsEvent event) { event.registerItem(new IClientItemExtensions() { - BlockEntityWithoutLevelRenderer renderer = new SkilletItemRenderer(); - - @Override - public @NotNull BlockEntityWithoutLevelRenderer getCustomRenderer() { - return renderer; - } - @Override public HumanoidModel.@Nullable ArmPose getArmPose(LivingEntity living, InteractionHand hand, ItemStack stack) { return stack.has(ModDataComponents.SKILLET_FLIP_TIMESTAMP.get()) ? EnumParameters.PROXY_SKILLET_FLIP.getValue() : null; @@ -57,6 +53,14 @@ public static void registerClientExtensions(RegisterClientExtensionsEvent event) }, ModItems.SKILLET.get()); } + @SubscribeEvent + public static void registerSpecialRenderers(RegisterSpecialModelRendererEvent event) { + event.register( + Identifier.fromNamespaceAndPath(FarmersDelight.MODID, "skillet_special"), + SkilletItemRenderer.Unbaked.MAP_CODEC + ); + } + @SubscribeEvent public static void registerRecipeBookCategories(RegisterRecipeBookSearchCategoriesEvent event) { ModRecipeCategories.init(event); diff --git a/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletItemRenderer.java b/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletItemRenderer.java index 26bbd4345..9cea2c936 100644 --- a/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletItemRenderer.java +++ b/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletItemRenderer.java @@ -2,69 +2,69 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Axis; +import com.mojang.serialization.MapCodec; import net.minecraft.client.Minecraft; import net.minecraft.client.model.HumanoidModel; -import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; -import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.block.BlockModelRenderState; +import net.minecraft.client.renderer.block.model.BlockDisplayContext; +import net.minecraft.client.renderer.entity.state.HumanoidRenderState; +import net.minecraft.client.renderer.item.ItemStackRenderState; +import net.minecraft.client.renderer.special.SpecialModelRenderer; +import net.minecraft.client.resources.model.sprite.SpriteGetter; import net.minecraft.util.Mth; -import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.HumanoidArm; -import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; import net.minecraft.world.level.block.state.BlockState; import net.neoforged.neoforge.client.IArmPoseTransformer; +import org.joml.Vector3fc; +import org.jspecify.annotations.Nullable; import vectorwing.farmersdelight.common.item.SkilletItem; import vectorwing.farmersdelight.common.item.component.ItemStackWrapper; import vectorwing.farmersdelight.common.registry.ModDataComponents; -public class SkilletItemRenderer extends BlockEntityWithoutLevelRenderer +import java.util.Optional; +import java.util.function.Consumer; + +// TODO this needs a corresponding client item when model datagen is fixed +public record SkilletItemRenderer(SpriteGetter spriteGetter) implements SpecialModelRenderer { - public SkilletItemRenderer() { - super(Minecraft.getInstance().getBlockEntityRenderDispatcher(), Minecraft.getInstance().getEntityModels()); - } + public static final BlockDisplayContext BLOCK_DISPLAY_CONTEXT = BlockDisplayContext.create(); @Override - public void renderByItem(ItemStack stack, ItemDisplayContext displayContext, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) { - //render block - BlockItem item = ((BlockItem) stack.getItem()); - BlockState state = item.getBlock().defaultBlockState(); - - + public void submit(SkilletItemRenderArgument argument, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, int lightCoords, int overlayCoords, boolean hasFoil, int outlineColor) { Minecraft mc = Minecraft.getInstance(); - ItemStackWrapper stackWrapper = stack.getOrDefault(ModDataComponents.SKILLET_INGREDIENT.get(), ItemStackWrapper.EMPTY); - ItemStack ingredientStack = stackWrapper.getStack(); - float animation = 0; - if (!ingredientStack.isEmpty()) { + // Render item + if (argument.maybeIngredientRenderState.isPresent()) { poseStack.pushPose(); poseStack.translate(0.5, 1 / 16f, 0.5); long gameTime = mc.level.getGameTime(); - if (stack.has(ModDataComponents.SKILLET_FLIP_TIMESTAMP.get()) && displayContext != ItemDisplayContext.GUI) { - long time = stack.get(ModDataComponents.SKILLET_FLIP_TIMESTAMP.get()); - float partialTicks = mc.getTimer().getGameTimeDeltaPartialTick(false); + if (argument.flipTime.isPresent() && !argument.inGui()) { + long time = argument.flipTime().get(); + float partialTicks = mc.getDeltaTracker().getGameTimeDeltaPartialTick(false); animation = ((gameTime - time) + partialTicks) / SkilletItem.FLIP_TIME; animation = Mth.clamp(animation, 0, 1); float maxH = 0.4F; poseStack.translate(0, maxH * Mth.sin(animation * Mth.PI), 0); - float rotationAnimation = stack.getOrDefault(ModDataComponents.SKILLET_FLIPPED.get(), false) ? animation + 1.0F : animation; + float rotationAnimation = argument.skilletFlipped ? animation + 1.0F : animation; poseStack.mulPose(Axis.XP.rotationDegrees(180 * rotationAnimation)); } else { - poseStack.mulPose(Axis.XP.rotationDegrees(stack.getOrDefault(ModDataComponents.SKILLET_FLIPPED.get(), false) ? 180 : 0)); + poseStack.mulPose(Axis.XP.rotationDegrees(argument.skilletFlipped ? 180 : 0)); } poseStack.mulPose(Axis.XP.rotationDegrees(90)); poseStack.scale(0.5F, 0.5F, 0.5F); - if (displayContext != ItemDisplayContext.GUI) { - var itemRenderer = mc.getItemRenderer(); - itemRenderer.renderStatic(ingredientStack, ItemDisplayContext.FIXED, packedLight, - packedOverlay, poseStack, buffer, null, 0); + // Render item + if (!argument.inGui()) { + argument.maybeIngredientRenderState().get().submit( + poseStack, submitNodeCollector, lightCoords, overlayCoords, outlineColor); } poseStack.popPose(); @@ -72,25 +72,81 @@ public void renderByItem(ItemStack stack, ItemDisplayContext displayContext, Pos poseStack.pushPose(); - if (animation != 0 && displayContext.firstPerson()) { + // Render block + if (animation != 0 && argument.firstperson) { poseStack.translate(0, 0, 1); poseStack.mulPose(Axis.XN.rotationDegrees(Mth.sin(animation * Mth.TWO_PI) * 15)); poseStack.translate(0F, 0, -1); poseStack.translate(0, 0, -Mth.sin(animation * Mth.PI) * 0.2); } - mc.getBlockRenderer().renderSingleBlock(state, poseStack, buffer, packedLight, packedOverlay); + argument.blockRenderState.submit(poseStack, submitNodeCollector, lightCoords, overlayCoords, outlineColor); + poseStack.popPose(); } - public static class ArmPoseTransformer implements IArmPoseTransformer { + @Override + public void getExtents(Consumer consumer) { + // TODO??? + } + + @Override + public SkilletItemRenderer.@Nullable SkilletItemRenderArgument extractArgument(ItemStack stack) { + BlockState blockstate = ((BlockItem)stack.getItem()).getBlock().defaultBlockState(); + BlockModelRenderState blockRenderState = new BlockModelRenderState(); + Minecraft.getInstance().getBlockModelResolver().update(blockRenderState, blockstate, BLOCK_DISPLAY_CONTEXT); + + ItemStackWrapper ingredientWrapper = stack.getOrDefault(ModDataComponents.SKILLET_INGREDIENT.get(), ItemStackWrapper.EMPTY); + ItemStack ingredientStack = ingredientWrapper.getStack(); + Optional maybeIngredientRenderState = Optional.empty(); + if (!ingredientStack.isEmpty()) { + ItemStackRenderState ingredientRenderState = new ItemStackRenderState(); + Minecraft.getInstance().getItemModelResolver().updateForTopItem( + ingredientRenderState, + ingredientStack, + ItemDisplayContext.FIXED, + null, // TODO is this fine? + null, + 0 + ); + maybeIngredientRenderState = Optional.of(ingredientRenderState); + } + + Optional flipTime; + if (stack.has(ModDataComponents.SKILLET_FLIP_TIMESTAMP.get())) { + flipTime = Optional.of(stack.get(ModDataComponents.SKILLET_FLIP_TIMESTAMP.get())); + } else { + flipTime = Optional.empty(); + } + + // TODO how get? + boolean inGui = false; + boolean firstperson = false; + boolean skilletFlipped = stack.getOrDefault(ModDataComponents.SKILLET_FLIPPED.get(), false); + + return new SkilletItemRenderArgument(blockRenderState, maybeIngredientRenderState, flipTime, skilletFlipped, inGui, firstperson); + } + + public record SkilletItemRenderArgument( + BlockModelRenderState blockRenderState, + Optional maybeIngredientRenderState, + Optional flipTime, + boolean skilletFlipped, + boolean inGui, + boolean firstperson) { + + } + + public record ArmPoseTransformer(long skilletFlipTimestamp) implements IArmPoseTransformer { @Override - public void applyTransform(HumanoidModel model, LivingEntity entity, HumanoidArm arm) { - ItemStack stack = entity.getUseItem(); + public void applyTransform(HumanoidModel model, HumanoidRenderState entity, HumanoidArm arm) { + ItemStack stack = arm == HumanoidArm.LEFT ? entity.leftHandItemStack : entity.rightHandItemStack; + Minecraft mc = Minecraft.getInstance(); + if (stack.has(ModDataComponents.SKILLET_FLIP_TIMESTAMP.get())) { long time = stack.get(ModDataComponents.SKILLET_FLIP_TIMESTAMP.get()); - float partialTicks = Minecraft.getInstance().getTimer().getGameTimeDeltaPartialTick(false); - float animation = ((entity.level().getGameTime() - time) + partialTicks) / SkilletItem.FLIP_TIME; + float partialTicks = mc.getDeltaTracker().getGameTimeDeltaPartialTick(false); + float animation = ((mc.level.getGameTime() - time) + partialTicks) / SkilletItem.FLIP_TIME; animation = Mth.clamp(animation, 0, 1); if (arm == HumanoidArm.LEFT) { @@ -98,7 +154,22 @@ public void applyTransform(HumanoidModel model, LivingEntity entity, Humanoid } else { model.rightArm.xRot = (-Mth.sin(animation * Mth.TWO_PI) * 15 - 20) * (float) (Math.PI / 180.0); } - } + } + } + } + + public record Unbaked() implements SpecialModelRenderer.Unbaked { + public static final Unbaked INSTANCE = new Unbaked(); + public static final MapCodec MAP_CODEC = MapCodec.unit(INSTANCE); + + @Override + public MapCodec> type() { + return MAP_CODEC; + } + + @Override + public SpecialModelRenderer bake(BakingContext ctx) { + return new SkilletItemRenderer(ctx.sprites()); } } } From 652645df43292215fda4cc6c53e78f8a3b36f087 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Sun, 7 Jun 2026 17:57:41 +0100 Subject: [PATCH 19/25] SkilletItemRenderer.ArmPoseTransformer does not need that --- .../farmersdelight/client/renderer/SkilletItemRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletItemRenderer.java b/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletItemRenderer.java index 9cea2c936..5c221b8ac 100644 --- a/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletItemRenderer.java +++ b/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletItemRenderer.java @@ -137,7 +137,7 @@ public record SkilletItemRenderArgument( } - public record ArmPoseTransformer(long skilletFlipTimestamp) implements IArmPoseTransformer { + public record ArmPoseTransformer() implements IArmPoseTransformer { @Override public void applyTransform(HumanoidModel model, HumanoidRenderState entity, HumanoidArm arm) { ItemStack stack = arm == HumanoidArm.LEFT ? entity.leftHandItemStack : entity.rightHandItemStack; From e8d55f52d2c73f0604cb9329b1c35926f025216c Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Sun, 7 Jun 2026 17:59:15 +0100 Subject: [PATCH 20/25] SkilletBlockEntity --- .../client/renderer/SkilletRenderer.java | 2 +- .../common/block/SkilletBlock.java | 20 ++-- .../block/entity/SkilletBlockEntity.java | 91 +++++++++++-------- 3 files changed, 69 insertions(+), 44 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletRenderer.java b/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletRenderer.java index e86c94f41..612e82f8a 100644 --- a/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletRenderer.java +++ b/src/main/java/vectorwing/farmersdelight/client/renderer/SkilletRenderer.java @@ -83,7 +83,7 @@ public void extractRenderState(SkilletBlockEntity skillet, SkilletRenderState st BlockEntityRenderer.super.extractRenderState(skillet, state, partialTicks, cameraPosition, breakProgress); state.direction = skillet.getBlockState().getValue(StoveBlock.FACING); - ItemStack inventory = skillet.getInventory().getStackInSlot(0); + ItemStack inventory = skillet.getInventory().getResource(0).toStack(skillet.getInventory().getAmountAsInt(0)); state.contents = new ItemStackRenderState(); state.seed = state.contents.isEmpty() ? 187 : Item.getId(inventory.getItem()) + inventory.getDamageValue();; this.itemModelResolver.updateForTopItem( diff --git a/src/main/java/vectorwing/farmersdelight/common/block/SkilletBlock.java b/src/main/java/vectorwing/farmersdelight/common/block/SkilletBlock.java index 8da86cf8b..6a63ff9b4 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/SkilletBlock.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/SkilletBlock.java @@ -35,6 +35,7 @@ import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; +import net.neoforged.neoforge.transfer.item.ItemStacksResourceHandler; import vectorwing.farmersdelight.common.block.entity.SkilletBlockEntity; import vectorwing.farmersdelight.common.registry.ModBlockEntityTypes; import vectorwing.farmersdelight.common.registry.ModSounds; @@ -81,13 +82,17 @@ public InteractionResult useItemOn(ItemStack stack, BlockState state, Level leve } return InteractionResult.SUCCESS; } - ItemStack remainderStack = skillet.addItemToCook(heldStack, player); - if (remainderStack.getCount() != heldStack.getCount()) { - if (!player.isCreative()) { - player.setItemSlot(heldSlot, remainderStack); + if (level instanceof ServerLevel sLevel) { + ItemStack remainderStack = skillet.addItemToCook(heldStack, player, sLevel); + if (remainderStack.getCount() != heldStack.getCount()) { + if (!player.isCreative()) { + player.setItemSlot(heldSlot, remainderStack); + } + level.playSound(null, pos, SoundEvents.LANTERN_PLACE, SoundSource.BLOCKS, 0.7F, 1.0F); + return InteractionResult.SUCCESS; } - level.playSound(null, pos, SoundEvents.LANTERN_PLACE, SoundSource.BLOCKS, 0.7F, 1.0F); - return InteractionResult.SUCCESS; + } else { + return InteractionResult.SUCCESS_SERVER; } } return InteractionResult.PASS; @@ -101,7 +106,8 @@ public RenderShape getRenderShape(BlockState state) { @Override public void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) { if (level.getBlockEntity(pos) instanceof SkilletBlockEntity skillet) { - Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), skillet.getInventory().getStackInSlot(0)); + ItemStacksResourceHandler inventory = skillet.getInventory(); + Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), inventory.getResource(0).toStack(inventory.getAmountAsInt(0))); } super.affectNeighborsAfterRemoval(state, level, pos, movedByPiston); diff --git a/src/main/java/vectorwing/farmersdelight/common/block/entity/SkilletBlockEntity.java b/src/main/java/vectorwing/farmersdelight/common/block/entity/SkilletBlockEntity.java index f80328cba..21934838a 100644 --- a/src/main/java/vectorwing/farmersdelight/common/block/entity/SkilletBlockEntity.java +++ b/src/main/java/vectorwing/farmersdelight/common/block/entity/SkilletBlockEntity.java @@ -7,6 +7,7 @@ import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; @@ -19,8 +20,11 @@ import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; -import net.neoforged.neoforge.items.IItemHandler; -import net.neoforged.neoforge.items.ItemStackHandler; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.neoforged.neoforge.transfer.item.ItemResource; +import net.neoforged.neoforge.transfer.item.ItemStacksResourceHandler; +import net.neoforged.neoforge.transfer.transaction.Transaction; import vectorwing.farmersdelight.common.block.SkilletBlock; import vectorwing.farmersdelight.common.registry.ModBlockEntityTypes; import vectorwing.farmersdelight.common.registry.ModItems; @@ -33,7 +37,7 @@ public class SkilletBlockEntity extends SyncedBlockEntity implements HeatableBlockEntity, Clearable { - private final ItemStackHandler inventory = createHandler(); + private final ItemStacksResourceHandler inventory = createHandler(); private int cookingTime; private int cookingTimeTotal; @@ -60,8 +64,8 @@ public static void cookingTick(Level level, BlockPos pos, BlockState state, Skil ItemStack cookingStack = skillet.getStoredStack(); if (cookingStack.isEmpty()) { skillet.cookingTime = 0; - } else { - skillet.cookAndOutputItems(cookingStack, level); + } else if (level instanceof ServerLevel sLevel) { + skillet.cookAndOutputItems(cookingStack, sLevel); } } else if (skillet.cookingTime > 0) { skillet.cookingTime = Mth.clamp(skillet.cookingTime - 2, 0, skillet.cookingTimeTotal); @@ -91,19 +95,22 @@ public static void animationTick(Level level, BlockPos pos, BlockState state, Sk } - private void cookAndOutputItems(ItemStack cookingStack, Level level) { + private void cookAndOutputItems(ItemStack cookingStack, ServerLevel level) { ++cookingTime; if (cookingTime >= cookingTimeTotal) { - Optional> recipe = getMatchingRecipe(cookingStack); + Optional> recipe = getMatchingRecipe(cookingStack, level); if (recipe.isPresent()) { - ItemStack resultStack = recipe.get().value().assemble(new SingleRecipeInput(cookingStack), level.registryAccess()); + ItemStack resultStack = recipe.get().value().assemble(new SingleRecipeInput(cookingStack)); Direction direction = getBlockState().getValue(SkilletBlock.FACING).getClockWise(); ItemUtils.spawnItemEntity(level, resultStack.copy(), worldPosition.getX() + 0.5, worldPosition.getY() + 0.3, worldPosition.getZ() + 0.5, direction.getStepX() * 0.08F, 0.25F, direction.getStepZ() * 0.08F); cookingTime = 0; - inventory.extractItem(0, 1, false); + try (Transaction tx = Transaction.openRoot()) { + inventory.extract(0, inventory.getResource(0), 1, tx); + tx.commit(); + } } } } @@ -119,29 +126,29 @@ public boolean isHeated() { return false; } - private Optional> getMatchingRecipe(ItemStack stack) { + private Optional> getMatchingRecipe(ItemStack stack, ServerLevel serverLevel) { if (level == null) return Optional.empty(); - return this.quickCheck.getRecipeFor(new SingleRecipeInput(stack), this.level); + return this.quickCheck.getRecipeFor(new SingleRecipeInput(stack), serverLevel); } @Override - public void loadAdditional(CompoundTag compound, HolderLookup.Provider registries) { - super.loadAdditional(compound, registries); - inventory.deserializeNBT(registries, compound.getCompound("Inventory")); - cookingTime = compound.getInt("CookTime"); - cookingTimeTotal = compound.getInt("CookTimeTotal"); - skilletStack = ItemStack.parseOptional(registries, compound.getCompound("Skillet")); - fireAspectLevel = EnchantmentHelper.getTagEnchantmentLevel(registries.holder(Enchantments.FIRE_ASPECT).get(), skilletStack); + protected void loadAdditional(ValueInput input) { + super.loadAdditional(input); + inventory.deserialize(input); + cookingTime = input.getInt("CookTime").orElse(0); + cookingTimeTotal = input.getInt("CookTimeTotal").orElse(0); + skilletStack = input.read("Skillet", ItemStack.CODEC).orElse(ItemStack.EMPTY); + fireAspectLevel = EnchantmentHelper.getTagEnchantmentLevel(input.lookup().holder(Enchantments.FIRE_ASPECT).get(), skilletStack); } @Override - public void saveAdditional(CompoundTag compound, HolderLookup.Provider registries) { - super.saveAdditional(compound, registries); - compound.put("Inventory", inventory.serializeNBT(registries)); - compound.putInt("CookTime", cookingTime); - compound.putInt("CookTimeTotal", cookingTimeTotal); + protected void saveAdditional(ValueOutput output) { + super.saveAdditional(output); + inventory.serialize(output); + output.putInt("CookTime", cookingTime); + output.putInt("CookTimeTotal", cookingTimeTotal); if (!skilletStack.isEmpty()) { - compound.put("Skillet", skilletStack.save(registries)); + output.store("Skillet", ItemStack.CODEC, skilletStack); } } @@ -160,17 +167,22 @@ public void setSkilletItem(ItemStack stack) { inventoryChanged(); } - public ItemStack addItemToCook(ItemStack addedStack, Player player) { - Optional> recipe = getMatchingRecipe(addedStack); + public ItemStack addItemToCook(ItemStack addedStack, Player player, ServerLevel serverLevel) { + Optional> recipe = getMatchingRecipe(addedStack, serverLevel); if (recipe.isPresent() && getStoredStack().isEmpty()) { if (getBlockState().getValue(SkilletBlock.WATERLOGGED)) { - player.displayClientMessage(TextUtils.block("skillet.underwater"), true); + player.sendOverlayMessage(TextUtils.block("skillet.underwater")); return addedStack; } boolean wasEmpty = getStoredStack().isEmpty(); - ItemStack remainderStack = inventory.insertItem(0, addedStack.copy(), false); + ItemStack remainderStack; + try (Transaction tx = Transaction.openRoot()) { + int inserted = inventory.insert(0, ItemResource.of(addedStack), addedStack.count(), tx); + remainderStack = addedStack.copyWithCount(addedStack.count() - inserted); + tx.commit(); + } if (!ItemStack.matches(remainderStack, addedStack)) { - cookingTimeTotal = SkilletBlock.getSkilletCookingTime(recipe.get().value().getCookingTime(), fireAspectLevel); + cookingTimeTotal = SkilletBlock.getSkilletCookingTime(recipe.get().value().cookingTime(), fireAspectLevel); cookingTime = 0; if (wasEmpty && level != null && isHeated(level, worldPosition)) { level.playSound(null, worldPosition.getX() + 0.5F, worldPosition.getY() + 0.5F, worldPosition.getZ() + 0.5F, ModSounds.BLOCK_SKILLET_ADD_FOOD.get(), SoundSource.BLOCKS, 0.8F, 1.0F); @@ -178,32 +190,39 @@ public ItemStack addItemToCook(ItemStack addedStack, Player player) { return remainderStack; } } else { - player.displayClientMessage(TextUtils.block("skillet.invalid_item"), true); + player.sendOverlayMessage(TextUtils.block("skillet.invalid_item")); } return addedStack; } public ItemStack removeItem() { - return inventory.extractItem(0, getStoredStack().getMaxStackSize(), false); + ItemStack withdrawn; + try (Transaction tx = Transaction.openRoot()){ + ItemResource content = inventory.getResource(0); + int amount = inventory.extract(content, getStoredStack().getMaxStackSize(), tx); + withdrawn = content.toStack(amount); + tx.commit(); + } + return withdrawn; } - public IItemHandler getInventory() { + public ItemStacksResourceHandler getInventory() { return inventory; } public ItemStack getStoredStack() { - return inventory.getStackInSlot(0); + return inventory.getResource(0).toStack(inventory.getAmountAsInt(0)); } public boolean hasStoredStack() { return !getStoredStack().isEmpty(); } - private ItemStackHandler createHandler() { - return new ItemStackHandler() + private ItemStacksResourceHandler createHandler() { + return new ItemStacksResourceHandler(0) { @Override - protected void onContentsChanged(int slot) { + protected void onContentsChanged(int index, ItemStack previousContents) { inventoryChanged(); } }; From 72a99b5d1e44ba85ea72d709624888bcc2cc1292 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Sun, 7 Jun 2026 18:25:27 +0100 Subject: [PATCH 21/25] ItemAbilityIngredient --- .../ingredient/ItemAbilityIngredient.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/common/crafting/ingredient/ItemAbilityIngredient.java b/src/main/java/vectorwing/farmersdelight/common/crafting/ingredient/ItemAbilityIngredient.java index 7cc1b436d..67a6e2c2b 100644 --- a/src/main/java/vectorwing/farmersdelight/common/crafting/ingredient/ItemAbilityIngredient.java +++ b/src/main/java/vectorwing/farmersdelight/common/crafting/ingredient/ItemAbilityIngredient.java @@ -3,9 +3,10 @@ import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Holder; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.Ingredient; import net.neoforged.neoforge.common.ItemAbility; import net.neoforged.neoforge.common.crafting.ICustomIngredient; import net.neoforged.neoforge.common.crafting.IngredientType; @@ -27,17 +28,17 @@ public class ItemAbilityIngredient implements ICustomIngredient ).apply(inst, ItemAbilityIngredient::new)); protected final ItemAbility itemAbility; - protected Stream itemStacks; + protected Stream> itemHolders; public ItemAbilityIngredient(ItemAbility itemAbility) { this.itemAbility = itemAbility; } protected void dissolve() { - if (this.itemStacks == null) { - itemStacks = BuiltInRegistries.ITEM.stream() - .map(ItemStack::new) - .filter(stack -> stack.canPerformAction(itemAbility)); + if (this.itemHolders == null) { + itemHolders = BuiltInRegistries.ITEM.stream() + .filter(item -> item.canPerformAction(new ItemStack(item), itemAbility)) + .map(BuiltInRegistries.ITEM::wrapAsHolder); } } @@ -47,9 +48,9 @@ public boolean test(@Nullable ItemStack stack) { } @Override - public Stream getItems() { + public Stream> items() { dissolve(); - return itemStacks; + return itemHolders; } @Override From 7884f9f4c16dc16b8d905ba04a5c32efdaeeb0a2 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Sun, 7 Jun 2026 18:33:07 +0100 Subject: [PATCH 22/25] Loot modifiers take a priority argument --- .../common/loot/modifier/PastrySlicingModifier.java | 10 +++++++--- .../common/loot/modifier/ReplaceItemModifier.java | 8 ++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/common/loot/modifier/PastrySlicingModifier.java b/src/main/java/vectorwing/farmersdelight/common/loot/modifier/PastrySlicingModifier.java index 06fe55538..02416c82e 100644 --- a/src/main/java/vectorwing/farmersdelight/common/loot/modifier/PastrySlicingModifier.java +++ b/src/main/java/vectorwing/farmersdelight/common/loot/modifier/PastrySlicingModifier.java @@ -38,15 +38,19 @@ public class PastrySlicingModifier extends LootModifier * If the block is a PieBlock, it drops up to 4 slices. * Otherwise, this does nothing. */ - public PastrySlicingModifier(LootItemCondition[] conditions, Item pastrySlice) { - super(conditions); + public PastrySlicingModifier(LootItemCondition[] conditions, int priority, Item pastrySlice) { + super(conditions, priority); this.pastrySlice = pastrySlice; } + public PastrySlicingModifier(LootItemCondition[] conditions, Item pastrySlice) { + this(conditions, IGlobalLootModifier.DEFAULT_PRIORITY, pastrySlice); + } + @Nonnull @Override protected ObjectArrayList doApply(ObjectArrayList generatedLoot, LootContext context) { - BlockState state = context.getParamOrNull(LootContextParams.BLOCK_STATE); + BlockState state = context.getOptionalParameter(LootContextParams.BLOCK_STATE); if (state != null) { Block targetBlock = state.getBlock(); if (targetBlock instanceof CakeBlock) { diff --git a/src/main/java/vectorwing/farmersdelight/common/loot/modifier/ReplaceItemModifier.java b/src/main/java/vectorwing/farmersdelight/common/loot/modifier/ReplaceItemModifier.java index 5a81cccb8..24f51c296 100644 --- a/src/main/java/vectorwing/farmersdelight/common/loot/modifier/ReplaceItemModifier.java +++ b/src/main/java/vectorwing/farmersdelight/common/loot/modifier/ReplaceItemModifier.java @@ -35,13 +35,17 @@ public class ReplaceItemModifier extends LootModifier /** * This loot modifier removes all instances of the specified item, replacing it by another specified addition. */ - public ReplaceItemModifier(LootItemCondition[] conditions, Item removedItem, Item addedItem, int addedCount) { - super(conditions); + public ReplaceItemModifier(LootItemCondition[] conditions, int priority, Item removedItem, Item addedItem, int addedCount) { + super(conditions, priority); this.removedItem = removedItem; this.addedItem = addedItem; this.addedCount = addedCount; } + public ReplaceItemModifier(LootItemCondition[] conditions, Item removedItem, Item addedItem, int addedCount) { + this(conditions, IGlobalLootModifier.DEFAULT_PRIORITY, removedItem, addedItem, addedCount); + } + @Override protected @NotNull ObjectArrayList doApply(ObjectArrayList generatedLoot, LootContext context) { ItemStack addedStack = new ItemStack(addedItem, addedCount); From 95b726847a3b1ebb7d23c6e4ee54e725da5f6eb1 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Sun, 7 Jun 2026 18:41:02 +0100 Subject: [PATCH 23/25] Remove RecipeUtils#getResultItem --- .../common/utility/RecipeUtils.java | 15 --------------- .../jei/category/CookingRecipeCategory.java | 3 ++- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/common/utility/RecipeUtils.java b/src/main/java/vectorwing/farmersdelight/common/utility/RecipeUtils.java index 0c2c25c31..f4c149e0c 100644 --- a/src/main/java/vectorwing/farmersdelight/common/utility/RecipeUtils.java +++ b/src/main/java/vectorwing/farmersdelight/common/utility/RecipeUtils.java @@ -1,28 +1,13 @@ package vectorwing.farmersdelight.common.utility; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Recipe; import vectorwing.farmersdelight.FarmersDelight; public class RecipeUtils { - // Copyright (c) 2014-2015 mezz - public static ItemStack getResultItem(Recipe recipe) { - Minecraft minecraft = Minecraft.getInstance(); - ClientLevel level = minecraft.level; - if (level == null) { - throw new NullPointerException("level must not be null."); - } - RegistryAccess registryAccess = level.registryAccess(); - return recipe.getResultItem(registryAccess); - } - public static Identifier FDLocation(String name) { return Identifier.fromNamespaceAndPath(FarmersDelight.MODID, name); } diff --git a/src/main/java/vectorwing/farmersdelight/integration/jei/category/CookingRecipeCategory.java b/src/main/java/vectorwing/farmersdelight/integration/jei/category/CookingRecipeCategory.java index 63b650d40..575b928a3 100644 --- a/src/main/java/vectorwing/farmersdelight/integration/jei/category/CookingRecipeCategory.java +++ b/src/main/java/vectorwing/farmersdelight/integration/jei/category/CookingRecipeCategory.java @@ -89,7 +89,8 @@ public IDrawable getIcon() { public void setRecipe(IRecipeLayoutBuilder builder, RecipeHolder holder, IFocusGroup focusGroup) { CookingPotRecipe recipe = holder.value(); NonNullList recipeIngredients = recipe.getIngredients(); - ItemStack resultStack = RecipeUtils.getResultItem(recipe); + ItemStack resultStack = recipe.assemble(null); // This is usually very bad, but CookingPotRecipe always + // has the same output, so it's fine to pass null in this case. ItemStack containerStack = recipe.getOutputContainer(); int borderSlotSize = 18; From 89fe6d3527472ecbe2916ba2ce0e442620436454 Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Sun, 7 Jun 2026 19:15:59 +0100 Subject: [PATCH 24/25] Stop referencing ClientSetupEvents::init --- src/main/java/vectorwing/farmersdelight/FarmersDelight.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/vectorwing/farmersdelight/FarmersDelight.java b/src/main/java/vectorwing/farmersdelight/FarmersDelight.java index 35f8bc5da..bfd696f6b 100644 --- a/src/main/java/vectorwing/farmersdelight/FarmersDelight.java +++ b/src/main/java/vectorwing/farmersdelight/FarmersDelight.java @@ -30,7 +30,8 @@ public static Identifier id(String name) { public FarmersDelight(IEventBus modEventBus, ModContainer modContainer) { modEventBus.addListener(CommonSetup::init); if (FMLEnvironment.getDist().isClient()) { - modEventBus.addListener(ClientSetupEvents::init); + // TODO may be unnecessary? + //modEventBus.addListener(ClientSetupEvents::init); modContainer.registerExtensionPoint(IConfigScreenFactory.class, ConfigurationScreen::new); } From 9fb83bdb4f619b9613d0193a74bce6f0e92ebdbe Mon Sep 17 00:00:00 2001 From: AyOhEe <74308087+AyOhEe@users.noreply.github.com> Date: Sun, 7 Jun 2026 19:27:08 +0100 Subject: [PATCH 25/25] FDRecipes only initialises JEI on server Only the server is properly aware of recipes, now, it seems. I'm really not a fan of stealing and keeping a server reference like that. --- .../common/event/CommonEvents.java | 8 ++++ .../integration/jei/FDRecipes.java | 38 +++++++++++++------ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/main/java/vectorwing/farmersdelight/common/event/CommonEvents.java b/src/main/java/vectorwing/farmersdelight/common/event/CommonEvents.java index 9491e94ba..25f82a9eb 100644 --- a/src/main/java/vectorwing/farmersdelight/common/event/CommonEvents.java +++ b/src/main/java/vectorwing/farmersdelight/common/event/CommonEvents.java @@ -12,12 +12,14 @@ import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.event.entity.living.LivingEntityUseItemEvent; import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; import net.neoforged.neoforge.network.PacketDistributor; import vectorwing.farmersdelight.FarmersDelight; import vectorwing.farmersdelight.common.Configuration; import vectorwing.farmersdelight.common.FoodValues; import vectorwing.farmersdelight.common.network.payload.NaturalRegenerationGameRulePayload; +import vectorwing.farmersdelight.integration.jei.FDRecipes; @EventBusSubscriber(modid = FarmersDelight.MODID) public class CommonEvents @@ -62,6 +64,12 @@ public void onServerWorldTick(ServerTickEvent.Post event) { } } + // TODO this absolutely sucks but might be necessary for JEI recipe registration + @SubscribeEvent + public void onServerAboutToStart(ServerAboutToStartEvent event) { + FDRecipes.SERVER = event.getServer(); + } + private static void sendPayload(ServerPlayer player, CustomPacketPayload payload) { if (!player.connection.hasChannel(payload.type().id())) { return; diff --git a/src/main/java/vectorwing/farmersdelight/integration/jei/FDRecipes.java b/src/main/java/vectorwing/farmersdelight/integration/jei/FDRecipes.java index 0a76eb956..b1da47797 100644 --- a/src/main/java/vectorwing/farmersdelight/integration/jei/FDRecipes.java +++ b/src/main/java/vectorwing/farmersdelight/integration/jei/FDRecipes.java @@ -4,7 +4,13 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.NonNullList; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; import net.minecraft.world.item.Items; import net.minecraft.world.item.crafting.*; import net.minecraft.world.level.ItemLike; @@ -19,26 +25,30 @@ public class FDRecipes { - // TODO: RecipeAccess isn't the same thing. Maybe you'll have to access the registries directly? - private final RecipeAccess recipeManager; + // TODO this absolutely sucks but might be necessary for JEI recipe registration + public static MinecraftServer SERVER; + private final RecipeManager recipeManager; public FDRecipes() { - Minecraft minecraft = Minecraft.getInstance(); - ClientLevel level = minecraft.level; - - if (level != null) { - this.recipeManager = level.recipeAccess(); + if (SERVER != null) { + this.recipeManager = SERVER.getRecipeManager(); } else { throw new NullPointerException("Minecraft level must not be null."); } } public List> getCookingPotRecipes() { - return recipeManager.getAllRecipesFor(ModRecipeTypes.COOKING.get()); + return recipeManager.getRecipes().stream() + .filter(r -> r.value().getType() == ModRecipeTypes.COOKING.get()) + .map(r -> (RecipeHolder) (r) ) + .toList(); } public List> getCuttingBoardRecipes() { - return recipeManager.getAllRecipesFor(ModRecipeTypes.CUTTING.get()); + return recipeManager.getRecipes().stream() + .filter(r -> r.value().getType() == ModRecipeTypes.CUTTING.get()) + .map(r -> (RecipeHolder) (r) ) + .toList(); } public List> getSpecialCraftingRecipes() { @@ -57,10 +67,16 @@ public List> getSpecialCraftingRecipes() { } public void addValidatedSpecialRecipe(List> recipeList, String recipeId, String group, NonNullList inputs, ItemLike output) { - Optional> specialRecipe = recipeManager.byKey(RecipeUtils.FDLocation(recipeId)); + Optional> specialRecipe = recipeManager.byKey(ResourceKey.create(Registries.RECIPE, RecipeUtils.FDLocation(recipeId))); specialRecipe.ifPresent((recipe) -> { - recipeList.add(new RecipeHolder<>(specialRecipe.get().id(), new ShapelessRecipe(group, CraftingBookCategory.MISC, new ItemStack(output.asItem()), inputs))); + ShapelessRecipe shapeless = new ShapelessRecipe( + new Recipe.CommonInfo(false), + new CraftingRecipe.CraftingBookInfo(CraftingBookCategory.MISC, group), + ItemStackTemplate.fromNonEmptyStack(new ItemStack(output)), + inputs + ); + recipeList.add(new RecipeHolder<>(specialRecipe.get().id(), shapeless)); }); } }