Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/client/java/com/tcm/MineTale/MineTaleClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import net.minecraft.client.gui.screens.MenuScreens;

public class MineTaleClient implements ClientModInitializer {



/**
* Registers client-side screen factories for custom workbench menu types.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
package com.tcm.MineTale.block.workbenches.screen;

import java.util.List;

import com.tcm.MineTale.MineTale;
import com.tcm.MineTale.block.workbenches.menu.CampfireWorkbenchMenu;
import com.tcm.MineTale.recipe.MineTaleRecipeBookComponent;
import com.tcm.MineTale.registry.ModBlocks;
import com.tcm.MineTale.registry.ModRecipeDisplay;

import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.gui.navigation.ScreenPosition;
import net.minecraft.client.gui.screens.inventory.AbstractRecipeBookScreen;
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.resources.Identifier;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack;
import net.minecraft.network.chat.Component;

public class CampfireWorkbenchScreen extends AbstractContainerScreen<CampfireWorkbenchMenu> {
public class CampfireWorkbenchScreen extends AbstractRecipeBookScreen<CampfireWorkbenchMenu> {
private static final Identifier TEXTURE =
Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "textures/gui/container/furnace_workbench.png");

/**
* Creates a campfire workbench screen for the provided menu, player inventory, and title.
*
Expand All @@ -21,16 +30,33 @@ public class CampfireWorkbenchScreen extends AbstractContainerScreen<CampfireWor
* @param title the title component shown at the top of the screen
*/
public CampfireWorkbenchScreen(CampfireWorkbenchMenu menu, Inventory inventory, Component title) {
super(menu, inventory, title);
super(menu, createRecipeBookComponent(menu), inventory, title);
}

/**
* Static helper to build the component with the custom MineTale tabs
* before the super constructor is called.
*/
private static MineTaleRecipeBookComponent createRecipeBookComponent(CampfireWorkbenchMenu menu) {
ItemStack tabIcon = new ItemStack(ModBlocks.CAMPFIRE_WORKBENCH_BLOCK.asItem());

List<RecipeBookComponent.TabInfo> tabs = List.of(
new RecipeBookComponent.TabInfo(tabIcon.getItem(), ModRecipeDisplay.CAMPFIRE_SEARCH)
);

return new MineTaleRecipeBookComponent(menu, tabs);
}

/**
* Initializes the screen and centers the title horizontally by setting {@code titleLabelX}.
*/
@Override
protected void init() {
// Important: Set your GUI size before super.init()
this.imageWidth = 176;
this.imageHeight = 166;

super.init();
this.titleLabelX = (this.imageWidth - this.font.width(this.title)) / 2;
}

/**
Expand All @@ -57,8 +83,25 @@ protected void renderBg(GuiGraphics guiGraphics, float f, int i, int j) {
*/
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
// 1. Always render the dark background tint first
renderBackground(graphics, mouseX, mouseY, delta);

// 3. Call super (this draws your slots and items)
super.render(graphics, mouseX, mouseY, delta);

renderTooltip(graphics, mouseX, mouseY);
}

@Override
protected ScreenPosition getRecipeBookButtonPosition() {
// 1. Calculate the start (left) of your workbench GUI
int guiLeft = (this.width - this.imageWidth) / 2;

// 2. Calculate the top of your workbench GUI
int guiTop = (this.height - this.imageHeight) / 2;

// 3. Standard Vanilla positioning:
// Usually 5 pixels in from the left and 49 pixels up from the center
return new ScreenPosition(guiLeft + 5, guiTop + this.imageHeight / 2 - 49);
}
}
Original file line number Diff line number Diff line change
@@ -1,65 +1,104 @@
package com.tcm.MineTale.block.workbenches.screen;

import java.util.List;

import com.tcm.MineTale.MineTale;
import com.tcm.MineTale.block.workbenches.menu.FurnaceWorkbenchMenu;
import com.tcm.MineTale.recipe.MineTaleRecipeBookComponent;
import com.tcm.MineTale.registry.ModBlocks;

import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.gui.navigation.ScreenPosition;
import net.minecraft.client.gui.screens.inventory.AbstractRecipeBookScreen;
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.resources.Identifier;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeBookCategories;
import net.minecraft.network.chat.Component;

public class FurnaceWorkbenchScreen extends AbstractContainerScreen<FurnaceWorkbenchMenu> {
public class FurnaceWorkbenchScreen extends AbstractRecipeBookScreen<FurnaceWorkbenchMenu> {
private static final Identifier TEXTURE =
Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "textures/gui/container/furnace_workbench.png");

/**
* Creates a new furnace workbench screen for the given menu, player inventory, and title.
*
* @param menu the container menu that provides slots and syncs state for this screen
* @param inventory the player's inventory to display and interact with
* @param title the title component shown at the top of the screen
* Creates a new furnace workbench screen.
* Note: recipeBookComponent is inherited from AbstractRecipeBookScreen.
*/
public FurnaceWorkbenchScreen(FurnaceWorkbenchMenu menu, Inventory inventory, Component title) {
super(menu, inventory, title);
super(menu, createRecipeBookComponent(menu), inventory, title);
}

/**
* Initializes the screen and centers the title horizontally by setting {@code titleLabelX}.
* Static helper to build the component with the custom MineTale tabs.
* This uses the FURNACE_WORKBENCH icon for this specific screen's tab.
*/
private static MineTaleRecipeBookComponent createRecipeBookComponent(FurnaceWorkbenchMenu menu) {
ItemStack tabIcon = new ItemStack(ModBlocks.FURNACE_WORKBENCH_BLOCK_T1.asItem());

List<RecipeBookComponent.TabInfo> tabs = List.of(
new RecipeBookComponent.TabInfo(tabIcon.getItem(), RecipeBookCategories.CRAFTING_MISC)
);

return new MineTaleRecipeBookComponent(menu, tabs);
}

@Override
protected void init() {
this.imageWidth = 176;
this.imageHeight = 166;

super.init();
this.titleLabelX = (this.imageWidth - this.font.width(this.title)) / 2;

// // Initialize the inherited recipeBookComponent UI state
// this.recipeBookComponent.init(this.width, this.height, this.minecraft, false);
// this.leftPos = this.recipeBookComponent.updateScreenPosition(this.width, this.imageWidth);

// // The toggle button is managed via getRecipeBookButtonPosition() in 1.21.1
// // but we add the ImageButton manually to match your CampfireWorkbenchScreen logic exactly.
// this.addRenderableWidget(new ImageButton(
// this.leftPos + 5,
// this.height / 2 - 49,
// 20, 18,
// RecipeBookComponent.RECIPE_BUTTON_SPRITES,
// (button) -> {
// this.recipeBookComponent.toggleVisibility();
// this.leftPos = this.recipeBookComponent.updateScreenPosition(this.width, this.imageWidth);
// button.setPosition(this.leftPos + 5, this.height / 2 - 49);
// }
// ));
}

/**
* Draws the furnace workbench background texture onto the screen.
*
* @param guiGraphics the graphics context used for drawing
* @param f partial tick time used for interpolation
* @param i current mouse x position
* @param j current mouse y position
*/
protected void renderBg(GuiGraphics guiGraphics, float f, int i, int j) {
int k = this.leftPos;
int l = this.topPos;
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, k, l, 0.0F, 0.0F, this.imageWidth, this.imageHeight, 256, 256);
}
@Override
protected void renderBg(GuiGraphics guiGraphics, float f, int i, int j) {
int k = this.leftPos;
int l = this.topPos;
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, k, l, 0.0F, 0.0F, this.imageWidth, this.imageHeight, 256, 256);
}

/**
* Renders the furnace workbench screen, drawing its background, contents, and tooltips.
*
* @param graphics the graphics context used for rendering
* @param mouseX the current mouse X coordinate
* @param mouseY the current mouse Y coordinate
* @param delta the frame time delta (partial tick) used for animated rendering
*/
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
renderBackground(graphics, mouseX, mouseY, delta);

// if (this.recipeBookComponent != null) {
// this.recipeBookComponent.render(graphics, mouseX, mouseY, delta);
// }

super.render(graphics, mouseX, mouseY, delta);

// if (this.recipeBookComponent != null) {
// this.recipeBookComponent.renderGhostRecipe(graphics, true);
// this.recipeBookComponent.renderTooltip(graphics, this.leftPos, this.topPos, this.hoveredSlot);
// }

renderTooltip(graphics, mouseX, mouseY);
}

@Override
protected ScreenPosition getRecipeBookButtonPosition() {
int guiLeft = (this.width - this.imageWidth) / 2;
int guiTop = (this.height - this.imageHeight) / 2;
return new ScreenPosition(guiLeft + 5, guiTop + this.imageHeight / 2 - 49);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.tcm.MineTale.mixin.client;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import com.tcm.MineTale.registry.ModRecipeDisplay;
import com.tcm.MineTale.registry.ModRecipes;

import net.minecraft.client.ClientRecipeBook;
import net.minecraft.world.item.crafting.RecipeBookCategory;
import net.minecraft.world.item.crafting.RecipeHolder;

@Mixin(ClientRecipeBook.class)
public abstract class ClientRecipeBookMixin {
@Inject(method = "getCategory", at = @At("HEAD"), cancellable = true)
private static void minetale$addCustomCategory(RecipeHolder<?> recipe, CallbackInfoReturnable<RecipeBookCategory> cir) {
if (recipe.value().getType() == ModRecipes.FURNACE_SERIALIZER) {
// This tells the search engine to put your recipes into your custom tab
cir.setReturnValue(ModRecipeDisplay.CAMPFIRE_SEARCH);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.tcm.MineTale.recipe;

import java.util.List;

import com.tcm.MineTale.registry.ModRecipeDisplay;
import com.tcm.MineTale.util.Constants;

import net.minecraft.client.gui.components.WidgetSprites;
import net.minecraft.client.gui.screens.recipebook.GhostSlots;
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.util.context.ContextMap;
import net.minecraft.world.entity.player.StackedItemContents;
import net.minecraft.world.inventory.RecipeBookMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.crafting.display.RecipeDisplay;

public class MineTaleRecipeBookComponent extends RecipeBookComponent<RecipeBookMenu> {

// Standard button sprites (the "Filter" checkmark button)
protected static final WidgetSprites FILTER_BUTTON_SPRITES = new WidgetSprites(
Identifier.withDefaultNamespace("recipe_book/filter_enabled"),
Identifier.withDefaultNamespace("recipe_book/filter_disabled"),
Identifier.withDefaultNamespace("recipe_book/filter_enabled_focused"),
Identifier.withDefaultNamespace("recipe_book/filter_disabled_focused")
);

public MineTaleRecipeBookComponent(RecipeBookMenu recipeBookMenu, List<TabInfo> list) {
super(recipeBookMenu, list);
}

@Override
protected void selectMatchingRecipes(RecipeCollection recipeCollection, StackedItemContents stackedItemContents) {
// Force everything to be "selected"
// recipeCollection.selectRecipes(stackedItemContents, (recipeDisplay) -> true);

recipeCollection.selectRecipes(stackedItemContents, (recipeDisplay) -> {
// Only allow recipes that use your custom Workbench display type
// This effectively filters out vanilla CraftingRecipeDisplays (the boats)
return recipeDisplay.type() == ModRecipeDisplay.WORKBENCH_TYPE;
});
}



@Override
protected WidgetSprites getFilterButtonTextures() {
// Returns the textures for the "Toggle craftable" button
return FILTER_BUTTON_SPRITES;
}

@Override
protected boolean isCraftingSlot(Slot slot) {
return slot.index == Constants.INPUT_START || slot.index == Constants.INPUT_START + 1;
}

@Override
protected Component getRecipeFilterName() {
// The text shown when hovering over the filter button
return Component.translatable("gui.recipebook.toggleRecipes.all");
}

@Override
protected void fillGhostRecipe(GhostSlots ghostSlots, RecipeDisplay recipeDisplay, ContextMap contextMap) {
// This places the faint "ghost" items in the workbench slots when hovering a recipe
// We use SlotDisplayContext.fromLevel(this.minecraft.level) to handle dynamic displays
// ghostSlots.setRecipe(recipeDisplay);

// // We assume the first two slots of your menu are the inputs
// // Your AbstractWorkbenchContainerMenu adds input slots first (index 0 and 1)
// ghostSlots.addSlot(this.menu.slots.get(0), this.minecraft.level.registryAccess(), recipeDisplay.result());

// // If your custom recipe has specific inputs, you'd map them here.
// // For a generic implementation, we use the display's suggested placement:
// recipeDisplay.setupGhostSlots(ghostSlots, SlotDisplayContext.fromLevel(this.minecraft.level));
}
Comment on lines +65 to +78
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Empty fillGhostRecipe implementation breaks ghost recipe display.

The method body is entirely commented out, so hovering over recipes in the book won't show ghost items in the crafting slots. This degrades the recipe book UX—users expect to see ingredient hints.

Either implement the ghost slot logic or add a clear // TODO: explaining why it's deferred and what's blocking completion.

Would you like help implementing the ghost recipe logic based on the commented hints?

🤖 Prompt for AI Agents
In `@src/client/java/com/tcm/MineTale/recipe/MineTaleRecipeBookComponent.java`
around lines 65 - 78, fillGhostRecipe is currently empty which prevents ghost
items from showing; implement the ghost-slot population using the commented
hints: call recipeDisplay.setupGhostSlots(ghostSlots,
SlotDisplayContext.fromLevel(this.minecraft.level)) to let the display populate
slots, and if your menu requires manual mapping also add
ghostSlots.addSlot(this.menu.slots.get(0),
this.minecraft.level.registryAccess(), recipeDisplay.result()) (and any
additional input slots from this.menu.slots) before/after setupGhostSlots as
appropriate; alternatively, if you cannot implement now add a clear TODO comment
in fillGhostRecipe explaining why it's deferred and what inputs (e.g.,
SlotDisplayContext or menu slot ordering) are needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore for now not sure how to do it due to GhostSlots functions all being private.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@The-Code-Monkey, understood! I see you've already created GhostSlotsProxy.java in this PR to work around the package-private methods in GhostSlots (with setInputProxy and setResultProxy). If you need additional GhostSlots methods exposed, you can add more proxy methods to that class following the same pattern.

Feel free to implement this when you're ready or when you've figured out the full set of methods needed for the ghost recipe logic.


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package net.minecraft.client.gui.screens.recipebook;

import net.minecraft.world.inventory.Slot;
import net.minecraft.util.context.ContextMap;
import net.minecraft.world.item.crafting.display.SlotDisplay;

public class GhostSlotsProxy {
public static void setInputProxy(GhostSlots ghostSlots, Slot slot, ContextMap contextMap, SlotDisplay display) {
// Because this class is in the same package, it can see protected methods!
ghostSlots.setInput(slot, contextMap, display);
}

public static void setResultProxy(GhostSlots ghostSlots, Slot slot, ContextMap contextMap, SlotDisplay display) {
ghostSlots.setResult(slot, contextMap, display);
}
}
Loading