Skip to content

Commit 357b705

Browse files
Merge pull request #68 from CodeMonkeysMods/Blacksmith-an-stuff3
Fixed some of #66's problems
2 parents 97a1923 + a978de2 commit 357b705

19 files changed

Lines changed: 953 additions & 177 deletions

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public void onInitializeClient() {
4141
MenuScreens.register(ModMenuTypes.BUILDERS_WORKBENCH_MENU, BuildersWorkbenchScreen::new);
4242
MenuScreens.register(ModMenuTypes.BLACKSMITHS_WORKBENCH_MENU, BlacksmithsWorkbenchScreen::new);
4343
MenuScreens.register(ModMenuTypes.FURNITURE_WORKBENCH_MENU, FurnitureWorkbenchScreen::new);
44+
MenuScreens.register(ModMenuTypes.ALCHEMISTS_WORKBENCH_MENU, AlchemistsWorkbenchScreen::new);
4445

4546
BlockRenderLayerMap.putBlock(ModBlocks.FURNACE_WORKBENCH_BLOCK_T1, ChunkSectionLayer.CUTOUT);
4647
BlockRenderLayerMap.putBlock(ModBlocks.FURNACE_WORKBENCH_BLOCK_T2, ChunkSectionLayer.CUTOUT);
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/datagen/ModBlockTagProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ protected void addTags(HolderLookup.Provider provider) {
3838
.add(ModBlocks.FURNACE_WORKBENCH_BLOCK_T2)
3939
.add(ModBlocks.ARMORERS_WORKBENCH_BLOCK)
4040
.add(ModBlocks.BUILDERS_WORKBENCH_BLOCK)
41-
.add(ModBlocks.BLACKSMITHS_WORKBENCH_BLOCK);
41+
.add(ModBlocks.BLACKSMITHS_WORKBENCH_BLOCK)
42+
.add(ModBlocks.ALCHEMISTS_WORKBENCH_BLOCK);
4243

4344
valueLookupBuilder(BlockTags.MINEABLE_WITH_AXE)
4445
.add(ModBlocks.CAMPFIRE_WORKBENCH_BLOCK)

src/client/java/com/tcm/MineTale/datagen/ModLangProvider.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public void generateTranslations(HolderLookup.Provider wrapperLookup, Translatio
3535
translationBuilder.add("block.minetale.blacksmiths_workbench", "Blacksmith's Workbench");
3636
translationBuilder.add("block.minetale.builders_workbench", "Builder's Workbench");
3737
translationBuilder.add("block.minetale.furniture_workbench", "Furniture Workbench");
38+
translationBuilder.add("block.minetale.alchemists_workbench", "Alchemist's Workbench");
3839

3940
translationBuilder.add("block.minetale.woodcutters_block", "Woodcutter's Block");
4041

@@ -270,6 +271,10 @@ public void generateTranslations(HolderLookup.Provider wrapperLookup, Translatio
270271
translationBuilder.add("item.minetale.essence_of_ice", "Essence of Ice");
271272
translationBuilder.add("item.minetale.essence_of_the_void", "Essence of the Void");
272273
translationBuilder.add("item.minetale.void_heart", "Void Heart");
274+
translationBuilder.add("item.minetale.empty_potion_bottle", "Empty Potion Bottle");
275+
translationBuilder.add("item.minetale.antidote", "Antidote");
276+
translationBuilder.add("item.minetale.boom_powder", "Boom Powder");
277+
translationBuilder.add("item.minetale.popberry_bomb", "Popberry Bomb");
273278

274279
// --- FLORA COMPONENTS ---
275280
translationBuilder.add("item.minetale.red_petals", "Red Petals");
@@ -288,6 +293,18 @@ public void generateTranslations(HolderLookup.Provider wrapperLookup, Translatio
288293
//translationBuilder.add("item.minetale.wood_gauntlets", "Wood Gauntlets");
289294
translationBuilder.add("item.minetale.wood_greaves", "Wood Greaves");
290295
translationBuilder.add("item.minetale.copper_mace", "Copper Mace");
296+
translationBuilder.add("item.minetale.copper_hatchet", "Copper Hatchet");
297+
translationBuilder.add("item.minetale.copper_battleaxe", "Copper Battleaxe");
298+
translationBuilder.add("item.minetale.copper_daggers", "Copper Daggers");
299+
translationBuilder.add("item.minetale.copper_longsword", "Copper Longsword");
300+
translationBuilder.add("item.minetale.copper_shortbow", "Copper Shortbow");
301+
translationBuilder.add("item.minetale.crude_longsword", "Crude Longsword");
302+
translationBuilder.add("item.minetale.crude_builders_hammer", "Crude Builders Hammer");
303+
translationBuilder.add("item.minetale.crude_hatchet", "Crude Hatchet");
304+
translationBuilder.add("item.minetale.crude_mace", "Crude Mace");
305+
translationBuilder.add("item.minetale.crude_daggers", "Crude Daggers");
306+
translationBuilder.add("item.minetale.crude_shortbow", "Crude Shortbow");
307+
translationBuilder.add("item.minetale.crude_sword", "Crude Sword");
291308

292309
// --- GUI ---
293310
translationBuilder.add("gui.minetale.craftbtn", "Craft");

src/client/java/com/tcm/MineTale/datagen/ModLootTableProvider.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ public void generate() {
6464
)
6565
);
6666

67+
this.add(ModBlocks.ALCHEMISTS_WORKBENCH_BLOCK,
68+
LootTable.lootTable() // Use the static factory method to start the builder
69+
.withPool(LootPool.lootPool()
70+
.setRolls(ConstantValue.exactly(1.0F))
71+
.add(LootItem.lootTableItem(ModBlocks.ALCHEMISTS_WORKBENCH_BLOCK))
72+
.when(LootItemBlockStatePropertyCondition.hasBlockStateProperties(ModBlocks.ALCHEMISTS_WORKBENCH_BLOCK)
73+
.setProperties(StatePropertiesPredicate.Builder.properties()
74+
.hasProperty(AbstractWorkbench.HALF, DoubleBlockHalf.LOWER)
75+
.hasProperty(AbstractWorkbench.TYPE, ChestType.LEFT)
76+
)
77+
)
78+
.when(ExplosionCondition.survivesExplosion())
79+
)
80+
);
81+
6782
this.add(ModBlocks.BUILDERS_WORKBENCH_BLOCK,
6883
LootTable.lootTable() // Use the static factory method to start the builder
6984
.withPool(LootPool.lootPool()

0 commit comments

Comments
 (0)