Skip to content

Commit a5a423d

Browse files
committed
feat: Add navigateToGhostIngredients tweak #113
1 parent 428f388 commit a5a423d

8 files changed

Lines changed: 192 additions & 1 deletion

File tree

common/src/main/java/net/blay09/mods/clienttweaks/ClientTweaks.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public void setEnabled(boolean enabled) {
6868
registerTweak(new DoNotUseLastMending());
6969
registerTweak(new ClearCreativeMenuSearch());
7070
registerTweak(new ClearRecipeBookSearch());
71+
registerTweak(new NavigateToGhostIngredients());
7172

7273
ModKeyMappings.initialize(tweaks.values());
7374
MineSingleBlockHandler.initialize();

common/src/main/java/net/blay09/mods/clienttweaks/ClientTweaksConfigData.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ public static class Tweaks {
9898
@Comment("This option makes right clicking the recipe book search bar clear it.")
9999
public boolean clearRecipeBookOnRightClick = true;
100100

101+
@Comment("This option makes clicking ghost ingredients in the crafting grid navigate to it in the recipe book.")
102+
public boolean navigateToGhostIngredients = true;
103+
101104
@Comment("This option retains recipe book search text when reopening the recipe book.")
102105
public boolean retainRecipeBookSearch = true;
103106

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package net.blay09.mods.clienttweaks.mixin;
2+
3+
import net.minecraft.world.item.ItemStack;
4+
import org.spongepowered.asm.mixin.Mixin;
5+
import org.spongepowered.asm.mixin.gen.Accessor;
6+
import org.spongepowered.asm.mixin.gen.Invoker;
7+
8+
import java.util.List;
9+
10+
@Mixin(targets = "net.minecraft.client.gui.screens.recipebook.GhostSlots$GhostSlot")
11+
public interface GhostSlotAccessor {
12+
13+
@Accessor
14+
boolean getIsResultSlot();
15+
16+
@Invoker("getItem")
17+
ItemStack callGetItem(int index);
18+
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package net.blay09.mods.clienttweaks.mixin;
2+
3+
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
4+
import net.minecraft.client.gui.screens.recipebook.GhostSlots;
5+
import net.minecraft.client.gui.screens.recipebook.SlotSelectTime;
6+
import net.minecraft.world.inventory.Slot;
7+
import org.spongepowered.asm.mixin.Mixin;
8+
import org.spongepowered.asm.mixin.gen.Accessor;
9+
10+
@Mixin(GhostSlots.class)
11+
public interface GhostSlotsAccessor {
12+
13+
@Accessor
14+
Reference2ObjectMap<Slot, ?> getIngredients();
15+
16+
@Accessor
17+
SlotSelectTime getSlotSelectTime();
18+
19+
}

common/src/main/java/net/blay09/mods/clienttweaks/mixin/RecipeBookComponentAccessor.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package net.blay09.mods.clienttweaks.mixin;
22

3+
import net.minecraft.client.ClientRecipeBook;
34
import net.minecraft.client.gui.components.EditBox;
5+
import net.minecraft.client.gui.screens.recipebook.GhostSlots;
6+
import net.minecraft.client.gui.screens.recipebook.RecipeBookPage;
47
import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
8+
import net.minecraft.world.item.crafting.display.RecipeDisplayId;
9+
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
510
import org.spongepowered.asm.mixin.Mixin;
611
import org.spongepowered.asm.mixin.gen.Accessor;
712
import org.spongepowered.asm.mixin.gen.Invoker;
@@ -12,7 +17,19 @@ public interface RecipeBookComponentAccessor {
1217
@Accessor
1318
EditBox getSearchBox();
1419

20+
@Accessor
21+
GhostSlots getGhostSlots();
22+
23+
@Accessor
24+
ClientRecipeBook getBook();
25+
26+
@Accessor
27+
RecipeBookPage getRecipeBookPage();
28+
1529
@Invoker
1630
void callCheckSearchStringUpdate();
1731

32+
@Invoker
33+
boolean callTryPlaceRecipe(RecipeCollection recipeCollection, RecipeDisplayId recipeDisplayId, boolean shiftDown);
34+
1835
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package net.blay09.mods.clienttweaks.tweak;
2+
3+
import com.mojang.blaze3d.platform.InputConstants;
4+
import net.blay09.mods.balm.Balm;
5+
import net.blay09.mods.balm.client.platform.event.callback.ScreenCallback;
6+
import net.blay09.mods.balm.mixin.AbstractContainerScreenAccessor;
7+
import net.blay09.mods.clienttweaks.ClientTweaksConfig;
8+
import net.blay09.mods.clienttweaks.ClientTweaksConfigData;
9+
import net.blay09.mods.clienttweaks.mixin.AbstractRecipeBookScreenAccessor;
10+
import net.blay09.mods.clienttweaks.mixin.GhostSlotAccessor;
11+
import net.blay09.mods.clienttweaks.mixin.GhostSlotsAccessor;
12+
import net.blay09.mods.clienttweaks.mixin.RecipeBookComponentAccessor;
13+
import net.minecraft.client.Minecraft;
14+
import net.minecraft.client.gui.screens.Screen;
15+
import net.minecraft.client.gui.screens.inventory.AbstractRecipeBookScreen;
16+
import net.minecraft.client.gui.screens.recipebook.RecipeCollection;
17+
import net.minecraft.client.input.KeyEvent;
18+
import net.minecraft.client.input.MouseButtonEvent;
19+
import net.minecraft.world.item.ItemStack;
20+
import net.minecraft.world.item.crafting.display.RecipeDisplayId;
21+
import net.minecraft.world.item.crafting.display.SlotDisplayContext;
22+
import org.jspecify.annotations.Nullable;
23+
24+
import java.util.ArrayDeque;
25+
import java.util.Deque;
26+
27+
public class NavigateToGhostIngredients extends AbstractClientTweak {
28+
29+
private final Deque<HistoryEntry> history = new ArrayDeque<>();
30+
@Nullable
31+
private HistoryEntry currentEntry;
32+
33+
public NavigateToGhostIngredients() {
34+
super("click_ghost_ingredients_in_recipe_book");
35+
36+
ScreenCallback.Opening.EVENT.register(this::onScreenOpening);
37+
ScreenCallback.MousePress.Before.EVENT.register(this::onMousePress);
38+
ScreenCallback.KeyPress.Before.EVENT.register(this::onKeyPress);
39+
}
40+
41+
private Screen onScreenOpening(Screen screen) {
42+
history.clear();
43+
return screen;
44+
}
45+
46+
private boolean onMousePress(Screen screen, MouseButtonEvent event) {
47+
final var level = Minecraft.getInstance().level;
48+
if (!isEnabled() || event.button() != 0 || !(screen instanceof AbstractRecipeBookScreen) || level == null) {
49+
return false;
50+
}
51+
52+
final var recipeBookComponent = ((AbstractRecipeBookScreenAccessor) screen).getRecipeBookComponent();
53+
if (!recipeBookComponent.isVisible()) {
54+
return false;
55+
}
56+
57+
final var ghostSlots = (GhostSlotsAccessor) ((RecipeBookComponentAccessor) recipeBookComponent).getGhostSlots();
58+
final var hoveredSlot = ((AbstractContainerScreenAccessor) screen).getHoveredSlot();
59+
final var ghostSlot = (GhostSlotAccessor) ghostSlots.getIngredients().get(hoveredSlot);
60+
if (ghostSlot == null || ghostSlot.getIsResultSlot()) {
61+
return false;
62+
}
63+
final var currentIndex = ghostSlots.getSlotSelectTime().currentIndex();
64+
final var ghostItem = ghostSlot.callGetItem(currentIndex);
65+
66+
var recipeBook = ((RecipeBookComponentAccessor) recipeBookComponent).getBook();
67+
var context = SlotDisplayContext.fromLevel(level);
68+
69+
for (final var collection : recipeBook.getCollections()) {
70+
for (final var entry : collection.getRecipes()) {
71+
for (final var result : entry.resultItems(context)) {
72+
if (ItemStack.isSameItemSameComponents(result, ghostItem)) {
73+
if (currentEntry == null) {
74+
final var recipeBookPage = ((RecipeBookComponentAccessor) recipeBookComponent).getRecipeBookPage();
75+
final var initialCollection = recipeBookPage.getLastClickedRecipeCollection();
76+
final var initialRecipeId = recipeBookPage.getLastClickedRecipe();
77+
currentEntry = new HistoryEntry(initialCollection, initialRecipeId);
78+
}
79+
history.add(currentEntry);
80+
currentEntry = new HistoryEntry(collection, entry.id());
81+
((RecipeBookComponentAccessor) recipeBookComponent).callTryPlaceRecipe(collection, entry.id(), event.hasShiftDown());
82+
return true;
83+
}
84+
}
85+
}
86+
}
87+
88+
return false;
89+
}
90+
91+
private boolean onKeyPress(Screen screen, KeyEvent event) {
92+
if (!isEnabled() || event.key() != InputConstants.KEY_BACKSPACE || !(screen instanceof AbstractRecipeBookScreen)) {
93+
return false;
94+
}
95+
96+
final var recipeBookComponent = ((AbstractRecipeBookScreenAccessor) screen).getRecipeBookComponent();
97+
if (!recipeBookComponent.isVisible()) {
98+
return false;
99+
}
100+
101+
final var searchBox = ((RecipeBookComponentAccessor) recipeBookComponent).getSearchBox();
102+
if (searchBox != null && searchBox.isFocused()) {
103+
return false;
104+
}
105+
106+
if (history.isEmpty()) {
107+
return false;
108+
}
109+
110+
currentEntry = history.pop();
111+
((RecipeBookComponentAccessor) recipeBookComponent).callTryPlaceRecipe(currentEntry.collection(), currentEntry.recipeDisplayId(), false);
112+
return true;
113+
}
114+
115+
@Override
116+
public boolean isEnabled() {
117+
return ClientTweaksConfig.getActive().tweaks.navigateToGhostIngredients;
118+
}
119+
120+
@Override
121+
public void setEnabled(boolean enabled) {
122+
Balm.config().updateLocalConfig(ClientTweaksConfigData.class, it -> it.tweaks.navigateToGhostIngredients = enabled);
123+
}
124+
125+
private record HistoryEntry(RecipeCollection collection, RecipeDisplayId recipeDisplayId) {
126+
}
127+
128+
}

common/src/main/resources/assets/clienttweaks/lang/en_us.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@
6969
"clienttweaks.configuration.tweaks.creativeBreakingSupport.tooltip": "This option will increase the hitbox of random-offset blocks in creative mode, making it easier to break them quickly.",
7070
"clienttweaks.configuration.tweaks.clearRecipeBookOnRightClick": "Clear Recipe Book Search",
7171
"clienttweaks.configuration.tweaks.clearRecipeBookOnRightClick.tooltip": "This option makes right clicking the recipe book search bar clear it.",
72+
"clienttweaks.configuration.tweaks.navigateToGhostIngredients": "Navigate to Ghost Ingredients",
73+
"clienttweaks.configuration.tweaks.navigateToGhostIngredients.tooltip": "This option makes clicking ghost ingredients in the crafting grid navigate to it in the recipe book.",
7274
"clienttweaks.configuration.tweaks.retainRecipeBookSearch": "Retain Recipe Book Search",
7375
"clienttweaks.configuration.tweaks.retainRecipeBookSearch.tooltip": "This option retains recipe book search text when reopening the recipe book.",
7476
"clienttweaks.configuration.tweaks.clearCreativeMenuSearchOnRightClick": "Clear Creative Menu Search",

common/src/main/resources/clienttweaks.mixins.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
"LivingEntityMixin",
2121
"LivingEntityAccessor",
2222
"AbstractRecipeBookScreenAccessor",
23-
"RecipeBookComponentAccessor"
23+
"RecipeBookComponentAccessor",
24+
"GhostSlotsAccessor",
25+
"GhostSlotAccessor"
2426
],
2527
"injectors": {
2628
"defaultRequire": 1

0 commit comments

Comments
 (0)