Skip to content

Commit 09a6560

Browse files
committed
Added A Whole lotta stuff includes the Furniture Workbench
1 parent b71a892 commit 09a6560

30 files changed

Lines changed: 1482 additions & 411 deletions

src/client/java/com/tcm/MineTale/MineTaleClient.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public void onInitializeClient() {
4040
MenuScreens.register(ModMenuTypes.FARMERS_WORKBENCH_MENU, FarmersWorkbenchScreen::new);
4141
MenuScreens.register(ModMenuTypes.BUILDERS_WORKBENCH_MENU, BuildersWorkbenchScreen::new);
4242
MenuScreens.register(ModMenuTypes.BLACKSMITHS_WORKBENCH_MENU, BlacksmithsWorkbenchScreen::new);
43+
MenuScreens.register(ModMenuTypes.FURNITURE_WORKBENCH_MENU, FurnitureWorkbenchScreen::new);
4344

4445
BlockRenderLayerMap.putBlock(ModBlocks.FURNACE_WORKBENCH_BLOCK_T1, ChunkSectionLayer.CUTOUT);
4546
BlockRenderLayerMap.putBlock(ModBlocks.FURNACE_WORKBENCH_BLOCK_T2, ChunkSectionLayer.CUTOUT);

src/client/java/com/tcm/MineTale/block/workbenches/screen/ArmorersWorkbenchScreen.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,15 @@ protected void init() {
105105
int defaultLeft = this.leftPos + 90;
106106
int defaultTop = this.topPos + 25;
107107

108-
this.craftOneBtn = addRenderableWidget(Button.builder(Component.literal("Craft"), (button) -> {
108+
this.craftOneBtn = addRenderableWidget(Button.builder(Component.translatable("gui.minetale.craftbtn"), (button) -> {
109109
handleCraftRequest(1);
110110
}).bounds(defaultLeft, defaultTop, 75, 20).build());
111111

112112
this.craftTenBtn = addRenderableWidget(Button.builder(Component.literal("x10"), (button) -> {
113113
handleCraftRequest(10);
114114
}).bounds(defaultLeft, defaultTop + 22, 35, 20).build());
115115

116-
this.craftAllBtn = addRenderableWidget(Button.builder(Component.literal("All"), (button) -> {
116+
this.craftAllBtn = addRenderableWidget(Button.builder(Component.translatable("gui.minetale.allbtn"), (button) -> {
117117
handleCraftRequest(-1); // -1 represents "All" logic
118118
}).bounds(defaultLeft + 40, defaultTop + 22, 35, 20).build());
119119
}

src/client/java/com/tcm/MineTale/block/workbenches/screen/BlacksmithsWorkbenchScreen.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,15 @@ protected void init() {
106106
int defaultLeft = this.leftPos + 90;
107107
int defaultTop = this.topPos + 25;
108108

109-
this.craftOneBtn = addRenderableWidget(Button.builder(Component.literal("Craft"), (button) -> {
109+
this.craftOneBtn = addRenderableWidget(Button.builder(Component.translatable("gui.minetale.craftbtn"), (button) -> {
110110
handleCraftRequest(1);
111111
}).bounds(defaultLeft, defaultTop, 75, 20).build());
112112

113113
this.craftTenBtn = addRenderableWidget(Button.builder(Component.literal("x10"), (button) -> {
114114
handleCraftRequest(10);
115115
}).bounds(defaultLeft, defaultTop + 22, 35, 20).build());
116116

117-
this.craftAllBtn = addRenderableWidget(Button.builder(Component.literal("All"), (button) -> {
117+
this.craftAllBtn = addRenderableWidget(Button.builder(Component.translatable("gui.minetale.allbtn"), (button) -> {
118118
handleCraftRequest(-1); // -1 represents "All" logic
119119
}).bounds(defaultLeft + 40, defaultTop + 22, 35, 20).build());
120120
}

src/client/java/com/tcm/MineTale/block/workbenches/screen/BuildersWorkbenchScreen.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,15 @@ protected void init() {
105105
int defaultLeft = this.leftPos + 90;
106106
int defaultTop = this.topPos + 25;
107107

108-
this.craftOneBtn = addRenderableWidget(Button.builder(Component.literal("Craft"), (button) -> {
108+
this.craftOneBtn = addRenderableWidget(Button.builder(Component.translatable("gui.minetale.craftbtn"), (button) -> {
109109
handleCraftRequest(1);
110110
}).bounds(defaultLeft, defaultTop, 75, 20).build());
111111

112112
this.craftTenBtn = addRenderableWidget(Button.builder(Component.literal("x10"), (button) -> {
113113
handleCraftRequest(10);
114114
}).bounds(defaultLeft, defaultTop + 22, 35, 20).build());
115115

116-
this.craftAllBtn = addRenderableWidget(Button.builder(Component.literal("All"), (button) -> {
116+
this.craftAllBtn = addRenderableWidget(Button.builder(Component.translatable("gui.minetale.allbtn"), (button) -> {
117117
handleCraftRequest(-1); // -1 represents "All" logic
118118
}).bounds(defaultLeft + 40, defaultTop + 22, 35, 20).build());
119119
}

src/client/java/com/tcm/MineTale/block/workbenches/screen/FarmersWorkbenchScreen.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,15 @@ protected void init() {
106106
int defaultLeft = this.leftPos + 90;
107107
int defaultTop = this.topPos + 25;
108108

109-
this.craftOneBtn = addRenderableWidget(Button.builder(Component.literal("Craft"), (button) -> {
109+
this.craftOneBtn = addRenderableWidget(Button.builder(Component.translatable("gui.minetale.craftbtn"), (button) -> {
110110
handleCraftRequest(1);
111111
}).bounds(defaultLeft, defaultTop, 75, 20).build());
112112

113113
this.craftTenBtn = addRenderableWidget(Button.builder(Component.literal("x10"), (button) -> {
114114
handleCraftRequest(10);
115115
}).bounds(defaultLeft, defaultTop + 22, 35, 20).build());
116116

117-
this.craftAllBtn = addRenderableWidget(Button.builder(Component.literal("All"), (button) -> {
117+
this.craftAllBtn = addRenderableWidget(Button.builder(Component.translatable("gui.minetale.allbtn"), (button) -> {
118118
handleCraftRequest(-1); // -1 represents "All" logic
119119
}).bounds(defaultLeft + 40, defaultTop + 22, 35, 20).build());
120120
}

src/client/java/com/tcm/MineTale/block/workbenches/screen/FurnaceWorkbenchScreen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import net.minecraft.world.item.ItemStack;
2020
import net.minecraft.network.chat.Component;
2121

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

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
package com.tcm.MineTale.block.workbenches.screen;
2+
3+
import com.tcm.MineTale.MineTale;
4+
import com.tcm.MineTale.block.workbenches.menu.AbstractWorkbenchContainerMenu;
5+
import com.tcm.MineTale.block.workbenches.menu.BlacksmithsWorkbenchMenu;
6+
import com.tcm.MineTale.block.workbenches.menu.FurnitureWorkbenchMenu;
7+
import com.tcm.MineTale.mixin.client.ClientRecipeBookAccessor;
8+
import com.tcm.MineTale.network.CraftRequestPayload;
9+
import com.tcm.MineTale.recipe.MineTaleRecipeBookComponent;
10+
import com.tcm.MineTale.registry.ModBlocks;
11+
import com.tcm.MineTale.registry.ModRecipeDisplay;
12+
import com.tcm.MineTale.registry.ModRecipes;
13+
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
14+
import net.minecraft.client.ClientRecipeBook;
15+
import net.minecraft.client.gui.GuiGraphics;
16+
import net.minecraft.client.gui.components.Button;
17+
import net.minecraft.client.gui.navigation.ScreenPosition;
18+
import net.minecraft.client.gui.screens.inventory.AbstractRecipeBookScreen;
19+
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
20+
import net.minecraft.client.renderer.RenderPipelines;
21+
import net.minecraft.core.Holder;
22+
import net.minecraft.network.chat.Component;
23+
import net.minecraft.resources.Identifier;
24+
import net.minecraft.world.entity.player.Inventory;
25+
import net.minecraft.world.entity.player.Player;
26+
import net.minecraft.world.item.Item;
27+
import net.minecraft.world.item.ItemStack;
28+
import net.minecraft.world.item.crafting.Ingredient;
29+
import net.minecraft.world.item.crafting.display.RecipeDisplayEntry;
30+
import net.minecraft.world.item.crafting.display.RecipeDisplayId;
31+
import net.minecraft.world.item.crafting.display.SlotDisplayContext;
32+
33+
import java.util.HashMap;
34+
import java.util.List;
35+
import java.util.Map;
36+
import java.util.Optional;
37+
38+
public class FurnitureWorkbenchScreen extends AbstractRecipeBookScreen<FurnitureWorkbenchMenu> {
39+
private static final Identifier TEXTURE =
40+
Identifier.fromNamespaceAndPath(MineTale.MOD_ID, "textures/gui/container/workbench_workbench.png");
41+
42+
private final MineTaleRecipeBookComponent mineTaleRecipeBook;
43+
44+
private RecipeDisplayId lastKnownSelectedId = null;
45+
46+
private Button craftOneBtn;
47+
private Button craftTenBtn;
48+
private Button craftAllBtn;
49+
50+
/**
51+
* Initialize a workbench GUI screen using the provided container menu, player inventory, and title.
52+
*
53+
* @param menu the menu supplying slots and synchronized state for this screen
54+
* @param inventory the player's inventory to display and interact with
55+
* @param title the title component shown at the top of the screen
56+
*/
57+
public FurnitureWorkbenchScreen(FurnitureWorkbenchMenu menu, Inventory inventory, Component title) {
58+
this(menu, inventory, title, createRecipeBookComponent(menu));
59+
}
60+
61+
/**
62+
* Creates a WorkbenchWorkbenchScreen bound to the given menu, player inventory, title, and recipe book component.
63+
*
64+
* @param menu the menu backing this screen
65+
* @param inventory the player's inventory shown in the screen
66+
* @param title the screen title component
67+
* @param recipeBook the MineTaleRecipeBookComponent used to display and manage recipes in this screen
68+
*/
69+
private FurnitureWorkbenchScreen(FurnitureWorkbenchMenu menu, Inventory inventory, Component title, MineTaleRecipeBookComponent recipeBook) {
70+
super(menu, recipeBook, inventory, title);
71+
this.mineTaleRecipeBook = recipeBook;
72+
}
73+
74+
/**
75+
* Create a MineTaleRecipeBookComponent configured for the workbench screen.
76+
*
77+
* @param menu the workbench menu used to initialize the recipe book component
78+
* @return a MineTaleRecipeBookComponent containing the workbench tab and associated recipe category
79+
*/
80+
private static MineTaleRecipeBookComponent createRecipeBookComponent(FurnitureWorkbenchMenu menu) {
81+
ItemStack tabIcon = new ItemStack(ModBlocks.FURNITURE_WORKBENCH_BLOCK.asItem());
82+
83+
List<RecipeBookComponent.TabInfo> tabs = List.of(
84+
new RecipeBookComponent.TabInfo(tabIcon.getItem(), ModRecipeDisplay.FURNITURE_SEARCH)
85+
);
86+
87+
return new MineTaleRecipeBookComponent(menu, tabs, ModRecipes.FURNITURE_TYPE);
88+
}
89+
90+
/**
91+
* Initialises the workbench screen's GUI size and interactive widgets.
92+
*
93+
* Sets the screen image dimensions, delegates remaining setup to the superclass,
94+
* computes default button positions and creates three craft buttons:
95+
* - "Craft" (requests 1),
96+
* - "x10" (requests 10),
97+
* - "All" (requests -1 to indicate all).
98+
*/
99+
@Override
100+
protected void init() {
101+
// Important: Set your GUI size before super.init()
102+
this.imageWidth = 176;
103+
this.imageHeight = 166;
104+
105+
super.init();
106+
107+
int defaultLeft = this.leftPos + 90;
108+
int defaultTop = this.topPos + 25;
109+
110+
this.craftOneBtn = addRenderableWidget(Button.builder(Component.translatable("gui.minetale.craftbtn"), (button) -> {
111+
handleCraftRequest(1);
112+
}).bounds(defaultLeft, defaultTop, 75, 20).build());
113+
114+
this.craftTenBtn = addRenderableWidget(Button.builder(Component.literal("x10"), (button) -> {
115+
handleCraftRequest(10);
116+
}).bounds(defaultLeft, defaultTop + 22, 35, 20).build());
117+
118+
this.craftAllBtn = addRenderableWidget(Button.builder(Component.translatable("gui.minetale.allbtn"), (button) -> {
119+
handleCraftRequest(-1); // -1 represents "All" logic
120+
}).bounds(defaultLeft + 40, defaultTop + 22, 35, 20).build());
121+
}
122+
123+
/**
124+
* Request crafting for the currently selected recipe from the integrated recipe book.
125+
*
126+
* If a recipe is selected, sends a CraftRequestPayload to the server for that recipe and the
127+
* specified quantity. If no recipe is selected, no request is sent.
128+
*
129+
* @param amount the quantity to craft; use -1 to request crafting of the full available stack ("All")
130+
*/
131+
private void handleCraftRequest(int amount) {
132+
// Look at our "Memory" instead of the component
133+
if (this.lastKnownSelectedId != null) {
134+
ClientRecipeBook book = this.minecraft.player.getRecipeBook();
135+
RecipeDisplayEntry entry = ((ClientRecipeBookAccessor) book).getKnown().get(this.lastKnownSelectedId);
136+
137+
if (entry != null) {
138+
List<ItemStack> results = entry.resultItems(SlotDisplayContext.fromLevel(this.minecraft.level));
139+
if (!results.isEmpty()) {
140+
System.out.println("Persistent Selection Success: " + results.get(0));
141+
ClientPlayNetworking.send(new CraftRequestPayload(results.get(0), amount));
142+
return;
143+
}
144+
}
145+
}
146+
System.out.println("Request failed: No recipe was ever selected!");
147+
}
148+
149+
/**
150+
* Draws the workbench background texture at the screen's current GUI origin.
151+
*
152+
* @param guiGraphics the graphics context used to draw GUI elements
153+
* @param f partial tick time for interpolation
154+
* @param i current mouse x coordinate
155+
* @param j current mouse y coordinate
156+
*/
157+
protected void renderBg(GuiGraphics guiGraphics, float f, int i, int j) {
158+
int k = this.leftPos;
159+
int l = this.topPos;
160+
guiGraphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, k, l, 0.0F, 0.0F, this.imageWidth, this.imageHeight, 256, 256);
161+
}
162+
163+
/**
164+
* Render the screen, remember the current recipe selection and update craft-button availability.
165+
*
166+
* Remembers the recipe selected in the recipe book, resolves that selection against the client's known recipes when possible,
167+
* sets the craft buttons active or inactive according to whether the player has sufficient ingredients for counts of 1, 2 and 10,
168+
* renders the background, the superclass UI and any tooltips.
169+
*/
170+
@Override
171+
public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
172+
renderBackground(graphics, mouseX, mouseY, delta);
173+
super.render(graphics, mouseX, mouseY, delta);
174+
175+
// 1. Get the current selection from the book
176+
RecipeDisplayId currentId = this.mineTaleRecipeBook.getSelectedRecipeId();
177+
178+
// 2. If it's NOT null, remember it!
179+
if (currentId != null) {
180+
this.lastKnownSelectedId = currentId;
181+
}
182+
183+
// 3. Use the remembered ID to find the entry for button activation
184+
RecipeDisplayEntry selectedEntry = null;
185+
if (this.lastKnownSelectedId != null && this.minecraft.level != null) {
186+
ClientRecipeBook book = this.minecraft.player.getRecipeBook();
187+
selectedEntry = ((ClientRecipeBookAccessor) book).getKnown().get(this.lastKnownSelectedId);
188+
}
189+
190+
// 2. Button Activation Logic
191+
if (selectedEntry != null) {
192+
// We use the entry directly. It contains the 15 ingredients needed!
193+
boolean canCraftOne = canCraft(this.minecraft.player, selectedEntry, 1);
194+
boolean canCraftMoreThanOne = canCraft(this.minecraft.player, selectedEntry, 2);
195+
boolean canCraftTen = canCraft(this.minecraft.player, selectedEntry, 10);
196+
197+
this.craftOneBtn.active = canCraftOne;
198+
this.craftTenBtn.active = canCraftTen;
199+
this.craftAllBtn.active = canCraftMoreThanOne;
200+
} else {
201+
this.craftOneBtn.active = false;
202+
this.craftTenBtn.active = false;
203+
this.craftAllBtn.active = false;
204+
}
205+
206+
renderTooltip(graphics, mouseX, mouseY);
207+
}
208+
209+
/**
210+
* Determines whether the player has enough ingredients to craft the given recipe the specified number of times.
211+
*
212+
* @param player the player whose inventory (and networked nearby items) will be checked; may be null
213+
* @param entry the recipe display entry providing crafting requirements; may be null
214+
* @param craftCount the multiplier for required ingredient quantities (e.g., 1, 10, or -1 is not specially handled here)
215+
* @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)
216+
*/
217+
private boolean canCraft(Player player, RecipeDisplayEntry entry, int craftCount) {
218+
if (player == null || entry == null) return false;
219+
220+
Optional<List<Ingredient>> reqs = entry.craftingRequirements();
221+
if (reqs.isEmpty()) return false;
222+
223+
// 1. Group ingredients by their underlying Item Holders.
224+
// Using List<Holder<Item>> as the key ensures structural equality (content-based hashing).
225+
Map<List<Holder<Item>>, Integer> aggregatedRequirements = new HashMap<>();
226+
Map<List<Holder<Item>>, Ingredient> holderToIngredient = new HashMap<>();
227+
228+
for (Ingredient ing : reqs.get()) {
229+
// Collect holders into a List to get a stable hashCode() and equals()
230+
@SuppressWarnings("deprecation")
231+
List<Holder<Item>> key = ing.items().toList();
232+
233+
// Aggregate the counts (how many of this specific ingredient set are required)
234+
aggregatedRequirements.put(key, aggregatedRequirements.getOrDefault(key, 0) + 1);
235+
236+
// Map the list back to the original ingredient for use in hasIngredientAmount
237+
holderToIngredient.putIfAbsent(key, ing);
238+
}
239+
240+
// 2. Check the player's inventory against the aggregated totals
241+
Inventory inv = player.getInventory();
242+
for (Map.Entry<List<Holder<Item>>, Integer> entryReq : aggregatedRequirements.entrySet()) {
243+
List<Holder<Item>> key = entryReq.getKey();
244+
int totalNeeded = entryReq.getValue() * craftCount;
245+
246+
// Retrieve the original Ingredient object associated with this list of holders
247+
Ingredient originalIng = holderToIngredient.get(key);
248+
249+
if (!hasIngredientAmount(inv, originalIng, totalNeeded)) {
250+
return false;
251+
}
252+
}
253+
254+
return true;
255+
}
256+
257+
private boolean hasIngredientAmount(Inventory inventory, Ingredient ingredient, int totalRequired) {
258+
System.out.println("DEBUG: Searching inventory + nearby for " + totalRequired + "...");
259+
if (totalRequired <= 0) return true;
260+
261+
int found = 0;
262+
263+
// 1. Check Player Inventory
264+
for (int i = 0; i < inventory.getContainerSize(); i++) {
265+
ItemStack stack = inventory.getItem(i);
266+
if (!stack.isEmpty() && ingredient.test(stack)) {
267+
found += stack.getCount();
268+
}
269+
}
270+
271+
// 2. CHECK THE NETWORKED ITEMS FROM CHESTS
272+
// This is the list we sent via the packet!
273+
if (this.menu instanceof AbstractWorkbenchContainerMenu workbenchMenu) {
274+
for (ItemStack stack : workbenchMenu.getNetworkedNearbyItems()) {
275+
if (!stack.isEmpty() && ingredient.test(stack)) {
276+
found += stack.getCount();
277+
System.out.println("DEBUG: Found " + stack.getCount() + " in nearby networked list. Total: " + found);
278+
}
279+
}
280+
}
281+
282+
if (found >= totalRequired) {
283+
System.out.println("DEBUG: Requirement MET with " + found + "/" + totalRequired);
284+
return true;
285+
}
286+
287+
System.out.println("DEBUG: FAILED. Only found: " + found + "/" + totalRequired);
288+
return false;
289+
}
290+
291+
/**
292+
* Computes the on-screen position for the recipe book toggle button for this GUI.
293+
*
294+
* @return the screen position placed 5 pixels from the GUI's left edge and 49 pixels above the GUI's vertical center
295+
*/
296+
@Override
297+
protected ScreenPosition getRecipeBookButtonPosition() {
298+
// 1. Calculate the start (left) of your workbench GUI
299+
int guiLeft = (this.width - this.imageWidth) / 2;
300+
301+
// 2. Calculate the top of your workbench GUI
302+
int guiTop = (this.height - this.imageHeight) / 2;
303+
304+
// 3. Standard Vanilla positioning:
305+
// Usually 5 pixels in from the left and 49 pixels up from the center
306+
return new ScreenPosition(guiLeft + 5, guiTop + this.imageHeight / 2 - 49);
307+
}
308+
}

0 commit comments

Comments
 (0)