diff --git a/src/client/java/com/tcm/MineTale/MineTaleClient.java b/src/client/java/com/tcm/MineTale/MineTaleClient.java index 0fc17a7..41bffcb 100644 --- a/src/client/java/com/tcm/MineTale/MineTaleClient.java +++ b/src/client/java/com/tcm/MineTale/MineTaleClient.java @@ -8,7 +8,9 @@ import com.tcm.MineTale.block.workbenches.menu.AbstractWorkbenchContainerMenu; import com.tcm.MineTale.block.workbenches.screen.ArmorersWorkbenchScreen; +import com.tcm.MineTale.block.workbenches.screen.BuildersWorkbenchScreen; import com.tcm.MineTale.block.workbenches.screen.CampfireWorkbenchScreen; +import com.tcm.MineTale.block.workbenches.screen.FarmersWorkbenchScreen; import com.tcm.MineTale.registry.ModBlocks; import com.tcm.MineTale.registry.ModMenuTypes; @@ -23,12 +25,15 @@ public class MineTaleClient implements ClientModInitializer { /** - * Register client-side screen factories for custom workbench menu types. + * Initialises client-side handlers for the MineTale mod. * - * 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. + * Registers screen factories for custom workbench menu types, configures render + * layers for furnace workbench blocks, and registers a global network receiver + * that applies nearby inventory items to an open workbench menu. + * + * The network receiver schedules work on the client thread and retries application + * for up to 10 client ticks if the expected workbench menu is not yet open; if + * synchronization still fails it logs a failure message. */ @Override public void onInitializeClient() { @@ -36,6 +41,8 @@ public void onInitializeClient() { MenuScreens.register(ModMenuTypes.CAMPFIRE_WORKBENCH_MENU, CampfireWorkbenchScreen::new); MenuScreens.register(ModMenuTypes.WORKBENCH_WORKBENCH_MENU, WorkbenchWorkbenchScreen::new); MenuScreens.register(ModMenuTypes.ARMORERS_WORKBENCH_MENU, ArmorersWorkbenchScreen::new); + MenuScreens.register(ModMenuTypes.FARMERS_WORKBENCH_MENU, FarmersWorkbenchScreen::new); + MenuScreens.register(ModMenuTypes.BUILDERS_WORKBENCH_MENU, BuildersWorkbenchScreen::new); BlockRenderLayerMap.putBlock(ModBlocks.FURNACE_WORKBENCH_BLOCK_T1, ChunkSectionLayer.CUTOUT); BlockRenderLayerMap.putBlock(ModBlocks.FURNACE_WORKBENCH_BLOCK_T2, ChunkSectionLayer.CUTOUT); diff --git a/src/client/java/com/tcm/MineTale/block/workbenches/screen/ArmorersWorkbenchScreen.java b/src/client/java/com/tcm/MineTale/block/workbenches/screen/ArmorersWorkbenchScreen.java index d90484e..6f27b05 100644 --- a/src/client/java/com/tcm/MineTale/block/workbenches/screen/ArmorersWorkbenchScreen.java +++ b/src/client/java/com/tcm/MineTale/block/workbenches/screen/ArmorersWorkbenchScreen.java @@ -88,12 +88,12 @@ private static MineTaleRecipeBookComponent createRecipeBookComponent(ArmorersWor } /** - * 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", "10", "All") wired to their respective handlers. - */ + * Initialises the screen size and adds the three crafting buttons. + * + * Sets the GUI image dimensions, delegates further initialisation to the superclass, + * and creates/registers three buttons wired to craft one, ten or all items + * (they invoke handleCraftRequest with 1, 10 and -1 respectively; -1 signifies "All"). + */ @Override protected void init() { // Important: Set your GUI size before super.init() @@ -119,45 +119,14 @@ protected void init() { } /** - * Sends a crafting request for the currently selected recipe in the integrated recipe book. + * Sends a craft request for the recipe remembered by this screen's last known selection. * - * 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. + * Resolves the remembered recipe to its resulting item(s) and, if available, sends a network + * CraftRequestPayload containing the first result and the requested amount. If no remembered + * selection or no results are available, no payload is sent. * - * @param amount the quantity to craft; use -1 to request crafting of the full available stack ("All") + * @param amount the quantity to craft; use -1 to request crafting all available units */ - - // 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 results = entry.resultItems(SlotDisplayContext.fromLevel(this.minecraft.level)); - - // if (!results.isEmpty()) { - // ItemStack resultStack = results.get(0); - - // // 4. LOG FOR DEBUGGING - // System.out.println("Sending craft request for: " + resultStack + " amount: " + amount); - - // ClientPlayNetworking.send(new CraftRequestPayload(resultStack, amount)); - // } - // break; - // } - // } - // } else { - // System.out.println("Request failed: Collection or DisplayID is null!"); - // } - // } - private void handleCraftRequest(int amount) { // Look at our "Memory" instead of the component if (this.lastKnownSelectedId != null) { diff --git a/src/client/java/com/tcm/MineTale/block/workbenches/screen/BuildersWorkbenchScreen.java b/src/client/java/com/tcm/MineTale/block/workbenches/screen/BuildersWorkbenchScreen.java new file mode 100644 index 0000000..b7a0960 --- /dev/null +++ b/src/client/java/com/tcm/MineTale/block/workbenches/screen/BuildersWorkbenchScreen.java @@ -0,0 +1,300 @@ +package com.tcm.MineTale.block.workbenches.screen; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import com.tcm.MineTale.MineTale; +import com.tcm.MineTale.block.workbenches.menu.AbstractWorkbenchContainerMenu; +import com.tcm.MineTale.block.workbenches.menu.BuildersWorkbenchMenu; +import com.tcm.MineTale.mixin.client.ClientRecipeBookAccessor; +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.ClientRecipeBook; +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.renderer.RenderPipelines; +import net.minecraft.core.Holder; +import net.minecraft.resources.Identifier; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +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 BuildersWorkbenchScreen extends AbstractRecipeBookScreen { + private static final Identifier TEXTURE = + Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "textures/gui/container/workbench_workbench.png"); + + private final MineTaleRecipeBookComponent mineTaleRecipeBook; + + private RecipeDisplayId lastKnownSelectedId = null; + + private Button craftOneBtn; + private Button craftTenBtn; + 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 BuildersWorkbenchScreen(BuildersWorkbenchMenu menu, Inventory inventory, Component title) { + this(menu, inventory, title, createRecipeBookComponent(menu)); + } + + /** + * Creates a BuildersWorkbenchScreen 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 BuildersWorkbenchScreen(BuildersWorkbenchMenu 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(BuildersWorkbenchMenu menu) { + ItemStack tabIcon = new ItemStack(ModBlocks.BUILDERS_WORKBENCH_BLOCK.asItem()); + + List tabs = List.of( + new RecipeBookComponent.TabInfo(tabIcon.getItem(), ModRecipeDisplay.BUILDERS_SEARCH) + ); + + return new MineTaleRecipeBookComponent(menu, tabs, ModRecipes.BUILDERS_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", "10", "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(); + + int defaultLeft = this.leftPos + 90; + int defaultTop = this.topPos + 25; + + this.craftOneBtn = addRenderableWidget(Button.builder(Component.literal("Craft"), (button) -> { + handleCraftRequest(1); + }).bounds(defaultLeft, defaultTop, 75, 20).build()); + + this.craftTenBtn = addRenderableWidget(Button.builder(Component.literal("x10"), (button) -> { + handleCraftRequest(10); + }).bounds(defaultLeft, defaultTop + 22, 35, 20).build()); + + this.craftAllBtn = addRenderableWidget(Button.builder(Component.literal("All"), (button) -> { + handleCraftRequest(-1); // -1 represents "All" logic + }).bounds(defaultLeft + 40, defaultTop + 22, 35, 20).build()); + } + + /** + * 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) { + // Look at our "Memory" instead of the component + if (this.lastKnownSelectedId != null) { + ClientRecipeBook book = this.minecraft.player.getRecipeBook(); + RecipeDisplayEntry entry = ((ClientRecipeBookAccessor) book).getKnown().get(this.lastKnownSelectedId); + + if (entry != null) { + List results = entry.resultItems(SlotDisplayContext.fromLevel(this.minecraft.level)); + if (!results.isEmpty()) { + System.out.println("Persistent Selection Success: " + results.get(0)); + ClientPlayNetworking.send(new CraftRequestPayload(results.get(0), amount)); + return; + } + } + } + System.out.println("Request failed: No recipe was ever selected!"); + } + + /** + * 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); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + renderBackground(graphics, mouseX, mouseY, delta); + super.render(graphics, mouseX, mouseY, delta); + + // 1. Get the current selection from the book + RecipeDisplayId currentId = this.mineTaleRecipeBook.getSelectedRecipeId(); + + // 2. If it's NOT null, remember it! + if (currentId != null) { + this.lastKnownSelectedId = currentId; + } + + // 3. Use the remembered ID to find the entry for button activation + RecipeDisplayEntry selectedEntry = null; + if (this.lastKnownSelectedId != null && this.minecraft.level != null) { + ClientRecipeBook book = this.minecraft.player.getRecipeBook(); + selectedEntry = ((ClientRecipeBookAccessor) book).getKnown().get(this.lastKnownSelectedId); + } + + // 2. Button Activation Logic + if (selectedEntry != null) { + // We use the entry directly. It contains the 15 ingredients needed! + boolean canCraftOne = canCraft(this.minecraft.player, selectedEntry, 1); + boolean canCraftMoreThanOne = canCraft(this.minecraft.player, selectedEntry, 2); + boolean canCraftTen = canCraft(this.minecraft.player, selectedEntry, 10); + + this.craftOneBtn.active = canCraftOne; + this.craftTenBtn.active = canCraftTen; + this.craftAllBtn.active = canCraftMoreThanOne; + } else { + this.craftOneBtn.active = false; + this.craftTenBtn.active = false; + this.craftAllBtn.active = false; + } + + renderTooltip(graphics, mouseX, mouseY); + } + + /** + * Determines whether the player has enough ingredients to craft the given recipe the specified number of times. + * + * @param player the player whose inventory (and networked nearby items) will be checked; may be null + * @param entry the recipe display entry providing crafting requirements; may be null + * @param craftCount the multiplier for required ingredient quantities (e.g., 1, 10, or -1 is not specially handled here) + * @return `true` if the player has at least the required quantity of each ingredient multiplied by `craftCount`, `false` otherwise (also returns `false` if `player` or `entry` is null or the recipe has no requirements) + */ + private boolean canCraft(Player player, RecipeDisplayEntry entry, int craftCount) { + if (player == null || entry == null) return false; + + Optional> reqs = entry.craftingRequirements(); + if (reqs.isEmpty()) return false; + + // 1. Group ingredients by their underlying Item Holders. + // Using List> as the key ensures structural equality (content-based hashing). + Map>, Integer> aggregatedRequirements = new HashMap<>(); + Map>, Ingredient> holderToIngredient = new HashMap<>(); + + for (Ingredient ing : reqs.get()) { + // Collect holders into a List to get a stable hashCode() and equals() + @SuppressWarnings("deprecation") + List> key = ing.items().toList(); + + // Aggregate the counts (how many of this specific ingredient set are required) + aggregatedRequirements.put(key, aggregatedRequirements.getOrDefault(key, 0) + 1); + + // Map the list back to the original ingredient for use in hasIngredientAmount + holderToIngredient.putIfAbsent(key, ing); + } + + // 2. Check the player's inventory against the aggregated totals + Inventory inv = player.getInventory(); + for (Map.Entry>, Integer> entryReq : aggregatedRequirements.entrySet()) { + List> key = entryReq.getKey(); + int totalNeeded = entryReq.getValue() * craftCount; + + // Retrieve the original Ingredient object associated with this list of holders + Ingredient originalIng = holderToIngredient.get(key); + + if (!hasIngredientAmount(inv, originalIng, totalNeeded)) { + return false; + } + } + + return true; + } + + private boolean hasIngredientAmount(Inventory inventory, Ingredient ingredient, int totalRequired) { + System.out.println("DEBUG: Searching inventory + nearby for " + totalRequired + "..."); + if (totalRequired <= 0) return true; + + int found = 0; + + // 1. Check Player Inventory + for (int i = 0; i < inventory.getContainerSize(); i++) { + ItemStack stack = inventory.getItem(i); + if (!stack.isEmpty() && ingredient.test(stack)) { + found += stack.getCount(); + } + } + + // 2. CHECK THE NETWORKED ITEMS FROM CHESTS + // This is the list we sent via the packet! + if (this.menu instanceof AbstractWorkbenchContainerMenu workbenchMenu) { + for (ItemStack stack : workbenchMenu.getNetworkedNearbyItems()) { + if (!stack.isEmpty() && ingredient.test(stack)) { + found += stack.getCount(); + System.out.println("DEBUG: Found " + stack.getCount() + " in nearby networked list. Total: " + found); + } + } + } + + if (found >= totalRequired) { + System.out.println("DEBUG: Requirement MET with " + found + "/" + totalRequired); + return true; + } + + System.out.println("DEBUG: FAILED. Only found: " + found + "/" + totalRequired); + return false; + } + + /** + * 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); + } +} \ No newline at end of file diff --git a/src/client/java/com/tcm/MineTale/block/workbenches/screen/FarmersWorkbenchScreen.java b/src/client/java/com/tcm/MineTale/block/workbenches/screen/FarmersWorkbenchScreen.java new file mode 100644 index 0000000..b10e9ca --- /dev/null +++ b/src/client/java/com/tcm/MineTale/block/workbenches/screen/FarmersWorkbenchScreen.java @@ -0,0 +1,318 @@ +package com.tcm.MineTale.block.workbenches.screen; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import com.tcm.MineTale.MineTale; +import com.tcm.MineTale.block.workbenches.menu.AbstractWorkbenchContainerMenu; +import com.tcm.MineTale.block.workbenches.menu.FarmersWorkbenchMenu; +import com.tcm.MineTale.mixin.client.ClientRecipeBookAccessor; +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.ClientRecipeBook; +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.renderer.RenderPipelines; +import net.minecraft.core.Holder; +import net.minecraft.resources.Identifier; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +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 FarmersWorkbenchScreen extends AbstractRecipeBookScreen { + private static final Identifier TEXTURE = + Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "textures/gui/container/workbench_workbench.png"); + + private final MineTaleRecipeBookComponent mineTaleRecipeBook; + + private RecipeDisplayId lastKnownSelectedId = null; + + private Button craftOneBtn; + private Button craftTenBtn; + private Button craftAllBtn; + + /** + * Creates a FarmersWorkbenchScreen configured with 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 FarmersWorkbenchScreen(FarmersWorkbenchMenu menu, Inventory inventory, Component title) { + this(menu, inventory, title, createRecipeBookComponent(menu)); + } + + /** + * Creates a FarmersWorkbenchScreen 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 FarmersWorkbenchScreen(FarmersWorkbenchMenu menu, Inventory inventory, Component title, MineTaleRecipeBookComponent recipeBook) { + super(menu, recipeBook, inventory, title); + this.mineTaleRecipeBook = recipeBook; + } + + /** + * Creates a MineTaleRecipeBookComponent configured for the farmers workbench screen. + * + * @param menu the workbench menu used to initialize the recipe book component + * @return the recipe book component containing the workbench tab and the FARMERS recipe category + */ + private static MineTaleRecipeBookComponent createRecipeBookComponent(FarmersWorkbenchMenu menu) { + ItemStack tabIcon = new ItemStack(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem()); + + List tabs = List.of( + new RecipeBookComponent.TabInfo(tabIcon.getItem(), ModRecipeDisplay.FARMERS_SEARCH) + ); + + return new MineTaleRecipeBookComponent(menu, tabs, ModRecipes.FARMERS_TYPE); + } + + /** + * Initialises the screen layout and registers the crafting controls. + * + * Sets the GUI dimensions to 176×166 and adds three buttons wired to craft requests: + * - "Craft" requests 1, + * - "x10" requests 10, + * - "All" requests all (represented by -1). + */ + @Override + protected void init() { + // Important: Set your GUI size before super.init() + this.imageWidth = 176; + this.imageHeight = 166; + + super.init(); + + int defaultLeft = this.leftPos + 90; + int defaultTop = this.topPos + 25; + + this.craftOneBtn = addRenderableWidget(Button.builder(Component.literal("Craft"), (button) -> { + handleCraftRequest(1); + }).bounds(defaultLeft, defaultTop, 75, 20).build()); + + this.craftTenBtn = addRenderableWidget(Button.builder(Component.literal("x10"), (button) -> { + handleCraftRequest(10); + }).bounds(defaultLeft, defaultTop + 22, 35, 20).build()); + + this.craftAllBtn = addRenderableWidget(Button.builder(Component.literal("All"), (button) -> { + handleCraftRequest(-1); // -1 represents "All" logic + }).bounds(defaultLeft + 40, defaultTop + 22, 35, 20).build()); + } + + /** + * Requests the server to craft the currently selected recipe from the integrated recipe book. + * + * If a previously selected recipe is remembered and has at least one result item, sends a + * CraftRequestPayload containing the recipe's primary result and the requested amount. If no + * remembered selection or no result exists, no request is sent. + * + * @param amount the quantity to craft; use -1 to request crafting the maximum available amount ("All") + */ + private void handleCraftRequest(int amount) { + // Look at our "Memory" instead of the component + if (this.lastKnownSelectedId != null) { + ClientRecipeBook book = this.minecraft.player.getRecipeBook(); + RecipeDisplayEntry entry = ((ClientRecipeBookAccessor) book).getKnown().get(this.lastKnownSelectedId); + + if (entry != null) { + List results = entry.resultItems(SlotDisplayContext.fromLevel(this.minecraft.level)); + if (!results.isEmpty()) { + System.out.println("Persistent Selection Success: " + results.get(0)); + ClientPlayNetworking.send(new CraftRequestPayload(results.get(0), amount)); + return; + } + } + } + System.out.println("Request failed: No recipe was ever selected!"); + } + + /** + * 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 screen, preserves the last selected recipe, and updates craft button states. + * + * Remembers the recipe book's current selection (so selection persists when the book is closed), + * resolves the corresponding known RecipeDisplayEntry from the player's recipe book when available, + * enables or disables the craft buttons based on whether the player can craft 1, more-than-one, or 10 + * of the selected recipe, and renders the background and tooltips. + */ + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + renderBackground(graphics, mouseX, mouseY, delta); + super.render(graphics, mouseX, mouseY, delta); + + // 1. Get the current selection from the book + RecipeDisplayId currentId = this.mineTaleRecipeBook.getSelectedRecipeId(); + + // 2. If it's NOT null, remember it! + if (currentId != null) { + this.lastKnownSelectedId = currentId; + } + + // 3. Use the remembered ID to find the entry for button activation + RecipeDisplayEntry selectedEntry = null; + if (this.lastKnownSelectedId != null && this.minecraft.level != null) { + ClientRecipeBook book = this.minecraft.player.getRecipeBook(); + selectedEntry = ((ClientRecipeBookAccessor) book).getKnown().get(this.lastKnownSelectedId); + } + + // 2. Button Activation Logic + if (selectedEntry != null) { + // We use the entry directly. It contains the 15 ingredients needed! + boolean canCraftOne = canCraft(this.minecraft.player, selectedEntry, 1); + boolean canCraftMoreThanOne = canCraft(this.minecraft.player, selectedEntry, 2); + boolean canCraftTen = canCraft(this.minecraft.player, selectedEntry, 10); + + this.craftOneBtn.active = canCraftOne; + this.craftTenBtn.active = canCraftTen; + this.craftAllBtn.active = canCraftMoreThanOne; + } else { + this.craftOneBtn.active = false; + this.craftTenBtn.active = false; + this.craftAllBtn.active = false; + } + + renderTooltip(graphics, mouseX, mouseY); + } + + /** + * Checks whether the given player has the required ingredients to craft the specified recipe the provided number of times. + * + * @param player the player whose inventory and nearby networked items are considered; may be null + * @param entry the recipe entry supplying crafting requirements; may be null + * @param craftCount the number of times to craft the recipe (multiplies each requirement) + * @return `true` if the player has at least the required quantity of each ingredient multiplied by `craftCount`, `false` otherwise (also returns `false` if `player` or `entry` is null or the recipe has no requirements) + */ + private boolean canCraft(Player player, RecipeDisplayEntry entry, int craftCount) { + if (player == null || entry == null) return false; + + Optional> reqs = entry.craftingRequirements(); + if (reqs.isEmpty()) return false; + + // 1. Group ingredients by their underlying Item Holders. + // Using List> as the key ensures structural equality (content-based hashing). + Map>, Integer> aggregatedRequirements = new HashMap<>(); + Map>, Ingredient> holderToIngredient = new HashMap<>(); + + for (Ingredient ing : reqs.get()) { + // Collect holders into a List to get a stable hashCode() and equals() + @SuppressWarnings("deprecation") + List> key = ing.items().toList(); + + // Aggregate the counts (how many of this specific ingredient set are required) + aggregatedRequirements.put(key, aggregatedRequirements.getOrDefault(key, 0) + 1); + + // Map the list back to the original ingredient for use in hasIngredientAmount + holderToIngredient.putIfAbsent(key, ing); + } + + // 2. Check the player's inventory against the aggregated totals + Inventory inv = player.getInventory(); + for (Map.Entry>, Integer> entryReq : aggregatedRequirements.entrySet()) { + List> key = entryReq.getKey(); + int totalNeeded = entryReq.getValue() * craftCount; + + // Retrieve the original Ingredient object associated with this list of holders + Ingredient originalIng = holderToIngredient.get(key); + + if (!hasIngredientAmount(inv, originalIng, totalNeeded)) { + return false; + } + } + + return true; + } + + /** + * Determine whether the player inventory together with the workbench's networked nearby items + * contains at least the specified quantity of items matching the given ingredient. + * + * @param inventory the player's inventory to check + * @param ingredient the ingredient matcher used to test item stacks + * @param totalRequired the total number of matching items required + * @return true if the combined sources contain at least totalRequired matching items, false otherwise + */ + private boolean hasIngredientAmount(Inventory inventory, Ingredient ingredient, int totalRequired) { + System.out.println("DEBUG: Searching inventory + nearby for " + totalRequired + "..."); + if (totalRequired <= 0) return true; + + int found = 0; + + // 1. Check Player Inventory + for (int i = 0; i < inventory.getContainerSize(); i++) { + ItemStack stack = inventory.getItem(i); + if (!stack.isEmpty() && ingredient.test(stack)) { + found += stack.getCount(); + } + } + + // 2. CHECK THE NETWORKED ITEMS FROM CHESTS + // This is the list we sent via the packet! + if (this.menu instanceof AbstractWorkbenchContainerMenu workbenchMenu) { + for (ItemStack stack : workbenchMenu.getNetworkedNearbyItems()) { + if (!stack.isEmpty() && ingredient.test(stack)) { + found += stack.getCount(); + System.out.println("DEBUG: Found " + stack.getCount() + " in nearby networked list. Total: " + found); + } + } + } + + if (found >= totalRequired) { + System.out.println("DEBUG: Requirement MET with " + found + "/" + totalRequired); + return true; + } + + System.out.println("DEBUG: FAILED. Only found: " + found + "/" + totalRequired); + return false; + } + + /** + * Get the screen position for the recipe book toggle button. + * + * @return the ScreenPosition located 5 pixels from the GUI's left edge and 49 pixels above the GUI's vertical centre + */ + @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); + } +} \ No newline at end of file diff --git a/src/client/java/com/tcm/MineTale/block/workbenches/screen/WorkbenchWorkbenchScreen.java b/src/client/java/com/tcm/MineTale/block/workbenches/screen/WorkbenchWorkbenchScreen.java index e628c65..61e5114 100644 --- a/src/client/java/com/tcm/MineTale/block/workbenches/screen/WorkbenchWorkbenchScreen.java +++ b/src/client/java/com/tcm/MineTale/block/workbenches/screen/WorkbenchWorkbenchScreen.java @@ -9,7 +9,6 @@ import com.tcm.MineTale.block.workbenches.menu.AbstractWorkbenchContainerMenu; import com.tcm.MineTale.block.workbenches.menu.WorkbenchWorkbenchMenu; import com.tcm.MineTale.mixin.client.ClientRecipeBookAccessor; -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; @@ -23,7 +22,6 @@ 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.RecipeCollection; import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.core.Holder; import net.minecraft.resources.Identifier; @@ -43,6 +41,8 @@ public class WorkbenchWorkbenchScreen extends AbstractRecipeBookScreen results = entry.resultItems(SlotDisplayContext.fromLevel(this.minecraft.level)); - - if (!results.isEmpty()) { - ItemStack resultStack = results.get(0); - - // 4. LOG FOR DEBUGGING - System.out.println("Sending craft request for: " + resultStack + " amount: " + amount); - - ClientPlayNetworking.send(new CraftRequestPayload(resultStack, amount)); - } - break; + // Look at our "Memory" instead of the component + if (this.lastKnownSelectedId != null) { + ClientRecipeBook book = this.minecraft.player.getRecipeBook(); + RecipeDisplayEntry entry = ((ClientRecipeBookAccessor) book).getKnown().get(this.lastKnownSelectedId); + + if (entry != null) { + List results = entry.resultItems(SlotDisplayContext.fromLevel(this.minecraft.level)); + if (!results.isEmpty()) { + System.out.println("Persistent Selection Success: " + results.get(0)); + ClientPlayNetworking.send(new CraftRequestPayload(results.get(0), amount)); + return; } } - } else { - System.out.println("Request failed: Collection or DisplayID is null!"); } + System.out.println("Request failed: No recipe was ever selected!"); } /** - * Draws the workbench GUI background texture at the screen's top-left corner. + * Draws the workbench background texture at the screen's current GUI origin. * * @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 + * @param i current mouse x coordinate + * @param j current mouse y coordinate */ protected void renderBg(GuiGraphics guiGraphics, float f, int i, int j) { int k = this.leftPos; @@ -172,20 +160,31 @@ protected void renderBg(GuiGraphics guiGraphics, float f, int i, int j) { guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, k, l, 0.0F, 0.0F, this.imageWidth, this.imageHeight, 256, 256); } + /** + * Render the screen, remember the current recipe selection and update craft-button availability. + * + * Remembers the recipe selected in the recipe book, resolves that selection against the client's known recipes when possible, + * sets the craft buttons active or inactive according to whether the player has sufficient ingredients for counts of 1, 2 and 10, + * renders the background, the superclass UI and any tooltips. + */ @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { renderBackground(graphics, mouseX, mouseY, delta); super.render(graphics, mouseX, mouseY, delta); - // Get the ID of the recipe clicked in the ghost-book - RecipeDisplayId displayId = this.mineTaleRecipeBook.getSelectedRecipeId(); - RecipeDisplayEntry selectedEntry = null; + // 1. Get the current selection from the book + RecipeDisplayId currentId = this.mineTaleRecipeBook.getSelectedRecipeId(); + + // 2. If it's NOT null, remember it! + if (currentId != null) { + this.lastKnownSelectedId = currentId; + } - if (displayId != null && this.minecraft.level != null) { + // 3. Use the remembered ID to find the entry for button activation + RecipeDisplayEntry selectedEntry = null; + if (this.lastKnownSelectedId != null && this.minecraft.level != null) { ClientRecipeBook book = this.minecraft.player.getRecipeBook(); - // Accessing the known recipes via your Accessor - Map knownRecipes = ((ClientRecipeBookAccessor) book).getKnown(); - selectedEntry = knownRecipes.get(displayId); + selectedEntry = ((ClientRecipeBookAccessor) book).getKnown().get(this.lastKnownSelectedId); } // 2. Button Activation Logic diff --git a/src/client/java/com/tcm/MineTale/datagen/recipes/BuilderRecipes.java b/src/client/java/com/tcm/MineTale/datagen/recipes/BuilderRecipes.java index 2c2a960..b6815ef 100644 --- a/src/client/java/com/tcm/MineTale/datagen/recipes/BuilderRecipes.java +++ b/src/client/java/com/tcm/MineTale/datagen/recipes/BuilderRecipes.java @@ -1,5 +1,11 @@ package com.tcm.MineTale.datagen.recipes; +import com.tcm.MineTale.datagen.builders.WorkbenchRecipeBuilder; +import com.tcm.MineTale.registry.ModBlocks; +import com.tcm.MineTale.registry.ModItems; +import com.tcm.MineTale.registry.ModRecipeDisplay; +import com.tcm.MineTale.registry.ModRecipes; + import net.minecraft.core.HolderLookup; import net.minecraft.data.recipes.RecipeOutput; import net.minecraft.data.recipes.RecipeProvider; @@ -7,22 +13,20 @@ public class BuilderRecipes { public static void buildRecipes(RecipeProvider provider, RecipeOutput exporter, HolderLookup.Provider lookup) { - // TODO: BUILDERS_WORKBENCH_BLOCK & ROPE Not Implemented - // new WorkbenchRecipeBuilder(ModRecipes.BUILDER_TYPE, ModRecipes.BUILDER_SERIALIZER) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.ROPE.asItem()) - // .time(3) - // .unlockedBy("has_builders_workbench", provider.has(ModBlocks.BUILDERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.BUILDER_SEARCH) - // .save(exporter, "builders_workbench_rope"); + new WorkbenchRecipeBuilder(ModRecipes.BUILDERS_TYPE, ModRecipes.BUILDERS_SERIALIZER) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.ROPE) + .time(3) + .unlockedBy("has_builders_workbench", provider.has(ModBlocks.BUILDERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.BUILDERS_SEARCH) + .save(exporter, "builders_workbench_rope"); - // TODO: BUILDERS_WORKBENCH_BLOCK & ROPE_DIAGONAL Not Implemented - // new WorkbenchRecipeBuilder(ModRecipes.BUILDER_TYPE, ModRecipes.BUILDER_SERIALIZER) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.ROPE_DIAGONAL.asItem()) - // .time(3) - // .unlockedBy("has_builders_workbench", provider.has(ModBlocks.BUILDERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.BUILDER_SEARCH) - // .save(exporter, "builders_workbench_rope_diagonal"); + new WorkbenchRecipeBuilder(ModRecipes.BUILDERS_TYPE, ModRecipes.BUILDERS_SERIALIZER) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.ROPE_DIAGONAL) + .time(3) + .unlockedBy("has_builders_workbench", provider.has(ModBlocks.BUILDERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.BUILDERS_SEARCH) + .save(exporter, "builders_workbench_rope_diagonal"); } } diff --git a/src/client/java/com/tcm/MineTale/datagen/recipes/FarmerRecipes.java b/src/client/java/com/tcm/MineTale/datagen/recipes/FarmerRecipes.java index eb0f05e..22867c9 100644 --- a/src/client/java/com/tcm/MineTale/datagen/recipes/FarmerRecipes.java +++ b/src/client/java/com/tcm/MineTale/datagen/recipes/FarmerRecipes.java @@ -1,24 +1,42 @@ package com.tcm.MineTale.datagen.recipes; +import com.tcm.MineTale.datagen.builders.WorkbenchRecipeBuilder; +import com.tcm.MineTale.registry.ModBlocks; +import com.tcm.MineTale.registry.ModItems; +import com.tcm.MineTale.registry.ModRecipeDisplay; +import com.tcm.MineTale.registry.ModRecipes; + import net.minecraft.core.HolderLookup; import net.minecraft.data.recipes.RecipeOutput; import net.minecraft.data.recipes.RecipeProvider; +import net.minecraft.tags.ItemTags; +import net.minecraft.world.level.block.Blocks; public class FarmerRecipes { + /** + * Registers Farmer workbench recipes and writes them to the provided exporter. + * + *

Emits multiple WorkbenchRecipeBuilder entries (bamboo planter, moss variants, rugs, + * vines, soil/grass variants, and other farmer-related recipes) and configures their + * ingredient lists, times, book categories, and unlock conditions.

+ * + * @param provider source of recipe unlock predicates (used to build "has_farmers_workbench" and similar conditions) + * @param exporter destination that receives the generated recipe data + * @param lookup holder/tag resolver used when recipes specify ingredients by tag + */ public static void buildRecipes(RecipeProvider provider, RecipeOutput exporter, HolderLookup.Provider lookup) { - // TODO: ChickenCoop Not Implemented // new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) // .input(ItemTags.PLANKS, lookup, 20) // .input(ModItems.ESSENCE_OF_LIFE, 50) // .input(ModItems.PLANT_FIBER, 6) - // .output(ModBlocks.CHICKEN_COOP.asItem()) + // .output(ModBlocks.CHICKEN_COOP) // .time(2) - // .unlockedBy("has_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH.asItem())) + // .unlockedBy("has_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) // .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) // .save(exporter, "CHICKEN_COOP"); // TODO: LOTS Not Implemented - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) + // new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) // .input(ModItems.GREATER_ESSENCE_OF_LIFE, 100) // .input(ModItems.WILD_BERRY, 100) // .input(Items.WHEAT, 100) @@ -47,437 +65,428 @@ public static void buildRecipes(RecipeProvider provider, RecipeOutput exporter, // .input(ModItems.PLANT_FIBER, 100) // .input(ItemTags.MILK_BUCKET, 8) // .input(ItemTags.MOSS_HORN_MILK_BUCKET, 8) - // .output(ModBlocks.HARVEST_TROPHY.asItem()) + // .output(ModBlocks.HARVEST_TROPHY) // .time(10) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) + // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + // .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) // .save(exporter, "farmers_workbench_harvest_trophy"); - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModItems.PLANT_FIBER, 20) - // .input(ItemTags.SOIL, 2) - // .input(ModItems.ESSENCE_OF_LIFE, 10) - // .input(ModBlocks.BAMBOO_LOG) - // .output(ModBlocks.BAMBOO_PLANTER.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "BAMBOO_PLANTER"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.BLUE_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.BLUE_MOSS_BLOCK.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "BLUE_MOSS_BLOCK"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.DARK_GREEN_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.DARK_GREEN_MOSS_BLOCK.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "DARK_GREEN_MOSS_BLOCK"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.GREEN_MOSS_RUG.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "GREEN_MOSS_RUG"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.RED_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.RED_MOSS_BLOCK.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "RED_MOSS_BLOCK"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.BLUE_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.BLUE_MOSS_RUG.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "BLUE_MOSS_RUG"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.RED_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.RED_HANGING_MOSS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "RED_HANGING_MOSS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.DARK_GREEN_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.DARK_GREEN_HANGING_MOSS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "DARK_GREEN_HANGING_MOSS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.BLUE_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.BLUE_HANGING_MOSS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "BLUE_HANGING_MOSS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.YELLOW_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.YELLOW_MOSS_BLOCK.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "YELLOW_MOSS_BLOCK"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.YELLOW_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.YELLOW_HANGING_MOSS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "YELLOW_HANGING_MOSS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.GREEN_MOSS_BLOCK.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "GREEN_MOSS_BLOCK"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.GREEN_HANGING_MOSS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "GREEN_HANGING_MOSS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.DARK_GREEN_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.DARK_GREEN_MOSS_RUG.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "DARK_GREEN_MOSS_RUG"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.YELLOW_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.YELLOW_MOSS_RUG.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "YELLOW_MOSS_RUG"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.SORREL_RUG.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "SORREL_RUG"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.RED_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.RED_MOSS_RUG.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "RED_MOSS_RUG"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.RED_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.SHORT_RED_MOSS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "SHORT_RED_MOSS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.YELLOW_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.SHORT_YELLOW_MOSS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "SHORT_YELLOW_MOSS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.BLUE_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.SHORT_BLUE_MOSS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "SHORT_BLUE_MOSS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.DARK_GREEN_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.SHORT_DARK_GREEN_MOSS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "SHORT_DARK_GREEN_MOSS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.SHORT_MOSS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "SHORT_MOSS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModBlocks.BLUE_MOSS, 4) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.SHORT_BLUE_MOSS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "SHORT_BLUE_MOSS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModItems.ESSENCE_OF_LIFE) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.LIANA.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "LIANA"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModItems.ESSENCE_OF_LIFE) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.VINE.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "VINE"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModItems.ESSENCE_OF_LIFE) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.DRY_VINE.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "DRY_VINE"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModItems.ESSENCE_OF_LIFE) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.POISONED_IVY.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "POISONED_IVY"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModItems.ESSENCE_OF_LIFE) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.WALL_IVY.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "WALL__IVY"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModItems.ESSENCE_OF_LIFE) - // .input(ModItems.PLANT_FIBER, 2) - // .output(ModBlocks.IVY.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "IVY"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(ModItems.ESSENCE_OF_LIFE) - // .input(ModItems.PLANT_FIBER, 4) - // .output(ModBlocks.VINE_RUG.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "VINE_RUG"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.COLD_GRASS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "COLD_GRASS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.BURNT_GRASS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "BURNT_GRASS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.POISONED_DIRT.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "POISONED_DIRT"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.DEEP_GRASS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "DEEP_GRASS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.DRY_DIRT.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "DRY_DIRT"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.DRY_GRASS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "DRY_GRASS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.WET_GRASS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "WET_GRASS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.SUMMER_GRASS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "SUMMER_GRASS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(Blocks.GRASS_BLOCK.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "GRASS_BLOCK"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.COLD_DIRT.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "COLD_DIRT"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.FULL_GRASS.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "FULL_GRASS"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.NEEDLED_SOIL.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "NEEDLED_SOIL"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.SOIL_PATHWAY.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "SOIL_PATHWAY"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(Blocks.MUD.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "MUD"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.LEAFY_SOIL.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "LEAFY_SOIL"); - - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) - // .input(BlockTags.SOIL) - // .input(ModItems.PLANT_FIBER) - // .output(ModBlocks.DRY_MUD.asItem()) - // .time(2) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) - // .save(exporter, "DRY_MUD"); + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModItems.PLANT_FIBER, 20) + .input(ItemTags.DIRT, lookup, 2) + .input(ModItems.ESSENCE_OF_LIFE, 10) + .input(ModBlocks.BAMBOO_LOG) + .output(ModBlocks.BAMBOO_PLANTER) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "bamboo_planter"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.BLUE_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.BLUE_MOSS_BLOCK) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "blue_moss_block"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.DARK_GREEN_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.DARK_GREEN_MOSS_BLOCK) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "dark_green_moss_block"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.GREEN_MOSS_RUG) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "green_moss_rug"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.RED_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.RED_MOSS_BLOCK) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "red_moss_block"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.BLUE_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.BLUE_MOSS_RUG) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "blue_moss_rug"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.RED_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.RED_HANGING_MOSS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "red_hanging_moss"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.DARK_GREEN_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.DARK_GREEN_HANGING_MOSS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "dark_green_hanging_moss"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.BLUE_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.BLUE_HANGING_MOSS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "blue_hanging_moss"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.YELLOW_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.YELLOW_MOSS_BLOCK) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "yellow_moss_block"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.YELLOW_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.YELLOW_HANGING_MOSS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "yellow_hanging_moss"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.GREEN_MOSS_BLOCK) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "green_moss_block"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.GREEN_HANGING_MOSS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "green_hanging_moss"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.DARK_GREEN_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.DARK_GREEN_MOSS_RUG) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "dark_green_moss_rug"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.YELLOW_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.YELLOW_MOSS_RUG) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "yellow_moss_rug"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.SORREL_RUG) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "sorrel_rug"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.RED_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.RED_MOSS_RUG) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "red_moss_rug"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.RED_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.SHORT_RED_MOSS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "short_red_moss"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.YELLOW_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.SHORT_YELLOW_MOSS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "short_yellow_moss"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.BLUE_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.SHORT_BLUE_MOSS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "short_blue_moss"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.DARK_GREEN_MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.SHORT_DARK_GREEN_MOSS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "short_dark_green_moss"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModBlocks.MOSS, 4) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.SHORT_MOSS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "short_moss"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModItems.ESSENCE_OF_LIFE) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.LIANA) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "liana"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModItems.ESSENCE_OF_LIFE) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.VINE) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "vine"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModItems.ESSENCE_OF_LIFE) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.DRY_VINE) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "dry_vine"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModItems.ESSENCE_OF_LIFE) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.POISONED_IVY) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "poisoned_ivy"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModItems.ESSENCE_OF_LIFE) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.WALL_IVY) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "wall_ivy"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModItems.ESSENCE_OF_LIFE) + .input(ModItems.PLANT_FIBER, 2) + .output(ModBlocks.IVY) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "ivy"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ModItems.ESSENCE_OF_LIFE) + .input(ModItems.PLANT_FIBER, 4) + .output(ModBlocks.VINE_RUG) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "vine_rug"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.COLD_GRASS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "cold_grass"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.BURNT_GRASS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "burnt_grass"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.POISONED_DIRT) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "poisoned_dirt"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.DEEP_GRASS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "deep_grass"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.DRY_DIRT) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "dry_dirt"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.DRY_GRASS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "dry_grass"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.WET_GRASS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "wet_grass"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.SUMMER_GRASS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "summer_grass"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(Blocks.GRASS_BLOCK) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "grass_block"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.COLD_DIRT) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "cold_dirt"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.FULL_GRASS) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "full_grass"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.NEEDLED_SOIL) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "needled_soil"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.SOIL_PATHWAY) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "soil_pathway"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(Blocks.MUD) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "mud"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.LEAFY_SOIL) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "leafy_soil"); + + new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) + .input(ItemTags.DIRT, lookup) + .input(ModItems.PLANT_FIBER) + .output(ModBlocks.DRY_MUD) + .time(2) + .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) + .save(exporter, "dry_mud"); - // new WorkbenchRecipeBuilder(ModRecipes.FARMER_TYPE, ModRecipes.FARMER_SERIALIZER) + // new WorkbenchRecipeBuilder(ModRecipes.FARMERS_TYPE, ModRecipes.FARMERS_SERIALIZER) // .input(ItemTags.LOGS, lookup, 10) // .input(ModItems.ESSENCE_OF_LIFE, 50) // .input(ModItems.PLANT_FIBER, 20) // .input(Items.IRON_INGOT) - // .output(ModBlocks.FISHING_TRAP.asItem()) + // .output(ModBlocks.FISHING_TRAP) // .time(10) - // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.FARMER_SEARCH) + // .unlockedBy("has_farmers_workbench", provider.has(ModBlocks.FARMERS_WORKBENCH_BLOCK)) + // .bookCategory(ModRecipeDisplay.FARMERS_SEARCH) // .save(exporter, "FISHING_TRAP"); } } diff --git a/src/client/java/com/tcm/MineTale/datagen/recipes/WorkbenchRecipes.java b/src/client/java/com/tcm/MineTale/datagen/recipes/WorkbenchRecipes.java index 63ce4ba..d187eed 100644 --- a/src/client/java/com/tcm/MineTale/datagen/recipes/WorkbenchRecipes.java +++ b/src/client/java/com/tcm/MineTale/datagen/recipes/WorkbenchRecipes.java @@ -2,6 +2,7 @@ import com.tcm.MineTale.datagen.builders.WorkbenchRecipeBuilder; import com.tcm.MineTale.registry.ModBlocks; +import com.tcm.MineTale.registry.ModItems; import com.tcm.MineTale.registry.ModRecipeDisplay; import com.tcm.MineTale.registry.ModRecipes; @@ -12,45 +13,54 @@ import net.minecraft.world.item.Items; public class WorkbenchRecipes { + /** + * Registers workbench crafting recipes and saves them to the provided exporter. + * + * Builds and configures multiple WorkbenchRecipeBuilder instances (inputs, outputs, craft time, + * unlock conditions, and book category) and persists each recipe using the exporter with a + * unique identifier. + * + * @param provider a RecipeProvider used to query existing items/blocks for unlock conditions and tags + * @param exporter the RecipeOutput that receives and writes the generated recipe data + * @param lookup a HolderLookup.Provider used to resolve tag holders (e.g., ItemTags) when specifying inputs + */ public static void buildRecipes(RecipeProvider provider, RecipeOutput exporter, HolderLookup.Provider lookup) { new WorkbenchRecipeBuilder(ModRecipes.WORKBENCH_TYPE, ModRecipes.WORKBENCH_SERIALIZER) .input(Items.COPPER_INGOT, 2) .input(ItemTags.LOGS, lookup, 10) .input(ItemTags.STONE_TOOL_MATERIALS, lookup, 5) - .output(ModBlocks.ARMORERS_WORKBENCH_BLOCK.asItem()) + .output(ModBlocks.ARMORERS_WORKBENCH_BLOCK) .time(3) - .unlockedBy("has_workbench", provider.has(ModBlocks.WORKBENCH_WORKBENCH_BLOCK.asItem())) + .unlockedBy("has_workbench", provider.has(ModBlocks.WORKBENCH_WORKBENCH_BLOCK)) .bookCategory(ModRecipeDisplay.WORKBENCH_SEARCH) .save(exporter, "workbench_armorers_workbench"); new WorkbenchRecipeBuilder(ModRecipes.WORKBENCH_TYPE, ModRecipes.WORKBENCH_SERIALIZER) .input(ItemTags.LOGS, lookup, 6) .input(ItemTags.STONE_TOOL_MATERIALS, lookup, 6) - .output(ModBlocks.FURNACE_WORKBENCH_BLOCK_T1.asItem()) + .output(ModBlocks.FURNACE_WORKBENCH_BLOCK_T1) .time(3) - .unlockedBy("has_workbench", provider.has(ModBlocks.WORKBENCH_WORKBENCH_BLOCK.asItem())) + .unlockedBy("has_workbench", provider.has(ModBlocks.WORKBENCH_WORKBENCH_BLOCK)) .bookCategory(ModRecipeDisplay.WORKBENCH_SEARCH) .save(exporter, "workbench_furnace_workbench_t1"); - // TODO: FarmersWorkbench Not Implemented - // new WorkbenchRecipeBuilder(ModRecipes.WORKBENCH_TYPE, ModRecipes.WORKBENCH_SERIALIZER) - // .input(ItemTags.LOGS, lookup, 6) - // .input(ModItems.PLANT_FIBER, 20) - // .output(ModBlocks.FARMERS_WORKBENCH) - // .time(3) - // .unlockedBy("has_workbench", provider.has(ModBlocks.WORKBENCH_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.WORKBENCH_SEARCH) - // .save(exporter, "workbench_farmers_workbench"); + new WorkbenchRecipeBuilder(ModRecipes.WORKBENCH_TYPE, ModRecipes.WORKBENCH_SERIALIZER) + .input(ItemTags.LOGS, lookup, 6) + .input(ModItems.PLANT_FIBER, 20) + .output(ModBlocks.FARMERS_WORKBENCH_BLOCK) + .time(3) + .unlockedBy("has_workbench", provider.has(ModBlocks.WORKBENCH_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.WORKBENCH_SEARCH) + .save(exporter, "workbench_farmers_workbench"); - // TODO: Builder's Workbench Not Implemented - // new WorkbenchRecipeBuilder(ModRecipes.WORKBENCH_TYPE, ModRecipes.WORKBENCH_SERIALIZER) - // .input(ItemTags.LOGS, lookup, 6) - // .input(ItemTags.STONE_TOOL_MATERIALS, lookup, 3) - // .output(ModBlocks.BUILDERS_WORKBENCH.asItem()) - // .time(2) - // .unlockedBy("has_workbench", provider.has(ModBlocks.WORKBENCH_WORKBENCH_BLOCK.asItem())) - // .bookCategory(ModRecipeDisplay.WORKBENCH_SEARCH) - // .save(exporter, "workbench_builders_workbench"); + new WorkbenchRecipeBuilder(ModRecipes.WORKBENCH_TYPE, ModRecipes.WORKBENCH_SERIALIZER) + .input(ItemTags.LOGS, lookup, 6) + .input(ItemTags.STONE_TOOL_MATERIALS, lookup, 3) + .output(ModBlocks.BUILDERS_WORKBENCH_BLOCK) + .time(2) + .unlockedBy("has_workbench", provider.has(ModBlocks.WORKBENCH_WORKBENCH_BLOCK)) + .bookCategory(ModRecipeDisplay.WORKBENCH_SEARCH) + .save(exporter, "workbench_builders_workbench"); // TODO: HAY_TARGET Not Implemented // new WorkbenchRecipeBuilder(ModRecipes.WORKBENCH_TYPE, ModRecipes.WORKBENCH_SERIALIZER) diff --git a/src/main/java/com/tcm/MineTale/MineTale.java b/src/main/java/com/tcm/MineTale/MineTale.java index 44dc877..a4cf275 100644 --- a/src/main/java/com/tcm/MineTale/MineTale.java +++ b/src/main/java/com/tcm/MineTale/MineTale.java @@ -18,8 +18,6 @@ import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity; import com.tcm.MineTale.block.workbenches.menu.AbstractWorkbenchContainerMenu; -import com.tcm.MineTale.block.workbenches.menu.ArmorersWorkbenchMenu; -import com.tcm.MineTale.block.workbenches.menu.WorkbenchWorkbenchMenu; import com.tcm.MineTale.network.ClientboundNearbyInventorySyncPacket; import com.tcm.MineTale.network.CraftRequestPayload; import com.tcm.MineTale.recipe.WorkbenchRecipe; @@ -42,10 +40,12 @@ public class MineTale implements ModInitializer { public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); /** - * Initializes and registers the mod's game content and subsystems during Fabric startup. + * Initialise the mod: register game content, networking codecs and runtime subsystems. * - *

Triggers initialization for blocks, block entities, menu types, entities, items, and entity - * data serializers so they are registered with the game before gameplay begins.

+ * Performs startup registration in dependency order (blocks, items, block entities, entities, + * menus, recipes and recipe displays), registers the creative tab and entity data serializers, + * synchronises the furnace recipe serializer, applies loot-table modifiers, registers client↔server + * payload codecs and a global server receiver that processes craft requests from workbench-like menus. */ @Override public void onInitialize() { @@ -81,80 +81,26 @@ public void onInitialize() { PayloadTypeRegistry.playS2C().register(ClientboundNearbyInventorySyncPacket.TYPE, ClientboundNearbyInventorySyncPacket.STREAM_CODEC); - // Register the server-side receiver using .TYPE - // ServerPlayNetworking.registerGlobalReceiver(CraftRequestPayload.TYPE, (payload, context) -> { - // context.server().execute(() -> { - // ServerPlayer player = context.player(); - - // // --- SECURITY GUARD --- - // // Ensure the player actually has the Workbench UI open before processing the craft - // if (!(player.containerMenu instanceof WorkbenchWorkbenchMenu)) { - // return; - // } - - // ItemStack requestedResult = payload.resultItem(); - // int amount = payload.amount(); - - // // 1. Get the RecipeManager from the server level - // RecipeManager recipeManager = player.level().recipeAccess(); - - // // 2. Find the recipe by matching the output ItemStack - // Optional> recipeOpt = recipeManager.getAllOfType(ModRecipes.WORKBENCH_TYPE).stream() - // .filter(holder -> { - // // Guard against recipes with no results before accessing index 0 - // if (holder.value().results().isEmpty()) { - // return false; - // } - - // // Compare the first result of the workbench recipe to the requested item - // ItemStack result = holder.value().results().get(0); - // return ItemStack.isSameItem(result, requestedResult); - // }) - // .findFirst(); - - // if (recipeOpt.isPresent()) { - // WorkbenchRecipe recipe = recipeOpt.get().value(); - - // // 2. Determine craft limit (Handle "All" logic) - // int limit = (amount == -1) ? 64 : Math.min(Math.max(amount, 0), 64); - - // for (int i = 0; i < limit; i++) { - // if (hasIngredients(player, recipe)) { - // consumeIngredients(player, recipe); - // player.getInventory().add(recipe.results().get(0).copy()); - // } else { - // break; - // } - // } - - // // 3. Sync inventory changes to the client screen - // player.containerMenu.broadcastChanges(); - // } - // }); - // }); - ServerPlayNetworking.registerGlobalReceiver(CraftRequestPayload.TYPE, (payload, context) -> { context.server().execute(() -> { ServerPlayer player = context.player(); // --- SELECTIVE SECURITY GUARD --- // Only proceed if the menu is one of the two specific workbenches - boolean isWorkbench = player.containerMenu instanceof WorkbenchWorkbenchMenu; - boolean isArmorers = player.containerMenu instanceof ArmorersWorkbenchMenu; + boolean isWorkbench = player.containerMenu instanceof AbstractWorkbenchContainerMenu; - if (!isWorkbench && !isArmorers) { + if (!isWorkbench) { return; // Reject packets from Campfires, Furnaces, or other menus } + AbstractWorkbenchContainerMenu instanceContainerMenu = (AbstractWorkbenchContainerMenu) player.containerMenu; + ItemStack requestedResult = payload.resultItem(); int amount = payload.amount(); RecipeManager recipeManager = player.level().recipeAccess(); - // 1. Determine which Recipe Type to search based on the open menu - var targetType = isWorkbench ? ModRecipes.WORKBENCH_TYPE : ModRecipes.ARMORERS_TYPE; - // 2. Find the recipe within that specific type - Optional> recipeOpt = recipeManager.getAllOfType(targetType).stream() + Optional> recipeOpt = recipeManager.getAllOfType(instanceContainerMenu.getRecipeType()).stream() .filter(holder -> { if (holder.value().results().isEmpty()) return false; diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/BuildersWorkbench.java b/src/main/java/com/tcm/MineTale/block/workbenches/BuildersWorkbench.java new file mode 100644 index 0000000..7f9c7eb --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/workbenches/BuildersWorkbench.java @@ -0,0 +1,96 @@ +package com.tcm.MineTale.block.workbenches; + +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import com.mojang.serialization.MapCodec; +import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity; +import com.tcm.MineTale.block.workbenches.entity.BuildersWorkbenchEntity; +import com.tcm.MineTale.registry.ModBlockEntities; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +public class BuildersWorkbench extends AbstractWorkbench { + public static final boolean IS_WIDE = true; + public static final boolean IS_TALL = false; + + public static final MapCodec CODEC = simpleCodec(BuildersWorkbench::new); + + /** + * Constructs a BuildersWorkbench that uses the mod's BUILDERS_WORKBENCH_BE block entity type. + * + * @param properties block properties for this workbench + */ + public BuildersWorkbench(Properties properties) { + // Hardcode the supplier and sounds here if they never change + super(properties, () -> ModBlockEntities.BUILDERS_WORKBENCH_BE, IS_WIDE, IS_TALL, 1); + } + + /** + * Constructs a BuildersWorkbench using the provided block properties and block-entity type supplier. + * + * @param properties block properties to apply to this workbench + * @param supplier supplier that provides the BlockEntityType for the BuildersWorkbenchEntity + */ + public BuildersWorkbench(Properties properties, Supplier> supplier) { + super(properties, supplier, IS_WIDE, IS_TALL, 1); + } + + /** + * Provides a ticker for workbench block entities when the supplied block entity type matches this block's entity type. + * + * @param type the block entity type to match against this block's workbench entity type + * @return a BlockEntityTicker that updates matching workbench block entities, or {@code null} if the types do not match + */ + @Nullable + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + // This connects the Level's ticking system to your static tick method + return createTickerHelper(type, ModBlockEntities.BUILDERS_WORKBENCH_BE, AbstractWorkbenchEntity::tick); + } + + /** + * Provides the MapCodec used to serialize and deserialize this workbench. + * + * @return the MapCodec for this BuildersWorkbench + */ + @Override + protected MapCodec codec() { + return CODEC; + } + + /** + * Specifies that this block is rendered using its block model. + * + * @return RenderShape.MODEL to render the block using its JSON/model representation. + */ + @Override + public RenderShape getRenderShape(BlockState state) { + // BaseEntityBlock defaults to INVISIBLE. + // We set it to MODEL so the JSON model is rendered. + return RenderShape.MODEL; + } + + private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 16, 16); + + /** + * The block's collision and interaction shape as a 1×1 footprint (x 0–16, y 0–16, z 0–16). + * + * @return the voxel shape used for collision and interaction + */ + @Override + public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { + return SHAPE; + } +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/FarmersWorkbench.java b/src/main/java/com/tcm/MineTale/block/workbenches/FarmersWorkbench.java new file mode 100644 index 0000000..f9f7ec0 --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/workbenches/FarmersWorkbench.java @@ -0,0 +1,98 @@ +package com.tcm.MineTale.block.workbenches; + +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import com.mojang.serialization.MapCodec; +import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity; +import com.tcm.MineTale.block.workbenches.entity.FarmersWorkbenchEntity; +import com.tcm.MineTale.registry.ModBlockEntities; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +// ChestBlock + +public class FarmersWorkbench extends AbstractWorkbench { + public static final boolean IS_WIDE = true; + public static final boolean IS_TALL = false; + + public static final MapCodec CODEC = simpleCodec(FarmersWorkbench::new); + + /** + * Initialise a FarmersWorkbench that uses the mod's FARMERS_WORKBENCH_BE block-entity type and default size. + * + * @param properties block properties for this workbench + */ + public FarmersWorkbench(Properties properties) { + // Hardcode the supplier and sounds here if they never change + super(properties, () -> ModBlockEntities.FARMERS_WORKBENCH_BE, IS_WIDE, IS_TALL, 1); + } + + /** + * Constructs a FarmersWorkbench with the supplied block properties and block-entity type supplier. + * + * @param properties block properties to apply to this workbench + * @param supplier supplier of the BlockEntityType to use for the workbench's block entity + */ + public FarmersWorkbench(Properties properties, Supplier> supplier) { + super(properties, supplier, IS_WIDE, IS_TALL, 1); + } + + /** + * Provides a ticker for workbench block entities when the supplied block entity type matches this block's entity type. + * + * @param type the block entity type to match against this block's workbench entity type + * @return a BlockEntityTicker that updates matching workbench block entities, or {@code null} if the types do not match + */ + @Nullable + @Override + public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + // This connects the Level's ticking system to your static tick method + return createTickerHelper(type, ModBlockEntities.FARMERS_WORKBENCH_BE, AbstractWorkbenchEntity::tick); + } + + /** + * Provides the MapCodec used to serialize and deserialize this workbench. + * + * @return the MapCodec for this FarmersWorkbench + */ + @Override + protected MapCodec codec() { + return CODEC; + } + + /** + * Specifies that this block is rendered using its block model. + * + * @return RenderShape.MODEL to render the block using its JSON/model representation. + */ + @Override + public RenderShape getRenderShape(BlockState state) { + // BaseEntityBlock defaults to INVISIBLE. + // We set it to MODEL so the JSON model is rendered. + return RenderShape.MODEL; + } + + private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 16, 16); + + /** + * The block's collision and interaction shape as a 1×1 footprint (x 0–16, y 0–16, z 0–16). + * + * @return the voxel shape used for collision and interaction + */ + @Override + public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { + return SHAPE; + } +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/BuildersWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/BuildersWorkbenchEntity.java new file mode 100644 index 0000000..510e9c7 --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/BuildersWorkbenchEntity.java @@ -0,0 +1,176 @@ +package com.tcm.MineTale.block.workbenches.entity; + +import java.util.ArrayList; +import java.util.List; + +import org.jspecify.annotations.Nullable; + +import com.mojang.serialization.Codec; +import com.tcm.MineTale.block.workbenches.menu.BuildersWorkbenchMenu; +import com.tcm.MineTale.recipe.WorkbenchRecipe; +import com.tcm.MineTale.registry.ModBlockEntities; +import com.tcm.MineTale.registry.ModRecipes; +import com.tcm.MineTale.util.Constants; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; + +public class BuildersWorkbenchEntity extends AbstractWorkbenchEntity { + protected final ContainerData data = new ContainerData() { + /** + * Retrieves an internal data value by index for UI synchronization. + * + * @param index There is no cook time or anything for this block as it doesnt use it + * @return the value associated with {@code index}, or 0 for any other index + */ + @Override + public int get(int index) { + return switch (index) { + default -> 0; + }; + } + + /** + * No-op for this workbench; data is server-driven and not set client-side. + * + * `@param` index the data index to set + * `@param` value the value to assign (ignored) + */ + @Override + public void set(int index, int value) { + // Not required on WorkbenchEntity + } + + /** + * The number of data values exposed by this ContainerData. + * + * @return the number of data entries (4) + */ + @Override + public int getCount() { + return 4; + } + }; + + /** + * Creates a BuildersWorkbenchEntity for the specified world position and block state. + * + * Sets the entity's scanRadius to 0.0 and tier to 1. + * + * @param blockPos the world position of this block entity + * @param blockState the block state for this block entity + */ + public BuildersWorkbenchEntity(BlockPos blockPos, BlockState blockState) { + super(ModBlockEntities.BUILDERS_WORKBENCH_BE, blockPos, blockState); + + this.tier = 1; + this.canPullFromNearby = true; + } + + /** + * Persist this workbench's state to the given ValueOutput. + * + * Stores "WorkbenchTier" (int), "ScanRadius" (double), and the full inventory under "Inventory" + * using type-safe Codecs. + * + * @param valueOutput the writer used to serialize this entity's fields + */ + @Override + protected void saveAdditional(ValueOutput valueOutput) { + super.saveAdditional(valueOutput); + // store() uses Codecs for type safety + valueOutput.store("WorkbenchTier", Codec.INT, this.tier); + valueOutput.store("ScanRadius", Codec.DOUBLE, this.scanRadius); + + // Convert the SimpleContainer to a List of ItemStacks for the Codec + // Or use the built-in NBT helper if your framework supports it + List stacks = new ArrayList<>(); + for (int i = 0; i < inventory.getContainerSize(); i++) { + stacks.add(inventory.getItem(i)); + } + + // CHANGE: Use OPTIONAL_CODEC instead of CODEC + valueOutput.store("Inventory", ItemStack.OPTIONAL_CODEC.listOf(), stacks); + } + + /** + * Restores workbench-specific state from persistent storage, applying defaults when keys are absent. + * + * Delegates to the superclass load logic, then: + * - reads "WorkbenchTier" (int) into {@code tier}, defaulting to {@code 1} if missing; + * - reads "ScanRadius" (double) into {@code scanRadius}, defaulting to {@code 0.0} if missing; + * - reads "Inventory" as a list of {@code ItemStack} and populates the internal inventory up to its capacity. + */ + @Override + protected void loadAdditional(ValueInput valueInput) { + super.loadAdditional(valueInput); + // read() returns an Optional + this.tier = valueInput.read("WorkbenchTier", Codec.INT).orElse(1); + this.scanRadius = valueInput.read("ScanRadius", Codec.DOUBLE).orElse(0.0); + + // Read the inventory list back + valueInput.read("Inventory", ItemStack.OPTIONAL_CODEC.listOf()).ifPresent(stacks -> { + for (int i = 0; i < stacks.size() && i < inventory.getContainerSize(); i++) { + inventory.setItem(i, stacks.get(i)); + } + }); + } + + /** + * Creates the server-side container menu for this workbench's UI. + * + * @param syncId the window id used to synchronize the menu with the client + * @param playerInventory the opening player's inventory + * @param player the player who opened the menu + * @return a BuildersWorkbenchMenu bound to this workbench's inventory and synced data + */ + @Override + public @Nullable AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { + // 1. Trigger the sync to the client before returning the menu + if (player instanceof ServerPlayer serverPlayer) { + this.syncNearbyToPlayer(serverPlayer); + } + + // // 2. Return the menu as usual + return new BuildersWorkbenchMenu(syncId, playerInventory, this.data, this); + } + + /** + * Identifies the recipe type used to find and match recipes for this workbench. + * + * @return the RecipeType for workbench recipes + */ + @Override + public RecipeType getWorkbenchRecipeType() { + return ModRecipes.BUILDERS_TYPE; + } + + /** + * Determines whether the workbench currently has fuel available. + * + * Checks that the entity is in a loaded level and that the configured fuel slot contains an item. + * + * @return `true` if the entity is in a loaded level and the fuel slot contains an item, `false` otherwise. + */ + @Override + protected boolean hasFuel() { + if (this.level == null) return false; + + // Check if block is lit + // BlockState state = this.level.getBlockState(this.worldPosition); + // boolean isLit = state.hasProperty(BlockStateProperties.LIT) && state.getValue(BlockStateProperties.LIT); + + boolean hasFuelItem = !this.getItem(Constants.FUEL_SLOT).isEmpty(); + + return hasFuelItem; + } +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/entity/FarmersWorkbenchEntity.java b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FarmersWorkbenchEntity.java new file mode 100644 index 0000000..5af0494 --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/workbenches/entity/FarmersWorkbenchEntity.java @@ -0,0 +1,176 @@ +package com.tcm.MineTale.block.workbenches.entity; + +import java.util.ArrayList; +import java.util.List; + +import org.jspecify.annotations.Nullable; + +import com.mojang.serialization.Codec; +import com.tcm.MineTale.block.workbenches.menu.FarmersWorkbenchMenu; +import com.tcm.MineTale.recipe.WorkbenchRecipe; +import com.tcm.MineTale.registry.ModBlockEntities; +import com.tcm.MineTale.registry.ModRecipes; +import com.tcm.MineTale.util.Constants; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; + +public class FarmersWorkbenchEntity extends AbstractWorkbenchEntity { + protected final ContainerData data = new ContainerData() { + /** + * Provide the container's UI-synchronized integer value for the specified data index. + * + * This workbench exposes four data slots (indices 0–3); all slots are currently unused and always return 0. + * + * @param index the data index to read (expected range: 0–3) + * @return 0 for the requested index + */ + @Override + public int get(int index) { + return switch (index) { + default -> 0; + }; + } + + /** + * Ignore attempts to modify the workbench's container data from the client; changes are a no-op. + * + * @param index the data slot index (ignored) + * @param value the value provided (ignored) + */ + @Override + public void set(int index, int value) { + // Not required on WorkbenchEntity + } + + /** + * The number of data values exposed by this ContainerData. + * + * @return the number of data entries (4) + */ + @Override + public int getCount() { + return 4; + } + }; + + /** + * Create a FarmersWorkbenchEntity at the given world position and block state. + * + * Initializes the workbench tier to 1 and enables pulling from nearby inventories. + * + * @param blockPos the world position of this block entity + * @param blockState the block state for this block entity + */ + public FarmersWorkbenchEntity(BlockPos blockPos, BlockState blockState) { + super(ModBlockEntities.FARMERS_WORKBENCH_BE, blockPos, blockState); + + this.tier = 1; + this.canPullFromNearby = true; + } + + /** + * Writes this workbench's persistent state to the provided output, including the workbench tier, + * scan radius, and full inventory (stored under the keys "WorkbenchTier", "ScanRadius", and "Inventory"). + * + * @param valueOutput the output used to serialize this entity's fields + */ + @Override + protected void saveAdditional(ValueOutput valueOutput) { + super.saveAdditional(valueOutput); + // store() uses Codecs for type safety + valueOutput.store("WorkbenchTier", Codec.INT, this.tier); + valueOutput.store("ScanRadius", Codec.DOUBLE, this.scanRadius); + + // Convert the SimpleContainer to a List of ItemStacks for the Codec + // Or use the built-in NBT helper if your framework supports it + List stacks = new ArrayList<>(); + for (int i = 0; i < inventory.getContainerSize(); i++) { + stacks.add(inventory.getItem(i)); + } + + // CHANGE: Use OPTIONAL_CODEC instead of CODEC + valueOutput.store("Inventory", ItemStack.OPTIONAL_CODEC.listOf(), stacks); + } + + /** + * Restores workbench-specific state from persistent storage, applying defaults when keys are absent. + * + * Delegates to the superclass load logic, then: + * - reads "WorkbenchTier" (int) into {@code tier}, defaulting to {@code 1} if missing; + * - reads "ScanRadius" (double) into {@code scanRadius}, defaulting to {@code 0.0} if missing; + * - reads "Inventory" as a list of {@code ItemStack} and populates the internal inventory up to its capacity. + */ + @Override + protected void loadAdditional(ValueInput valueInput) { + super.loadAdditional(valueInput); + // read() returns an Optional + this.tier = valueInput.read("WorkbenchTier", Codec.INT).orElse(1); + this.scanRadius = valueInput.read("ScanRadius", Codec.DOUBLE).orElse(0.0); + + // Read the inventory list back + valueInput.read("Inventory", ItemStack.OPTIONAL_CODEC.listOf()).ifPresent(stacks -> { + for (int i = 0; i < stacks.size() && i < inventory.getContainerSize(); i++) { + inventory.setItem(i, stacks.get(i)); + } + }); + } + + /** + * Create the server-side menu for a player to interact with this workbench and synchronize nearby data to that player. + * + * @param syncId the window id used to synchronize the menu with the client + * @param playerInventory the opening player's inventory + * @param player the player who opened the menu + * @return the FarmersWorkbenchMenu bound to this workbench's inventory and synchronization data + */ + @Override + public @Nullable AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) { + // 1. Trigger the sync to the client before returning the menu + if (player instanceof ServerPlayer serverPlayer) { + this.syncNearbyToPlayer(serverPlayer); + } + + // // 2. Return the menu as usual + return new FarmersWorkbenchMenu(syncId, playerInventory, this.data, this); + } + + /** + * Identifies the recipe type used to find and match recipes for this workbench. + * + * @return the RecipeType for workbench recipes + */ + @Override + public RecipeType getWorkbenchRecipeType() { + return ModRecipes.FARMERS_TYPE; + } + + /** + * Determines whether the workbench currently has fuel available. + * + * Checks that the entity is in a loaded level and that the configured fuel slot contains an item. + * + * @return `true` if the entity is in a loaded level and the fuel slot contains an item, `false` otherwise. + */ + @Override + protected boolean hasFuel() { + if (this.level == null) return false; + + // Check if block is lit + // BlockState state = this.level.getBlockState(this.worldPosition); + // boolean isLit = state.hasProperty(BlockStateProperties.LIT) && state.getValue(BlockStateProperties.LIT); + + boolean hasFuelItem = !this.getItem(Constants.FUEL_SLOT).isEmpty(); + + return hasFuelItem; + } +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/menu/AbstractWorkbenchContainerMenu.java b/src/main/java/com/tcm/MineTale/block/workbenches/menu/AbstractWorkbenchContainerMenu.java index d5a5007..de486d9 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/menu/AbstractWorkbenchContainerMenu.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/menu/AbstractWorkbenchContainerMenu.java @@ -27,6 +27,7 @@ import net.minecraft.world.item.Items; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.item.crafting.RecipeType; public abstract class AbstractWorkbenchContainerMenu extends RecipeBookMenu implements StackedContentsCompatible { protected final Container container; @@ -37,44 +38,63 @@ public abstract class AbstractWorkbenchContainerMenu extends RecipeBookMenu impl protected final Inventory playerInventory; + @Nullable + private final RecipeType recipeType; + private List networkedNearbyItems = new ArrayList<>(); /** - * Updates the list of items available from nearby chests. - * Called by the networking system when a sync packet arrives. + * Replace the cached list of nearby item stacks supplied by the server for recipe simulation. + * + * Updates the menu's networked nearby-items list with the provided stacks; this list is used + * when the recipe book or placement logic simulates available ingredients from nearby inventories. + * + * @param items the new list of nearby ItemStack instances received from the server */ public void setNetworkedNearbyItems(List items) { this.networkedNearbyItems = items; } /** - * Gets the list of items found in nearby chests. + * Accesses the network-synchronized list of nearby item stacks used to simulate available ingredients. + * + * @return the live list of {@code ItemStack} instances representing nearby items synchronized from the server; entries may be empty */ public List getNetworkedNearbyItems() { return this.networkedNearbyItems; } /** - * Constructs a workbench container menu, initializes inventory and sync state, and opens the container for the player. + * The recipe type used to match recipes for this workbench. + * + * @return the RecipeType for WorkbenchRecipe instances, or {@code null} if no recipe type was provided + */ + public RecipeType getRecipeType() { + return this.recipeType; + } + + /** + * Initialise a workbench container menu, attach the player inventory and hotbar, and open the backing container for the player. * - * The constructor conditionally validates container size and attaches container data slots only when the container actually - * contains slots (i.e., when `outputEnd >= 0` and the container size is > 0). For slotless workbenches it only verifies the - * container is non-null with size 0. If the container has slots, workbench-specific slots are added; the player's inventory - * and hotbar are always added. The container is opened for the provided player. + * If the backing container provides slots (determined by a non-negative `outputEnd` and a positive container size), + * container size and data slot counts are validated and the provided `data` is attached for progress syncing; when the + * container is slotless the constructor validates an empty container and skips slot/data attachment. * - * @param menuType the menu type or null for an unregistered type - * @param syncId synchronization id for the menu - * @param container the underlying container backing this menu - * @param data container data used to sync progress/state (e.g., burn/cook times) - * @param containerDataSize expected size of `data` when the container provides slots; used for data count validation - * @param playerInventory the player's inventory to attach to this menu - * @param inputEnd index (inclusive) of the last input slot in the container - * @param outputEnd index (inclusive) of the last output slot in the container; if negative or container is empty, - * the menu is treated as slotless and slot/data initialization is skipped + * @param menuType the menu type, or {@code null} for an unregistered type + * @param syncId synchronization id for this menu instance + * @param container the backing container for workbench slots (may be empty for slotless workbenches) + * @param data container data used to sync progress/state (e.g. burn/cook times) + * @param containerDataSize expected size of {@code data} when the container provides slots; used for validation + * @param playerInventory the player's inventory to attach to the menu and to open the container for + * @param inputEnd inclusive index of the last input slot in {@code container} + * @param outputEnd inclusive index of the last output slot in {@code container}; negative or empty container + * indicates a slotless workbench + * @param recipeType the recipe type used by this workbench, or {@code null} if none */ - public AbstractWorkbenchContainerMenu(@Nullable MenuType menuType, int syncId, Container container, ContainerData data, int containerDataSize, Inventory playerInventory, int inputEnd, int outputEnd) { + public AbstractWorkbenchContainerMenu(@Nullable MenuType menuType, int syncId, Container container, ContainerData data, int containerDataSize, Inventory playerInventory, int inputEnd, int outputEnd, RecipeType recipeType) { super(menuType, syncId); + this.recipeType = recipeType; this.outputEnd = outputEnd; this.inputEnd = inputEnd; diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/menu/ArmorersWorkbenchMenu.java b/src/main/java/com/tcm/MineTale/block/workbenches/menu/ArmorersWorkbenchMenu.java index 313423a..92e59d4 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/menu/ArmorersWorkbenchMenu.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/menu/ArmorersWorkbenchMenu.java @@ -7,6 +7,7 @@ import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity; import com.tcm.MineTale.recipe.WorkbenchRecipeInput; import com.tcm.MineTale.registry.ModMenuTypes; +import com.tcm.MineTale.registry.ModRecipes; import net.minecraft.world.SimpleContainer; import net.minecraft.world.entity.player.Inventory; @@ -36,13 +37,13 @@ public ArmorersWorkbenchMenu(int syncId, Inventory playerInventory) { } /** - * Creates a workbench menu associated with the given player inventory and optional block entity. + * Creates a workbench menu bound to the given player inventory and optional block entity. * - * Uses an empty internal container (size 0) and the class's data size for syncing numeric state. + * Uses an empty internal container (size 0) and the class's data size to synchronise numeric state. * - * @param syncId synchronization id for this menu + * @param syncId the synchronisation id for this menu * @param playerInventory the player's inventory used for slot access and recipe-book integration - * @param data container data used to sync numeric state between server and client + * @param data container data used to synchronise numeric state between server and client * @param blockEntity nullable block entity this menu is bound to, or {@code null} if not bound */ public ArmorersWorkbenchMenu(int syncId, Inventory playerInventory, ContainerData data, @Nullable AbstractWorkbenchEntity blockEntity) { @@ -56,7 +57,8 @@ public ArmorersWorkbenchMenu(int syncId, Inventory playerInventory, ContainerDat DATA_SIZE, playerInventory, EMPTY_SIZE, - EMPTY_SIZE + EMPTY_SIZE, + ModRecipes.ARMORERS_TYPE ); this.blockEntity = blockEntity; this.playerInventory = playerInventory; @@ -73,27 +75,13 @@ public ArmorersWorkbenchMenu(int syncId, Inventory playerInventory, ContainerDat } /** - * Populate the given StackedItemContents with the items available through this menu for recipe-book calculations. + * Populates the given StackedItemContents with item stacks relevant to crafting lookups. + * + * Includes stacks from the player's inventory, this menu's internal container slots, and + * nearby item stacks supplied via networked data when available. * - * @param stackedItemContents container to receive consolidated item counts from the menu's inventories + * @param contents the StackedItemContents to populate with accounted stacks */ - // @Override - // public void fillCraftSlotsStackedContents(StackedItemContents stackedItemContents) { - // // 1. Tell the book the player has items in their pockets - // this.playerInventory.fillStackedContents(stackedItemContents); - - // // 2. Tell the book the "Nearby Chests" items also count - // AbstractWorkbenchEntity be = this.getBlockEntity(); - // if (be != null && be.isCanPullFromNearby()) { - // // This runs on the CLIENT UI, making the icons turn WHITE - // for (Container nearby : be.getNearbyInventories()) { - // for (int i = 0; i < nearby.getContainerSize(); i++) { - // stackedItemContents.accountStack(nearby.getItem(i)); - // } - // } - // } - // } - @Override public void fillCraftSlotsStackedContents(StackedItemContents contents) { // 1. Account for items in the player's pockets diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/menu/BuildersWorkbenchMenu.java b/src/main/java/com/tcm/MineTale/block/workbenches/menu/BuildersWorkbenchMenu.java new file mode 100644 index 0000000..5fb4e8f --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/workbenches/menu/BuildersWorkbenchMenu.java @@ -0,0 +1,122 @@ +package com.tcm.MineTale.block.workbenches.menu; + +import java.util.List; + +import org.jspecify.annotations.Nullable; + +import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity; +import com.tcm.MineTale.recipe.WorkbenchRecipeInput; +import com.tcm.MineTale.registry.ModMenuTypes; +import com.tcm.MineTale.registry.ModRecipes; + +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.StackedItemContents; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.inventory.RecipeBookType; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.item.ItemStack; + +public class BuildersWorkbenchMenu extends AbstractWorkbenchContainerMenu { + // No internal inventory needed anymore, but we pass an empty container to the super + private static final int EMPTY_SIZE = 0; + private static final int DATA_SIZE = 0; + + @Nullable + private final AbstractWorkbenchEntity blockEntity; + private final Inventory playerInventory; + + /** + * Creates a client-side menu instance when the workbench UI is opened. + * + * @param syncId the synchronization id used to match this menu with the server + * @param playerInventory the player's inventory bound to this menu + */ + public BuildersWorkbenchMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, new SimpleContainerData(EMPTY_SIZE), null); + } + + /** + * Creates a workbench menu associated with the given player inventory and optional block entity. + * + * Uses an empty internal container (size 0) and the class's data size for syncing numeric state. + * + * @param syncId synchronization id for this menu + * @param playerInventory the player's inventory used for slot access and recipe-book integration + * @param data container data used to sync numeric state between server and client + * @param blockEntity nullable block entity this menu is bound to, or {@code null} if not bound + */ + public BuildersWorkbenchMenu(int syncId, Inventory playerInventory, ContainerData data, @Nullable AbstractWorkbenchEntity blockEntity) { + // Note: The order of arguments depends on your AbstractWorkbenchContainerMenu, + // but the 'expectedSize' parameter MUST be 0. + super( + ModMenuTypes.BUILDERS_WORKBENCH_MENU, + syncId, + new SimpleContainer(EMPTY_SIZE), + data, + DATA_SIZE, + playerInventory, + EMPTY_SIZE, + EMPTY_SIZE, + ModRecipes.BUILDERS_TYPE + ); + this.blockEntity = blockEntity; + this.playerInventory = playerInventory; + } + + /** + * Accesses the block entity bound to this menu, if present. + * + * @return the bound AbstractWorkbenchEntity, or {@code null} if this menu is not bound to a block entity + */ + @Override + public @Nullable AbstractWorkbenchEntity getBlockEntity() { + return this.blockEntity; + } + + @Override + public void fillCraftSlotsStackedContents(StackedItemContents contents) { + // 1. Account for items in the player's pockets + this.playerInventory.fillStackedContents(contents); + + // 2. Account for items sitting in the Workbench slots (if any) + for (int i = 0; i < this.container.getContainerSize(); i++) { + contents.accountStack(this.container.getItem(i)); + } + + // 3. THE FIX: Use the list provided by the Packet (Networked Items) + // We stop calling be.getNearbyInventories() here because it returns empty on Client + List nearbyItems = this.getNetworkedNearbyItems(); + + if (!nearbyItems.isEmpty() && this.playerInventory.player.level().isClientSide()) { + System.out.println("DEBUG: Recipe Book is now accounting for " + nearbyItems.size() + " stacks from the packet!"); + } + + for (ItemStack stack : nearbyItems) { + contents.accountStack(stack); + } + } + + /** + * Selects the crafting recipe-book category for this menu. + * + * @return {@code RecipeBookType.CRAFTING} + */ + @Override + public RecipeBookType getRecipeBookType() { + // This keeps the Crafting-style recipe book available on the UI + return RecipeBookType.CRAFTING; + } + + /** + * Create the recipe input used by this menu's crafting UI; this implementation provides an empty input. + * + * @return a WorkbenchRecipeInput with both input stacks set to ItemStack.EMPTY + */ + @Override + public WorkbenchRecipeInput createRecipeInput() { + // Since there are no slots, we return an empty input. + // The actual crafting logic will scan the player inventory directly when a button is clicked. + return new WorkbenchRecipeInput(ItemStack.EMPTY, ItemStack.EMPTY); + } +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/menu/CampfireWorkbenchMenu.java b/src/main/java/com/tcm/MineTale/block/workbenches/menu/CampfireWorkbenchMenu.java index 0245b26..166a2be 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/menu/CampfireWorkbenchMenu.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/menu/CampfireWorkbenchMenu.java @@ -5,6 +5,7 @@ import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity; import com.tcm.MineTale.recipe.WorkbenchRecipeInput; import com.tcm.MineTale.registry.ModMenuTypes; +import com.tcm.MineTale.registry.ModRecipes; import com.tcm.MineTale.util.Constants; import net.minecraft.world.Container; @@ -23,12 +24,11 @@ public class CampfireWorkbenchMenu extends AbstractWorkbenchContainerMenu { private final AbstractWorkbenchEntity blockEntity; /** - * Creates a CampfireWorkbenchMenu using default internal storage and data containers. + * Create a CampfireWorkbenchMenu initialised with the default internal inventory and data containers. * - * Constructs a menu with a new SimpleContainer of size {@code containerSize} and a new - * SimpleContainerData of size {@code containerDataSize}, then delegates to the primary constructor. + * The menu is constructed with a 7‑slot internal container and a data container sized by {@code containerDataSize}. * - * @param syncId synchronization id for the menu + * @param syncId the synchronization id for this menu * @param playerInventory the player's inventory interacting with this menu */ public CampfireWorkbenchMenu(int syncId, Inventory playerInventory) { @@ -36,15 +36,16 @@ public CampfireWorkbenchMenu(int syncId, Inventory playerInventory) { } /** - * Creates a CampfireWorkbenchMenu bound to the given player inventory, container, and container data. + * Create a CampfireWorkbenchMenu bound to the given player inventory, container and container data. * - * @param syncId the synchronization id for this menu (used by the client/server container sync) + * @param syncId the synchronisation id used for client–server container sync * @param playerInventory the player's inventory - * @param container the backing container for the workbench slots - * @param data the container data used for syncing additional numeric state + * @param container the backing container that provides the workbench slots + * @param data the container data used to synchronise numeric state + * @param blockEntity the associated workbench block entity, or {@code null} if not bound to a block */ public CampfireWorkbenchMenu(int syncId, Inventory playerInventory, Container container, ContainerData data, @Nullable AbstractWorkbenchEntity blockEntity) { - super(ModMenuTypes.CAMPFIRE_WORKBENCH_MENU, syncId, container, data, containerDataSize, playerInventory, Constants.INPUT_START + 1, 6); + super(ModMenuTypes.CAMPFIRE_WORKBENCH_MENU, syncId, container, data, containerDataSize, playerInventory, Constants.INPUT_START + 1, 6, ModRecipes.CAMPFIRE_TYPE); this.blockEntity = blockEntity; } diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/menu/FarmersWorkbenchMenu.java b/src/main/java/com/tcm/MineTale/block/workbenches/menu/FarmersWorkbenchMenu.java new file mode 100644 index 0000000..9d5484f --- /dev/null +++ b/src/main/java/com/tcm/MineTale/block/workbenches/menu/FarmersWorkbenchMenu.java @@ -0,0 +1,129 @@ +package com.tcm.MineTale.block.workbenches.menu; + +import java.util.List; + +import org.jspecify.annotations.Nullable; + +import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity; +import com.tcm.MineTale.recipe.WorkbenchRecipeInput; +import com.tcm.MineTale.registry.ModMenuTypes; +import com.tcm.MineTale.registry.ModRecipes; + +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.StackedItemContents; +import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.inventory.RecipeBookType; +import net.minecraft.world.inventory.SimpleContainerData; +import net.minecraft.world.item.ItemStack; + +public class FarmersWorkbenchMenu extends AbstractWorkbenchContainerMenu { + // No internal inventory needed anymore, but we pass an empty container to the super + private static final int EMPTY_SIZE = 0; + private static final int DATA_SIZE = 0; + + @Nullable + private final AbstractWorkbenchEntity blockEntity; + private final Inventory playerInventory; + + /** + * Constructs a client-side FarmersWorkbenchMenu with no bound block entity and empty container data. + * + * @param syncId the menu synchronization id used to match this client menu with its server counterpart + * @param playerInventory the player's inventory to bind to the menu + */ + public FarmersWorkbenchMenu(int syncId, Inventory playerInventory) { + this(syncId, playerInventory, new SimpleContainerData(EMPTY_SIZE), null); + } + + /** + * Initialise a Farmers workbench menu bound to a player's inventory and an optional block entity. + * + * Uses an empty internal container and the menu's container data for server–client numeric syncing. + * + * @param syncId synchronization id for this menu + * @param playerInventory the player's inventory used for slot access and recipe-book integration + * @param data container data used to sync numeric state between server and client + * @param blockEntity the block entity this menu is bound to, or {@code null} if not bound + */ + public FarmersWorkbenchMenu(int syncId, Inventory playerInventory, ContainerData data, @Nullable AbstractWorkbenchEntity blockEntity) { + // Note: The order of arguments depends on your AbstractWorkbenchContainerMenu, + // but the 'expectedSize' parameter MUST be 0. + super( + ModMenuTypes.FARMERS_WORKBENCH_MENU, + syncId, + new SimpleContainer(EMPTY_SIZE), + data, + DATA_SIZE, + playerInventory, + EMPTY_SIZE, + EMPTY_SIZE, + ModRecipes.FARMERS_TYPE + ); + this.blockEntity = blockEntity; + this.playerInventory = playerInventory; + } + + /** + * Accesses the block entity bound to this menu, if present. + * + * @return the bound AbstractWorkbenchEntity, or {@code null} if this menu is not bound to a block entity + */ + @Override + public @Nullable AbstractWorkbenchEntity getBlockEntity() { + return this.blockEntity; + } + + /** + * Populates the provided StackedItemContents with item stacks available for recipe matching. + * + * This accounts for items from the player's inventory, any items stored in the menu's internal container slots, and nearby item stacks supplied via the menu's networked list (used by clients). + * + * @param contents accumulator that will receive accounted item stacks for recipe lookup + */ + @Override + public void fillCraftSlotsStackedContents(StackedItemContents contents) { + // 1. Account for items in the player's pockets + this.playerInventory.fillStackedContents(contents); + + // 2. Account for items sitting in the Workbench slots (if any) + for (int i = 0; i < this.container.getContainerSize(); i++) { + contents.accountStack(this.container.getItem(i)); + } + + // 3. THE FIX: Use the list provided by the Packet (Networked Items) + // We stop calling be.getNearbyInventories() here because it returns empty on Client + List nearbyItems = this.getNetworkedNearbyItems(); + + if (!nearbyItems.isEmpty() && this.playerInventory.player.level().isClientSide()) { + System.out.println("DEBUG: Recipe Book is now accounting for " + nearbyItems.size() + " stacks from the packet!"); + } + + for (ItemStack stack : nearbyItems) { + contents.accountStack(stack); + } + } + + /** + * Selects the crafting recipe-book category for this menu. + * + * @return {@code RecipeBookType.CRAFTING} + */ + @Override + public RecipeBookType getRecipeBookType() { + // This keeps the Crafting-style recipe book available on the UI + return RecipeBookType.CRAFTING; + } + + /** + * Creates an empty WorkbenchRecipeInput for this menu's crafting UI. + * + * @return a WorkbenchRecipeInput with both input stacks set to ItemStack.EMPTY + */ + @Override + public WorkbenchRecipeInput createRecipeInput() { + // Since there are no slots, we return an empty input. + // The actual crafting logic will scan the player inventory directly when a button is clicked. + return new WorkbenchRecipeInput(ItemStack.EMPTY, ItemStack.EMPTY); + } +} \ No newline at end of file diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java b/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java index b12b33f..985edf4 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/menu/FurnaceWorkbenchMenu.java @@ -5,6 +5,7 @@ import com.tcm.MineTale.block.workbenches.entity.AbstractFurnaceWorkbenchEntity; import com.tcm.MineTale.recipe.WorkbenchRecipeInput; import com.tcm.MineTale.registry.ModMenuTypes; +import com.tcm.MineTale.registry.ModRecipes; import com.tcm.MineTale.util.Constants; import net.minecraft.world.Container; @@ -23,7 +24,7 @@ public class FurnaceWorkbenchMenu extends AbstractWorkbenchContainerMenu { private final AbstractFurnaceWorkbenchEntity blockEntity; /** - * Creates a client-side FurnaceWorkbenchMenu with a new internal container and container data. + * Create a client-side FurnaceWorkbenchMenu backed by a fresh internal container and default container data. * * @param syncId the window synchronization id assigned by the client * @param playerInventory the player's inventory to attach to this menu @@ -32,8 +33,17 @@ public FurnaceWorkbenchMenu(int syncId, Inventory playerInventory) { this(syncId, playerInventory, new SimpleContainer(7), new SimpleContainerData(containerDataSize), null); } + /** + * Create a menu instance for a furnace workbench bound to the supplied player inventory, container and optional block entity. + * + * @param syncId the window sync id for client-server menu synchronisation + * @param playerInventory the player's inventory + * @param container the backing container holding this menu's slot items + * @param data container data used to sync menu state (for example progress fields) + * @param blockEntity the associated AbstractFurnaceWorkbenchEntity, or {@code null} when constructed client-side + */ public FurnaceWorkbenchMenu(int syncId, Inventory playerInventory, Container container, ContainerData data, @Nullable AbstractFurnaceWorkbenchEntity blockEntity) { - super(ModMenuTypes.FURNACE_WORKBENCH_MENU, syncId, container, data, containerDataSize, playerInventory, Constants.INPUT_START + 1, 6); + super(ModMenuTypes.FURNACE_WORKBENCH_MENU, syncId, container, data, containerDataSize, playerInventory, Constants.INPUT_START + 1, 6, ModRecipes.FURNACE_T1_TYPE); this.blockEntity = blockEntity; } diff --git a/src/main/java/com/tcm/MineTale/block/workbenches/menu/WorkbenchWorkbenchMenu.java b/src/main/java/com/tcm/MineTale/block/workbenches/menu/WorkbenchWorkbenchMenu.java index 7b8fe06..8266b24 100644 --- a/src/main/java/com/tcm/MineTale/block/workbenches/menu/WorkbenchWorkbenchMenu.java +++ b/src/main/java/com/tcm/MineTale/block/workbenches/menu/WorkbenchWorkbenchMenu.java @@ -7,6 +7,7 @@ import com.tcm.MineTale.block.workbenches.entity.AbstractWorkbenchEntity; import com.tcm.MineTale.recipe.WorkbenchRecipeInput; import com.tcm.MineTale.registry.ModMenuTypes; +import com.tcm.MineTale.registry.ModRecipes; import net.minecraft.world.SimpleContainer; import net.minecraft.world.entity.player.Inventory; @@ -36,17 +37,16 @@ public WorkbenchWorkbenchMenu(int syncId, Inventory playerInventory) { } /** - * Creates a workbench menu associated with the given player inventory and optional block entity. + * Initialises a workbench menu bound to a player inventory and an optional block entity. * - * Uses an empty internal container (size 0) and the class's data size for syncing numeric state. + * The menu uses an empty internal container (size 0) and the class's data size for numeric state syncing. * - * @param syncId synchronization id for this menu - * @param playerInventory the player's inventory used for slot access and recipe-book integration - * @param data container data used to sync numeric state between server and client - * @param blockEntity nullable block entity this menu is bound to, or {@code null} if not bound + * @param syncId synchronization id for this menu + * @param playerInventory the player's inventory used for slot access and recipe-book integration + * @param data container data used to sync numeric state between server and client + * @param blockEntity nullable block entity this menu is bound to, or {@code null} if not bound */ - public WorkbenchWorkbenchMenu(int syncId, Inventory playerInventory, ContainerData data, @Nullable AbstractWorkbenchEntity blockEntity) { - // Note: The order of arguments depends on your AbstractWorkbenchContainerMenu, + public WorkbenchWorkbenchMenu(int syncId, Inventory playerInventory, ContainerData data, @Nullable AbstractWorkbenchEntity blockEntity) { // Note: The order of arguments depends on your AbstractWorkbenchContainerMenu, // but the 'expectedSize' parameter MUST be 0. super( ModMenuTypes.WORKBENCH_WORKBENCH_MENU, @@ -56,7 +56,8 @@ public WorkbenchWorkbenchMenu(int syncId, Inventory playerInventory, ContainerDa DATA_SIZE, playerInventory, EMPTY_SIZE, - EMPTY_SIZE + EMPTY_SIZE, + ModRecipes.WORKBENCH_TYPE ); this.blockEntity = blockEntity; this.playerInventory = playerInventory; @@ -73,27 +74,13 @@ public WorkbenchWorkbenchMenu(int syncId, Inventory playerInventory, ContainerDa } /** - * Populate the given StackedItemContents with the items available through this menu for recipe-book calculations. + * Populate the given StackedItemContents with all item stacks available to the crafting UI. + * + * Accounts for items in the player's inventory, items in this menu's internal container, + * and nearby item stacks synchronised from the server so the recipe book can consider them. * - * @param stackedItemContents container to receive consolidated item counts from the menu's inventories + * @param contents the StackedItemContents to populate with available item stacks */ - // @Override - // public void fillCraftSlotsStackedContents(StackedItemContents stackedItemContents) { - // // 1. Tell the book the player has items in their pockets - // this.playerInventory.fillStackedContents(stackedItemContents); - - // // 2. Tell the book the "Nearby Chests" items also count - // AbstractWorkbenchEntity be = this.getBlockEntity(); - // if (be != null && be.isCanPullFromNearby()) { - // // This runs on the CLIENT UI, making the icons turn WHITE - // for (Container nearby : be.getNearbyInventories()) { - // for (int i = 0; i < nearby.getContainerSize(); i++) { - // stackedItemContents.accountStack(nearby.getItem(i)); - // } - // } - // } - // } - @Override public void fillCraftSlotsStackedContents(StackedItemContents contents) { // 1. Account for items in the player's pockets diff --git a/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java b/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java index 3726b3c..6f05812 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java +++ b/src/main/java/com/tcm/MineTale/registry/ModBlockEntities.java @@ -2,9 +2,11 @@ import com.tcm.MineTale.MineTale; import com.tcm.MineTale.block.workbenches.entity.ArmorersWorkbenchEntity; +import com.tcm.MineTale.block.workbenches.entity.BuildersWorkbenchEntity; import com.tcm.MineTale.block.workbenches.entity.CampfireWorkbenchEntity; import com.tcm.MineTale.block.workbenches.entity.FurnaceWorkbenchEntity; import com.tcm.MineTale.block.workbenches.entity.WorkbenchWorkbenchEntity; +import com.tcm.MineTale.block.workbenches.entity.FarmersWorkbenchEntity; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; import net.minecraft.core.Registry; @@ -32,6 +34,18 @@ public class ModBlockEntities { ArmorersWorkbenchEntity::new, ModBlocks.ARMORERS_WORKBENCH_BLOCK ); + + public static final BlockEntityType FARMERS_WORKBENCH_BE = register( + "farmers_workbench_be", + FarmersWorkbenchEntity::new, + ModBlocks.FARMERS_WORKBENCH_BLOCK + ); + + public static final BlockEntityType BUILDERS_WORKBENCH_BE = register( + "builders_workbench_be", + BuildersWorkbenchEntity::new, + ModBlocks.BUILDERS_WORKBENCH_BLOCK + ); /** * Register a BlockEntityType for the given furnace tier and store it in the tier map. diff --git a/src/main/java/com/tcm/MineTale/registry/ModBlocks.java b/src/main/java/com/tcm/MineTale/registry/ModBlocks.java index f2096e6..d629d2f 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModBlocks.java +++ b/src/main/java/com/tcm/MineTale/registry/ModBlocks.java @@ -6,7 +6,9 @@ import com.tcm.MineTale.MineTale; import com.tcm.MineTale.block.workbenches.ArmorersWorkbench; +import com.tcm.MineTale.block.workbenches.BuildersWorkbench; import com.tcm.MineTale.block.workbenches.CampfireWorkbench; +import com.tcm.MineTale.block.workbenches.FarmersWorkbench; import com.tcm.MineTale.block.workbenches.FurnaceWorkbench; import com.tcm.MineTale.block.workbenches.WorkbenchWorkbench; import com.tcm.MineTale.item.ModCreativeTab; @@ -75,6 +77,20 @@ public class ModBlocks { true ); + public static final Block FARMERS_WORKBENCH_BLOCK = register( + "farmers_workbench", + FarmersWorkbench::new, + BlockBehaviour.Properties.of().sound(SoundType.WOOD), + true + ); + + public static final Block BUILDERS_WORKBENCH_BLOCK = register( + "builders_workbench", + BuildersWorkbench::new, + BlockBehaviour.Properties.of().sound(SoundType.WOOD), + true + ); + //Logs public static final Block AMBER_LOG = register("amber_log", RotatedPillarBlock::new, logProperties(MapColor.COLOR_ORANGE, MapColor.PODZOL, SoundType.WOOD), true); public static final Block BAMBOO_LOG = register("bamboo_log", RotatedPillarBlock::new, logProperties(MapColor.COLOR_GREEN, MapColor.GRASS, SoundType.WOOD), true); @@ -101,6 +117,75 @@ public class ModBlocks { public static final Block WILD_WISTERIA_LOG = register("wild_wisteria_log", RotatedPillarBlock::new, logProperties(MapColor.SAND, MapColor.DIRT, SoundType.WOOD), true); public static final Block WILD_WISTERIA_WOOD = register("wild_wisteria_wood", RotatedPillarBlock::new, BlockBehaviour.Properties.of().mapColor(MapColor.DIRT).instrument(NoteBlockInstrument.BASS).strength(2.0F).sound(SoundType.WOOD).ignitedByLava(), true); + // Base Moss (The raw materials used in recipes) + public static final Block MOSS = register("moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block BLUE_MOSS = register("blue_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block RED_MOSS = register("red_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block YELLOW_MOSS = register("yellow_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block DARK_GREEN_MOSS = register("dark_green_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + + // World Blocks (Natural Soils and Terrains) + public static final Block DRY_MUD = register("dry_mud", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MUD), true); + public static final Block LEAFY_SOIL = register("leafy_soil", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block SOIL_PATHWAY = register("soil_pathway", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block NEEDLED_SOIL = register("needled_soil", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block FULL_GRASS = register("full_grass", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block COLD_DIRT = register("cold_dirt", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block SUMMER_GRASS = register("summer_grass", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block WET_GRASS = register("wet_grass", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block DRY_GRASS = register("dry_grass", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block DRY_DIRT = register("dry_dirt", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block DEEP_GRASS = register("deep_grass", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block POISONED_DIRT = register("poisoned_dirt", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + + // Moss Blocks + public static final Block BLUE_MOSS_BLOCK = register("blue_moss_block", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block DARK_GREEN_MOSS_BLOCK = register("dark_green_moss_block", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block RED_MOSS_BLOCK = register("red_moss_block", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block YELLOW_MOSS_BLOCK = register("yellow_moss_block", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block GREEN_MOSS_BLOCK = register("green_moss_block", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + + // Moss Decoration (Rugs & Hanging) + public static final Block GREEN_MOSS_RUG = register("green_moss_rug", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block BLUE_MOSS_RUG = register("blue_moss_rug", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block DARK_GREEN_MOSS_RUG = register("dark_green_moss_rug", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block YELLOW_MOSS_RUG = register("yellow_moss_rug", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block RED_MOSS_RUG = register("red_moss_rug", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block SORREL_RUG = register("sorrel_rug", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block VINE_RUG = register("vine_rug", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + + public static final Block RED_HANGING_MOSS = register("red_hanging_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block DARK_GREEN_HANGING_MOSS = register("dark_green_hanging_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block BLUE_HANGING_MOSS = register("blue_hanging_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block YELLOW_HANGING_MOSS = register("yellow_hanging_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + public static final Block GREEN_HANGING_MOSS = register("green_hanging_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.MOSS), true); + + // Short Moss & Plants + public static final Block SHORT_RED_MOSS = register("short_red_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block SHORT_YELLOW_MOSS = register("short_yellow_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block SHORT_BLUE_MOSS = register("short_blue_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block SHORT_DARK_GREEN_MOSS = register("short_dark_green_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block SHORT_MOSS = register("short_moss", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + + // Vines & Ivy + public static final Block LIANA = register("liana", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block VINE = register("vine", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block DRY_VINE = register("dry_vine", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block POISONED_IVY = register("poisoned_ivy", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block WALL_IVY = register("wall_ivy", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block IVY = register("ivy", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + + // Specialized Grasses + public static final Block COLD_GRASS = register("cold_grass", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block BURNT_GRASS = register("burnt_grass", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + + // Functional / Crafted + public static final Block BAMBOO_PLANTER = register("bamboo_planter", Block::new, BlockBehaviour.Properties.of().sound(SoundType.WOOD), true); + + // Decorational + public static final Block ROPE = register("rope", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + public static final Block ROPE_DIAGONAL = register("rope_diagonal", Block::new, BlockBehaviour.Properties.of().sound(SoundType.GRASS), true); + // Ores /// 1. COPPER diff --git a/src/main/java/com/tcm/MineTale/registry/ModItems.java b/src/main/java/com/tcm/MineTale/registry/ModItems.java index 47aef66..fbbb289 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModItems.java +++ b/src/main/java/com/tcm/MineTale/registry/ModItems.java @@ -33,7 +33,6 @@ public static void initialize() { public static final Item SAP_GLOB = register("sap_glob", Item::new, new Item.Properties()); public static final Item RUBBLE = register("rubble", Item::new, new Item.Properties()); public static final Item TREE_BARK = register("tree_bark", Item::new, new Item.Properties()); - public static final Item MOSS = register("moss", Item::new, new Item.Properties()); public static final Item BLUE_CRYSTAL_SHARDS = register("blue_crystal_shards", Item::new, new Item.Properties()); public static final Item GREEN_CRYSTAL_SHARDS = register("green_crystal_shards", Item::new, new Item.Properties()); public static final Item YELLOW_CRYSTAL_SHARDS = register("yellow_crystal_shards", Item::new, new Item.Properties()); diff --git a/src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java b/src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java index ea6f050..e15a697 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java +++ b/src/main/java/com/tcm/MineTale/registry/ModMenuTypes.java @@ -3,7 +3,9 @@ import com.tcm.MineTale.MineTale; import com.tcm.MineTale.block.workbenches.menu.FurnaceWorkbenchMenu; import com.tcm.MineTale.block.workbenches.menu.WorkbenchWorkbenchMenu; +import com.tcm.MineTale.block.workbenches.menu.FarmersWorkbenchMenu; import com.tcm.MineTale.block.workbenches.menu.ArmorersWorkbenchMenu; +import com.tcm.MineTale.block.workbenches.menu.BuildersWorkbenchMenu; import com.tcm.MineTale.block.workbenches.menu.CampfireWorkbenchMenu; import net.minecraft.core.Registry; @@ -34,6 +36,16 @@ public class ModMenuTypes { "armorers_workbench_menu", ArmorersWorkbenchMenu::new ); + + public static final MenuType FARMERS_WORKBENCH_MENU = register( + "farmers_workbench_menu", + FarmersWorkbenchMenu::new + ); + + public static final MenuType BUILDERS_WORKBENCH_MENU = register( + "builders_workbench_menu", + BuildersWorkbenchMenu::new + ); /** * Triggers static registration of the mod's menu types. diff --git a/src/main/java/com/tcm/MineTale/registry/ModRecipeDisplay.java b/src/main/java/com/tcm/MineTale/registry/ModRecipeDisplay.java index 175e0ba..692e12e 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModRecipeDisplay.java +++ b/src/main/java/com/tcm/MineTale/registry/ModRecipeDisplay.java @@ -30,11 +30,19 @@ public class ModRecipeDisplay { public static final RecipeDisplay.Type ARMORERS_TYPE = new RecipeDisplay.Type<>(WorkbenchRecipeDisplay.CODEC, STREAM_CODEC); + public static final RecipeDisplay.Type FARMERS_TYPE = + new RecipeDisplay.Type<>(WorkbenchRecipeDisplay.CODEC, STREAM_CODEC); + + public static final RecipeDisplay.Type BUILDERS_TYPE = + new RecipeDisplay.Type<>(WorkbenchRecipeDisplay.CODEC, STREAM_CODEC); + // 1. Declare the fields but don't assign them yet public static final RecipeBookCategory CAMPFIRE_SEARCH = registerCategory("campfire_recipe_book_category"); public static final RecipeBookCategory WORKBENCH_SEARCH = registerCategory("workbench_recipe_book_category"); public static final RecipeBookCategory ARMORERS_SEARCH = registerCategory("armorers_recipe_book_category"); public static final RecipeBookCategory FURNACE_T1_SEARCH = registerCategory("furnace_t1_recipe_book_category"); + public static final RecipeBookCategory FARMERS_SEARCH = registerCategory("farmers_recipe_book_category"); + public static final RecipeBookCategory BUILDERS_SEARCH = registerCategory("builders_recipe_book_category"); /** * Registers the workbench recipe display type into the built-in recipe display registry. diff --git a/src/main/java/com/tcm/MineTale/registry/ModRecipes.java b/src/main/java/com/tcm/MineTale/registry/ModRecipes.java index 842ca60..842b581 100644 --- a/src/main/java/com/tcm/MineTale/registry/ModRecipes.java +++ b/src/main/java/com/tcm/MineTale/registry/ModRecipes.java @@ -17,6 +17,8 @@ public class ModRecipes { public static final RecipeType WORKBENCH_TYPE = createType("workbench_recipe_type"); public static final RecipeType ARMORERS_TYPE = createType("armorers_recipe_type"); public static final RecipeType FURNACE_T1_TYPE = createType("furnace_t1_recipe_type"); + public static final RecipeType FARMERS_TYPE = createType("farmers_recipe_type"); + public static final RecipeType BUILDERS_TYPE = createType("builders_recipe_type"); // 2. Define the Serializers (The "How") // We pass the specific Type into the Serializer's constructor @@ -29,14 +31,20 @@ public class ModRecipes { public static final RecipeSerializer WORKBENCH_SERIALIZER = new WorkbenchRecipe.Serializer(WORKBENCH_TYPE); - public static final RecipeSerializer ARMORERS_SERIALIZER = + public static final RecipeSerializer ARMORERS_SERIALIZER = new WorkbenchRecipe.Serializer(ARMORERS_TYPE); + public static final RecipeSerializer FARMERS_SERIALIZER = + new WorkbenchRecipe.Serializer(FARMERS_TYPE); + + public static final RecipeSerializer BUILDERS_SERIALIZER = + new WorkbenchRecipe.Serializer(BUILDERS_TYPE); + /** - * Registers the mod's recipe types and their serializers into the game's built-in registries. + * Registers the mod's recipe types and their serializers into Minecraft's built-in registries under the mod namespace. * - * Specifically registers the furnace (FURNACE_T1_TYPE), campfire (CAMPFIRE_TYPE), - * and workbench (WORKBENCH_TYPE) recipe types with their corresponding serializers. + * Registers the following recipe types and their corresponding serializers: FURNACE_T1_TYPE, CAMPFIRE_TYPE, + * WORKBENCH_TYPE, ARMORERS_TYPE, and FARMERS_TYPE. */ public static void initialize() { // Register the Furnace-flavored version @@ -48,6 +56,10 @@ public static void initialize() { register(WORKBENCH_TYPE, WORKBENCH_SERIALIZER); register(ARMORERS_TYPE, ARMORERS_SERIALIZER); + + register(FARMERS_TYPE, FARMERS_SERIALIZER); + + register(BUILDERS_TYPE, BUILDERS_SERIALIZER); } /**