Skip to content

Commit ee743ae

Browse files
Merge branch 'Andy/test-inventory-display-items' of https://github.com/CodeMonkeysMods/MineTale into Andy/test-inventory-display-items
2 parents ccb4650 + 1350000 commit ee743ae

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+4773
-625
lines changed

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
package com.tcm.MineTale;
22

3-
import com.tcm.MineTale.block.workbenches.screen.FurnaceWorkbenchScreen;
4-
import com.tcm.MineTale.block.workbenches.screen.WorkbenchWorkbenchScreen;
3+
import com.tcm.MineTale.block.workbenches.screen.*;
54
import com.tcm.MineTale.network.ClientboundNearbyInventorySyncPacket;
65

76
import java.util.List;
87

98
import com.tcm.MineTale.block.workbenches.menu.AbstractWorkbenchContainerMenu;
10-
import com.tcm.MineTale.block.workbenches.screen.ArmorersWorkbenchScreen;
11-
import com.tcm.MineTale.block.workbenches.screen.BuildersWorkbenchScreen;
12-
import com.tcm.MineTale.block.workbenches.screen.CampfireWorkbenchScreen;
13-
import com.tcm.MineTale.block.workbenches.screen.FarmersWorkbenchScreen;
149
import com.tcm.MineTale.registry.ModBlocks;
1510
import com.tcm.MineTale.registry.ModMenuTypes;
1611

@@ -43,6 +38,9 @@ public void onInitializeClient() {
4338
MenuScreens.register(ModMenuTypes.ARMORERS_WORKBENCH_MENU, ArmorersWorkbenchScreen::new);
4439
MenuScreens.register(ModMenuTypes.FARMERS_WORKBENCH_MENU, FarmersWorkbenchScreen::new);
4540
MenuScreens.register(ModMenuTypes.BUILDERS_WORKBENCH_MENU, BuildersWorkbenchScreen::new);
41+
MenuScreens.register(ModMenuTypes.BLACKSMITHS_WORKBENCH_MENU, BlacksmithsWorkbenchScreen::new);
42+
MenuScreens.register(ModMenuTypes.FURNITURE_WORKBENCH_MENU, FurnitureWorkbenchScreen::new);
43+
MenuScreens.register(ModMenuTypes.ALCHEMISTS_WORKBENCH_MENU, AlchemistsWorkbenchScreen::new);
4644

4745
BlockRenderLayerMap.putBlock(ModBlocks.FURNACE_WORKBENCH_BLOCK_T1, ChunkSectionLayer.CUTOUT);
4846
BlockRenderLayerMap.putBlock(ModBlocks.FURNACE_WORKBENCH_BLOCK_T2, ChunkSectionLayer.CUTOUT);

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
public class MineTaleDataGen implements DataGeneratorEntrypoint {
99

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

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,12 @@ private static MineTaleRecipeBookComponent createRecipeBookComponent(ArmorersWor
8888
}
8989

9090
/**
91-
* Initialises the screen size and adds the three crafting buttons.
91+
* Initialises the screen size and creates three crafting buttons.
9292
*
93-
* Sets the GUI image dimensions, delegates further initialisation to the superclass,
94-
* and creates/registers three buttons wired to craft one, ten or all items
95-
* (they invoke handleCraftRequest with 1, 10 and -1 respectively; -1 signifies "All").
93+
* Sets the GUI image dimensions, performs superclass initialisation, and adds
94+
* three buttons that request crafting of 1, 10 or all items. Each button calls
95+
* {@code handleCraftRequest} with arguments 1, 10 and -1 respectively; -1
96+
* signifies "craft all".
9697
*/
9798
@Override
9899
protected void init() {
@@ -105,15 +106,15 @@ protected void init() {
105106
int defaultLeft = this.leftPos + 90;
106107
int defaultTop = this.topPos + 25;
107108

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

112113
this.craftTenBtn = addRenderableWidget(Button.builder(Component.literal("x10"), (button) -> {
113114
handleCraftRequest(10);
114115
}).bounds(defaultLeft, defaultTop + 22, 35, 20).build());
115116

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

0 commit comments

Comments
 (0)