Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
13 changes: 7 additions & 6 deletions src/client/java/com/tcm/MineTale/MineTaleClient.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
package com.tcm.MineTale;

import com.tcm.MineTale.block.workbenches.screen.FurnaceWorkbenchScreen;
import com.tcm.MineTale.block.workbenches.screen.WorkbenchWorkbenchScreen;
import com.tcm.MineTale.block.workbenches.screen.CampfireWorkbenchScreen;
import com.tcm.MineTale.registry.ModMenuTypes;

import net.fabricmc.api.ClientModInitializer;
import net.minecraft.client.gui.screens.MenuScreens;

public class MineTaleClient implements ClientModInitializer {



/**
* Registers client-side screen factories for custom workbench menu types.
* Register client-side screen factories for custom workbench menu types.
*
* Binds the furnace and campfire workbench menu types to their corresponding screen constructors
* so the client can create the appropriate GUI when those menus are opened.
* Binds ModMenuTypes.FURNACE_WORKBENCH_MENU to FurnaceWorkbenchScreen,
* ModMenuTypes.CAMPFIRE_WORKBENCH_MENU to CampfireWorkbenchScreen, and
* ModMenuTypes.WORKBENCH_WORKBENCH_MENU to WorkbenchWorkbenchScreen so the client
* can create the appropriate GUI when those menus open.
*/
@Override
public void onInitializeClient() {
MenuScreens.register(ModMenuTypes.FURNACE_WORKBENCH_MENU, FurnaceWorkbenchScreen::new);
MenuScreens.register(ModMenuTypes.CAMPFIRE_WORKBENCH_MENU, CampfireWorkbenchScreen::new);
MenuScreens.register(ModMenuTypes.WORKBENCH_WORKBENCH_MENU, WorkbenchWorkbenchScreen::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
package com.tcm.MineTale.block.workbenches.screen;

import java.util.List;

import com.tcm.MineTale.MineTale;
import com.tcm.MineTale.block.workbenches.menu.WorkbenchWorkbenchMenu;
import com.tcm.MineTale.mixin.client.RecipeBookComponentAccessor;
import com.tcm.MineTale.network.CraftRequestPayload;
import com.tcm.MineTale.recipe.MineTaleRecipeBookComponent;
import com.tcm.MineTale.registry.ModBlocks;
import com.tcm.MineTale.registry.ModRecipeDisplay;
import com.tcm.MineTale.registry.ModRecipes;

import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
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.gui.screens.recipebook.RecipeBookPage;
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
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.display.RecipeDisplayEntry;
import net.minecraft.world.item.crafting.display.RecipeDisplayId;
import net.minecraft.world.item.crafting.display.SlotDisplayContext;
import net.minecraft.network.chat.Component;

public class WorkbenchWorkbenchScreen extends AbstractRecipeBookScreen<WorkbenchWorkbenchMenu> {
private static final Identifier TEXTURE =
Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "textures/gui/container/furnace_workbench.png");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

private final MineTaleRecipeBookComponent mineTaleRecipeBook;

private Button craftOneBtn;
private Button craftThirtyBtn;
private Button craftAllBtn;

/**
* Initialize a workbench GUI screen using the provided container menu, player inventory, and title.
*
* @param menu the menu supplying slots and synchronized 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
*/
public WorkbenchWorkbenchScreen(WorkbenchWorkbenchMenu menu, Inventory inventory, Component title) {
this(menu, inventory, title, createRecipeBookComponent(menu));
}

/**
* Creates a WorkbenchWorkbenchScreen bound to the given menu, player inventory, title, and recipe book component.
*
* @param menu the menu backing this screen
* @param inventory the player's inventory shown in the screen
* @param title the screen title component
* @param recipeBook the MineTaleRecipeBookComponent used to display and manage recipes in this screen
*/
private WorkbenchWorkbenchScreen(WorkbenchWorkbenchMenu menu, Inventory inventory, Component title, MineTaleRecipeBookComponent recipeBook) {
super(menu, recipeBook, inventory, title);
this.mineTaleRecipeBook = recipeBook;
}

/**
* Create a MineTaleRecipeBookComponent configured for the workbench screen.
*
* @param menu the workbench menu used to initialize the recipe book component
* @return a MineTaleRecipeBookComponent containing the workbench tab and associated recipe category
*/
private static MineTaleRecipeBookComponent createRecipeBookComponent(WorkbenchWorkbenchMenu menu) {
ItemStack tabIcon = new ItemStack(ModBlocks.WORKBENCH_WORKBENCH_BLOCK.asItem());

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

return new MineTaleRecipeBookComponent(menu, tabs, ModRecipes.WORKBENCH_TYPE);
}

/**
* Configure the screen's GUI dimensions and initialize widgets.
*
* Sets the layout size (imageWidth = 176, imageHeight = 166), delegates remaining
* layout initialization to the superclass, and creates the three craft buttons
* ("1", "30", "All") wired to their respective handlers.
*/
@Override
protected void init() {
// Important: Set your GUI size before super.init()
this.imageWidth = 176;
this.imageHeight = 166;

super.init();

this.craftOneBtn = addRenderableWidget(Button.builder(Component.literal("1"), (button) -> {
handleCraftRequest(1);
}).bounds(this.leftPos + 80, this.topPos + 20, 30, 20).build());

this.craftThirtyBtn = addRenderableWidget(Button.builder(Component.literal("30"), (button) -> {
handleCraftRequest(30);
}).bounds(this.leftPos + 112, this.topPos + 20, 30, 20).build());

this.craftAllBtn = addRenderableWidget(Button.builder(Component.literal("All"), (button) -> {
handleCraftRequest(-1); // -1 represents "All" logic
}).bounds(this.leftPos + 144, this.topPos + 20, 30, 20).build());
}

// private void handleCraftRequest(int amount) {
// RecipeBookPage page = ((RecipeBookComponentAccessor)this.mineTaleRecipeBook).getRecipeBookPage();
// RecipeCollection collection = page.getLastClickedRecipeCollection();
// RecipeDisplayId displayId = page.getLastClickedRecipe();

// if (collection != null && displayId != null) {
// // 1. Find the specific entry that was clicked
// for (RecipeDisplayEntry entry : collection.getSelectedRecipes(RecipeCollection.CraftableStatus.ANY)) {
// if (entry.id().equals(displayId)) {
// // 2. Resolve the visual result into an actual ItemStack
// List<ItemStack> results = entry.resultItems(SlotDisplayContext.fromLevel(this.minecraft.level));

// if (!results.isEmpty()) {
// ItemStack resultStack = results.get(0);
// // 3. Send the item and amount to the server
// // Note: Update your CraftRequestPayload to accept ItemStack instead of Identifier
// ClientPlayNetworking.send(new CraftRequestPayload(resultStack, amount));
// }
// break;
// }
// }
// }
// }

// private void handleCraftRequest(int amount) {
// // 1. Get the current page from the recipe book
// // We use your mixin/accessor to get the internal page object
// RecipeBookPage page = ((RecipeBookComponentAccessor)this.mineTaleRecipeBook).getRecipeBookPage();

// // 2. Identify WHAT was clicked
// RecipeCollection collection = page.getLastClickedRecipeCollection();
// RecipeDisplayId displayId = page.getLastClickedRecipe();

// if (collection != null && displayId != null) {
// // 3. Find the display entry
// for (RecipeDisplayEntry entry : collection.getSelectedRecipes(RecipeCollection.CraftableStatus.ANY)) {
// if (entry.id().equals(displayId)) {
// // 4. Get the result item (the Chest)
// // 1.21.1 uses SlotDisplayContext to handle dynamic results
// List<ItemStack> results = entry.resultItems(SlotDisplayContext.fromLevel(this.minecraft.level));

// if (!results.isEmpty()) {
// ItemStack resultStack = results.get(0);

// // 5. Send the packet to the Server
// // IMPORTANT: Ensure your CraftRequestPayload is registered to handle
// // an ItemStack and an Int.
// ClientPlayNetworking.send(new CraftRequestPayload(resultStack, amount));

// // Optional: Play a click sound so the player knows it worked
// this.minecraft.getSoundManager().play(net.minecraft.client.resources.sounds.SimpleSoundInstance.forUI(
// net.minecraft.sounds.SoundEvents.UI_BUTTON_CLICK, 1.0F));
// }
// break;
// }
// }
// }
// }
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

/**
* Sends a crafting request for the currently selected recipe in the integrated recipe book.
*
* Locates the last recipe collection and last selected recipe ID from the recipe book component,
* resolves the recipe's result item, and sends a CraftRequestPayload to the server containing that
* item and the requested amount.
*
* @param amount the quantity to craft; use -1 to request crafting of the full available stack ("All")
*/

private void handleCraftRequest(int amount) {
// 1. Cast the book component to the Accessor to get the selected data
RecipeBookComponentAccessor accessor = (RecipeBookComponentAccessor) this.mineTaleRecipeBook;

RecipeCollection collection = accessor.getLastRecipeCollection();
RecipeDisplayId displayId = accessor.getLastRecipe();

if (collection != null && displayId != null) {
// 2. Find the visual entry
for (RecipeDisplayEntry entry : collection.getSelectedRecipes(RecipeCollection.CraftableStatus.ANY)) {
if (entry.id().equals(displayId)) {
// 3. Resolve result for the packet
List<ItemStack> results = entry.resultItems(SlotDisplayContext.fromLevel(this.minecraft.level));

if (!results.isEmpty()) {
ItemStack resultStack = results.get(0);

// 4. LOG FOR DEBUGGING: Does this print in your console?
System.out.println("Sending craft request for: " + resultStack + " amount: " + amount);
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Feb 6, 2026

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace System.out.println with a proper logger.

Debug prints via System.out.println should not ship to production. Use SLF4J (MineTale.LOGGER or a class-level logger) so log level can be controlled at runtime.

Proposed fix
-                        // 4. LOG FOR DEBUGGING: Does this print in your console?
-                        System.out.println("Sending craft request for: " + resultStack + " amount: " + amount);
+                        MineTale.LOGGER.debug("Sending craft request for: {} amount: {}", resultStack, amount);
-            System.out.println("Request failed: Collection or DisplayID is null!");
+            MineTale.LOGGER.debug("Craft request skipped: collection or displayId is null");

Also applies to: 186-188

🤖 Prompt for AI Agents
In
`@src/client/java/com/tcm/MineTale/block/workbenches/screen/WorkbenchWorkbenchScreen.java`
around lines 178 - 179, Replace the System.out.println debug prints in
WorkbenchWorkbenchScreen (the println that logs "Sending craft request for: "
and the similar prints later) with SLF4J logging: use the existing
MineTale.LOGGER or create a private static final Logger for
WorkbenchWorkbenchScreen and call the appropriate level (debug or info) with a
formatted message including resultStack and amount; remove the
System.out.println calls and ensure imports/reference to MineTale.LOGGER are
used so log level can be controlled at runtime.

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.

I want to keep this for now.

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'll leave the debug logging as-is for now.


🧠 Learnings used
Learnt from: The-Code-Monkey
Repo: CodeMonkeysMods/MineTale PR: 28
File: src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java:54-57
Timestamp: 2026-02-06T08:19:40.485Z
Learning: In FurnaceWorkbenchMenu.java, returning RecipeBookType.CRAFTING from getRecipeBookType() is acceptable because the custom MineTaleRecipeBookComponent filters recipes by both display type and the specific recipe type (FURNACE_T1_TYPE), preventing these custom recipes from appearing in vanilla recipe book tabs.


ClientPlayNetworking.send(new CraftRequestPayload(resultStack, amount));
}
break;
}
}
} else {
System.out.println("Request failed: Collection or DisplayID is null!");
}
}

/**
* Draws the workbench GUI background texture at the screen's top-left corner.
*
* @param guiGraphics the graphics context used to draw GUI elements
* @param f partial tick time for interpolation
* @param i current mouse x coordinate relative to the window
* @param j current mouse y coordinate relative to the window
*/
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 workbench screen including the background tint, GUI elements, and tooltips.
*
* @param graphics the graphics context
* @param mouseX the current mouse x-coordinate
* @param mouseY the current mouse y-coordinate
* @param delta the partial tick delta for frame interpolation
*/
@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);

boolean hasSelection = this.mineTaleRecipeBook.getSelectedRecipeId() != null;
this.craftOneBtn.active = hasSelection;
this.craftThirtyBtn.active = hasSelection;
this.craftAllBtn.active = hasSelection;

renderTooltip(graphics, mouseX, mouseY);
}

/**
* Computes the on-screen position for the recipe book toggle button for this GUI.
*
* @return the screen position placed 5 pixels from the GUI's left edge and 49 pixels above the GUI's vertical center
*/
@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);
}
}
37 changes: 27 additions & 10 deletions src/client/java/com/tcm/MineTale/datagen/ModRecipeProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import net.minecraft.core.HolderLookup;
import net.minecraft.data.recipes.RecipeOutput;
import net.minecraft.data.recipes.RecipeProvider;
import net.minecraft.resources.Identifier;
import net.minecraft.tags.ItemTags;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.CraftingBookCategory;
Expand All @@ -30,19 +30,26 @@ public ModRecipeProvider(FabricDataOutput output, CompletableFuture<HolderLookup
}

/**
* Creates a RecipeProvider that registers a campfire cooking recipe for porkchop.
* Creates a RecipeProvider that registers the mod's recipe set: a campfire cooking recipe
* that cooks a porkchop into a cooked porkchop, a furnace cooking recipe that converts a
* porkchop into an acacia boat, and a workbench crafting recipe that assembles a chest from
* logs and sticks.
*
* The produced provider builds a single recipe that cooks a porkchop into a cooked porkchop,
* requires 10 time units, is unlocked when the player has a porkchop, and is saved under
* the mod namespace with path "campfire_pork_cooking".
* Each recipe includes its unlock condition, crafting category, book category, processing time,
* and is saved to the provided exporter under the mod-specific paths:
* "campfire_pork_cooking", "furnace_pork_cooking", and "workbench_wood_chest".
*
* @param registryLookup provider for looking up game registries used when building recipes
* @param exporter destination used to write the generated recipe JSON
* @return a RecipeProvider that produces the described campfire cooking recipe
* @param registryLookup provider for looking up game registries and tags used when building recipes
* @param exporter destination used to write the generated recipe JSON files
* @return a RecipeProvider that produces and saves the described recipes to the exporter
*/
@Override
protected RecipeProvider createRecipeProvider(HolderLookup.Provider registryLookup, RecipeOutput exporter) {
return new RecipeProvider(registryLookup, exporter) {
/**
* Registers three mod-specific recipes with the recipe exporter:
* a campfire pork cooking recipe producing cooked porkchop (unlocked by having a porkchop, saved as "campfire_pork_cooking"), a furnace pork cooking recipe producing an acacia boat (unlocked by having a porkchop, saved as "furnace_pork_cooking"), and a workbench recipe that crafts a chest from 5 logs and 10 sticks (unlocked by having logs, saved as "workbench_wood_chest").
*/
@Override
public void buildRecipes() {
new WorkbenchRecipeBuilder(ModRecipes.CAMPFIRE_TYPE, ModRecipes.CAMPFIRE_SERIALIZER)
Expand All @@ -52,7 +59,7 @@ public void buildRecipes() {
.unlockedBy("has_porkchop", has(Items.PORKCHOP))
.category(CraftingBookCategory.MISC)
.bookCategory(ModRecipeDisplay.CAMPFIRE_SEARCH)
.save(exporter, Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "campfire_pork_cooking"));
.save(exporter, "campfire_pork_cooking");

new WorkbenchRecipeBuilder(ModRecipes.FURNACE_T1_TYPE, ModRecipes.FURNACE_SERIALIZER)
.input(Ingredient.of(Items.PORKCHOP))
Expand All @@ -61,7 +68,17 @@ public void buildRecipes() {
.unlockedBy("has_porkchop", has(Items.PORKCHOP))
.category(CraftingBookCategory.MISC)
.bookCategory(ModRecipeDisplay.FURNACE_T1_SEARCH)
.save(exporter, Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "furnace_pork_cooking"));
.save(exporter, "furnace_pork_cooking");

new WorkbenchRecipeBuilder(ModRecipes.WORKBENCH_TYPE, ModRecipes.WORKBENCH_SERIALIZER)
.input(ItemTags.LOGS, registryLookup, 5)
.input(Items.STICK, 10)
.output(new ItemStack(Items.CHEST))
.time(50)
.unlockedBy("has_logs", has(ItemTags.LOGS))
.category(CraftingBookCategory.MISC)
.bookCategory(ModRecipeDisplay.WORKBENCH_SEARCH)
.save(exporter, "workbench_wood_chest");
}
};
}
Expand Down
Loading