Skip to content

Commit 64e2780

Browse files
authored
Merge pull request #128 from sylviameows/1.21
add an advanced middle clicking option
2 parents e982bab + e9efb9c commit 64e2780

File tree

11 files changed

+207
-16
lines changed

11 files changed

+207
-16
lines changed

src/main/java/dev/dfonline/codeclient/ChestFeature.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public boolean charTyped(char chr, int modifiers) {
4141
return false;
4242
}
4343

44-
public void clickSlot(Slot slot, int button, SlotActionType actionType, int syncId, int revision) {}
44+
public boolean clickSlot(Slot slot, int button, SlotActionType actionType, int syncId, int revision) {
45+
return false;
46+
}
4547

4648
}

src/main/java/dev/dfonline/codeclient/CodeClient.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import dev.dfonline.codeclient.dev.*;
2020
import dev.dfonline.codeclient.dev.debug.Debug;
2121
import dev.dfonline.codeclient.dev.highlighter.ExpressionHighlighter;
22+
import dev.dfonline.codeclient.dev.menu.AdvancedMiddleClickFeature;
2223
import dev.dfonline.codeclient.dev.menu.InsertOverlayFeature;
2324
import dev.dfonline.codeclient.dev.menu.RecentValues;
2425
import dev.dfonline.codeclient.dev.menu.SlotGhostManager;
@@ -219,6 +220,7 @@ private static void loadFeatures() {
219220
feat(new MessageHiding());
220221
feat(new ExpressionHighlighter());
221222
feat(new PreviewSoundChest());
223+
feat(new AdvancedMiddleClickFeature());
222224
feat(new StateSwitcher.StateSwitcherFeature());
223225
feat(new SpeedSwitcher.SpeedSwitcherFeature());
224226
feat(new ScopeSwitcher.ScopeSwitcherFeature());
@@ -422,8 +424,8 @@ public static boolean onCharTyped(char chr, int modifiers) {
422424
return chestFeatures().anyMatch(feature -> feature.charTyped(chr, modifiers));
423425
}
424426

425-
public static void onClickSlot(Slot slot, int button, SlotActionType actionType, int syncId, int revision) {
426-
chestFeatures().forEach(feature -> feature.clickSlot(slot, button, actionType, syncId, revision));
427+
public static boolean onClickSlot(Slot slot, int button, SlotActionType actionType, int syncId, int revision) {
428+
return chestFeatures().anyMatch(feature -> feature.clickSlot(slot, button, actionType, syncId, revision));
427429
}
428430

429431
public static boolean onMouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {

src/main/java/dev/dfonline/codeclient/config/Config.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public class Config {
6161
public boolean SignPeeker = true;
6262
public CustomChestMenuType CustomCodeChest = CustomChestMenuType.OFF;
6363
public boolean PickAction = true;
64+
public boolean AdvancedMiddleClick = false;
6465
public boolean DevForBuild = false;
6566
public boolean ChatEditsVars = true;
6667
public boolean InsertOverlay = true;
@@ -150,6 +151,7 @@ public void save() {
150151
object.addProperty("SignPeeker", SignPeeker);
151152
object.addProperty("CustomCodeChest", CustomCodeChest.name());
152153
object.addProperty("PickAction", PickAction);
154+
object.addProperty("AdvancedMiddleClick", AdvancedMiddleClick);
153155
object.addProperty("DevForBuild", DevForBuild);
154156
object.addProperty("ChatEditsVars",ChatEditsVars);
155157
object.addProperty("InsertOverlay",InsertOverlay);
@@ -564,6 +566,16 @@ public YetAnotherConfigLib getLibConfig() {
564566
)
565567
.controller(TickBoxControllerBuilder::create)
566568
.build())
569+
.option(Option.createBuilder(Boolean.class)
570+
.name(Text.translatable("codeclient.config.advanced_middle_click"))
571+
.description(OptionDescription.of(Text.translatable("codeclient.config.advanced_middle_click.description"), Text.translatable("codeclient.config.advanced_middle_click.description2")))
572+
.binding(
573+
false,
574+
() -> AdvancedMiddleClick,
575+
opt -> AdvancedMiddleClick = opt
576+
)
577+
.controller(TickBoxControllerBuilder::create)
578+
.build())
567579
.option(Option.createBuilder(int.class)
568580
.name(Text.translatable("codeclient.config.recent_values"))
569581
.description(OptionDescription.of(Text.translatable("codeclient.config.recent_values.description")))

src/main/java/dev/dfonline/codeclient/dev/InteractionManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ private static void breakCodeBlock(BlockPos pos) {
112112

113113
public static boolean onClickSlot(Slot slot, int button, SlotActionType actionType, int syncId, int revision) {
114114
if (CodeClient.location instanceof Dev) {
115-
CodeClient.onClickSlot(slot,button,actionType,syncId,revision);
115+
if (CodeClient.onClickSlot(slot,button,actionType,syncId,revision)) return true;
116116
if (!slot.hasStack()) return false;
117117
ItemStack item = slot.getStack();
118118
DFItem dfItem = DFItem.of(item);
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package dev.dfonline.codeclient.dev.menu;
2+
3+
import dev.dfonline.codeclient.ChestFeature;
4+
import dev.dfonline.codeclient.Feature;
5+
import dev.dfonline.codeclient.Utility;
6+
import dev.dfonline.codeclient.config.Config;
7+
import net.minecraft.client.MinecraftClient;
8+
import net.minecraft.client.gui.screen.Screen;
9+
import net.minecraft.client.gui.screen.ingame.HandledScreen;
10+
import net.minecraft.client.network.ClientPlayerEntity;
11+
import net.minecraft.item.ItemStack;
12+
import net.minecraft.network.packet.c2s.play.CreativeInventoryActionC2SPacket;
13+
import net.minecraft.screen.ScreenHandler;
14+
import net.minecraft.screen.slot.Slot;
15+
import net.minecraft.screen.slot.SlotActionType;
16+
17+
import java.util.Optional;
18+
19+
public class AdvancedMiddleClickFeature extends Feature {
20+
21+
@Override
22+
public boolean enabled() {
23+
return activated();
24+
}
25+
26+
public static boolean activated() {
27+
return Config.getConfig().AdvancedMiddleClick;
28+
}
29+
30+
public ChestFeature makeChestFeature(HandledScreen<?> screen) {
31+
return new AdvancedMiddleClick(screen);
32+
}
33+
34+
public static ItemStack getCopy(Slot slot, ItemStack cursor) {
35+
int max = slot.getStack().getMaxCount();
36+
if (cursor.isEmpty() && slot.hasStack()) {
37+
int count = 1;
38+
if (Screen.hasShiftDown()) count = max;
39+
return slot.getStack().copyWithCount(count);
40+
} else if (!cursor.isEmpty() && compare(slot.getStack(), cursor)) {
41+
int count = Math.min(cursor.getCount()+1, max);
42+
if (Screen.hasShiftDown()) count = max;
43+
return slot.getStack().copyWithCount(count);
44+
}
45+
return null;
46+
}
47+
48+
private static boolean compare(ItemStack a, ItemStack b) {
49+
if (a.itemMatches(b.getRegistryEntry())) {
50+
if (a.getName().equals(b.getName())) {
51+
return a.getComponents().equals(b.getComponents());
52+
}
53+
}
54+
return false;
55+
}
56+
57+
private static class AdvancedMiddleClick extends ChestFeature {
58+
public AdvancedMiddleClick(HandledScreen<?> screen) {
59+
super(screen);
60+
}
61+
62+
@Override
63+
public boolean clickSlot(Slot slot, int button, SlotActionType actionType, int syncId, int revision) {
64+
ClientPlayerEntity player = MinecraftClient.getInstance().player;
65+
var manager = MinecraftClient.getInstance().interactionManager;
66+
var network = MinecraftClient.getInstance().getNetworkHandler();
67+
if (!(manager == null || network == null || player == null) && actionType == SlotActionType.CLONE) {
68+
ScreenHandler handler = player.currentScreenHandler;
69+
70+
// find an empty slot to use
71+
var emptySlotId = player.getInventory().getEmptySlot();
72+
int external = -1; // if the code had to use an external slot
73+
if (emptySlotId == -1) {
74+
// no empty slot, try to create an available slot temporarily.
75+
Optional<Slot> candidate = handler.slots.stream().filter(s -> !s.hasStack()).findFirst();
76+
if (candidate.isPresent()) {
77+
Slot selected = candidate.get();
78+
external = selected.id;
79+
manager.clickSlot(syncId, external, 0, SlotActionType.SWAP, player);
80+
emptySlotId = 0;
81+
} else {
82+
// not possible to run the following code, return to vanilla behaviour
83+
return false;
84+
}
85+
}
86+
87+
int convertedSlotId = Utility.getRemoteSlot(emptySlotId)-9+(handler.slots.size()-36);
88+
Slot emptySlot = handler.getSlot(convertedSlotId);
89+
var stack = getCopy(slot, handler.getCursorStack()); // get the stack to set the slot to
90+
if (stack == null) return false;
91+
92+
if (!handler.getCursorStack().isEmpty()) {
93+
manager.clickSlot(syncId, convertedSlotId, 0, SlotActionType.PICKUP, player);
94+
emptySlot.getStack().setCount(0); // hides the temporary item in the empty slot.
95+
}
96+
97+
// create the item for the server
98+
network.sendPacket(new CreativeInventoryActionC2SPacket(
99+
Utility.getRemoteSlot(emptySlotId), stack)
100+
);
101+
102+
manager.clickSlot(syncId, convertedSlotId, 0, SlotActionType.PICKUP, player);
103+
104+
if (external != -1) {
105+
manager.clickSlot(syncId, external, 0, SlotActionType.SWAP, player);
106+
} else {
107+
emptySlot.getStack().setCount(0); // hides the temporary item in the empty slot.
108+
}
109+
110+
handler.setCursorStack(stack); // shows the item in your cursor before the server agrees.
111+
112+
113+
return true;
114+
}
115+
116+
return false;
117+
}
118+
119+
120+
}
121+
}

src/main/java/dev/dfonline/codeclient/dev/menu/InsertOverlayFeature.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,12 @@ public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmou
104104
}
105105

106106
@Override
107-
public void clickSlot(Slot slot, int button, SlotActionType actionType, int syncId, int revision) {
108-
if(CodeClient.MC.player != null && slot.inventory == CodeClient.MC.player.getInventory()) return;
107+
public boolean clickSlot(Slot slot, int button, SlotActionType actionType, int syncId, int revision) {
108+
if(CodeClient.MC.player != null && slot.inventory == CodeClient.MC.player.getInventory()) return false;
109109
if (actionType == SlotActionType.PICKUP && !slot.hasStack() && CodeClient.MC.player.currentScreenHandler.getCursorStack().isEmpty())
110110
selectedSlot = new AddWidget(slot, () -> selectedSlot = null);
111111
else if (selectedSlot != null) selectedSlot.close();
112+
return false;
112113
}
113114

114115
private class AddWidget {

src/main/java/dev/dfonline/codeclient/mixin/screen/MCreativeInventoryScreen.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ public abstract class MCreativeInventoryScreen {
2222

2323
@Inject(method = "onMouseClick", at = @At("HEAD"), cancellable = true)
2424
public void slotClicked(@Nullable Slot slot, int slotId, int button, SlotActionType actionType, CallbackInfo ci) {
25-
if (CodeClient.location instanceof Dev dev
25+
if (
26+
CodeClient.location instanceof Dev dev
2627
&& dev.isInDevSpace() // Clear the inventory regardless of mode if not in dev space
2728
&& actionType == SlotActionType.QUICK_MOVE
28-
&& slot == this.deleteItemSlot) {
29-
29+
&& slot == this.deleteItemSlot
30+
) {
3031
String cmd = Config.getConfig().DestroyItemResetMode.command;
3132

3233
if (cmd != null) {

src/main/java/dev/dfonline/codeclient/mixin/screen/MHandledScreen.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,4 @@ private void clickSlot(Slot slot, int slotId, int button, SlotActionType actionT
6363
if (InteractionManager.onClickSlot(slot,button,actionType,this.handler.syncId,this.handler.getRevision()))
6464
ci.cancel();
6565
}
66-
6766
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package dev.dfonline.codeclient.mixin.screen;
2+
3+
import dev.dfonline.codeclient.CodeClient;
4+
import dev.dfonline.codeclient.dev.menu.AdvancedMiddleClickFeature;
5+
import dev.dfonline.codeclient.location.Dev;
6+
import net.minecraft.entity.player.PlayerEntity;
7+
import net.minecraft.item.ItemStack;
8+
import net.minecraft.screen.ScreenHandler;
9+
import net.minecraft.screen.slot.Slot;
10+
import net.minecraft.screen.slot.SlotActionType;
11+
import net.minecraft.util.collection.DefaultedList;
12+
import org.spongepowered.asm.mixin.Final;
13+
import org.spongepowered.asm.mixin.Mixin;
14+
import org.spongepowered.asm.mixin.Shadow;
15+
import org.spongepowered.asm.mixin.injection.At;
16+
import org.spongepowered.asm.mixin.injection.Inject;
17+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
18+
19+
@Mixin(ScreenHandler.class)
20+
public abstract class MScreenHandler {
21+
22+
@Shadow public abstract ItemStack getCursorStack();
23+
24+
@Shadow @Final public DefaultedList<Slot> slots;
25+
26+
@Shadow public abstract void setCursorStack(ItemStack stack);
27+
28+
@Inject(
29+
method = "onSlotClick",
30+
at = @At("HEAD"),
31+
cancellable = true
32+
)
33+
public void clickSlot(int slotIndex, int button, SlotActionType actionType, PlayerEntity player, CallbackInfo ci) {
34+
// creative inventories act differently, and don't keep track of the item in the cursor, and will trust the client.
35+
if (CodeClient.location instanceof Dev && actionType == SlotActionType.CLONE) {
36+
if (!AdvancedMiddleClickFeature.activated()) return;
37+
if (player.isInCreativeMode()) {
38+
var slot = (Slot) slots.get(slotIndex);
39+
var clone = AdvancedMiddleClickFeature.getCopy(slot, getCursorStack());
40+
if (clone != null) {
41+
setCursorStack(clone);
42+
}
43+
ci.cancel();
44+
}
45+
}
46+
}
47+
}

src/main/resources/CodeClient.mixins.json

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
"package": "dev.dfonline.codeclient.mixin",
55
"compatibilityLevel": "JAVA_21",
66
"client": [
7-
"screen.chat.MChatScreen",
8-
"screen.chat.MChatInputSuggester",
97
"MKeyboard",
108
"MMouse",
119
"VersionChecker",
@@ -24,10 +22,12 @@
2422
"render.hud.MInGameHud",
2523
"render.hud.MInGameOverlayRenderer",
2624
"render.hud.MTitleScreen",
27-
"screen.MMultiplayerScreen",
25+
"screen.MCreativeInventoryScreen",
2826
"screen.MHandledScreen",
2927
"screen.MHandledScreens",
30-
"screen.MCreativeInventoryScreen",
28+
"screen.MMultiplayerScreen",
29+
"screen.chat.MChatInputSuggester",
30+
"screen.chat.MChatScreen",
3131
"world.MWorldRenderer",
3232
"world.block.MAbstractBlockState",
3333
"world.block.MBarrierBlock",
@@ -39,5 +39,8 @@
3939
],
4040
"injectors": {
4141
"defaultRequire": 1
42-
}
42+
},
43+
"mixins": [
44+
"screen.MScreenHandler"
45+
]
4346
}

0 commit comments

Comments
 (0)