Skip to content

Commit d1ca3b1

Browse files
committed
Added RemoteEnderChest
1 parent 5a5b617 commit d1ca3b1

4 files changed

Lines changed: 341 additions & 0 deletions

File tree

src/main/java/net/wurstclient/hack/HackList.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ public final class HackList implements UpdateListener
260260
public final InfiniteReachHack infiniteReachHack = new InfiniteReachHack();
261261
public final ReachHack reachHack = new ReachHack();
262262
public final RemoteViewHack remoteViewHack = new RemoteViewHack();
263+
public final RemoteEnderChestHack remoteEnderChestHack =
264+
new RemoteEnderChestHack();
263265
public final RestockHack restockHack = new RestockHack();
264266
public final SafeTpHack safeTpHack = new SafeTpHack();
265267
public final SafeWalkHack safeWalkHack = new SafeWalkHack();
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
/*
2+
* Copyright (c) 2014-2026 Wurst-Imperium and contributors.
3+
*
4+
* This source code is subject to the terms of the GNU General Public
5+
* License, version 3. If a copy of the GPL was not distributed with this
6+
* file, You can obtain one at: https://www.gnu.org/licenses/gpl-3.0.txt
7+
*/
8+
package net.wurstclient.hacks;
9+
10+
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
11+
import net.minecraft.core.BlockPos;
12+
import net.minecraft.network.protocol.game.ServerboundContainerClosePacket;
13+
import net.minecraft.world.inventory.MenuType;
14+
import net.minecraft.world.level.Level;
15+
import net.minecraft.world.level.block.Blocks;
16+
import net.minecraft.world.phys.BlockHitResult;
17+
import net.wurstclient.Category;
18+
import net.wurstclient.SearchTags;
19+
import net.wurstclient.events.UpdateListener;
20+
import net.wurstclient.hack.Hack;
21+
import net.wurstclient.util.ChatUtils;
22+
23+
@SearchTags({"remote echest", "remote ender chest", "ender chest",
24+
"portable echest", "portable ender chest"})
25+
public final class RemoteEnderChestHack extends Hack implements UpdateListener
26+
{
27+
private static RemoteEnderChestHack instance;
28+
29+
private boolean guiHidden;
30+
private boolean guiWasOpen;
31+
private boolean lastKeyState;
32+
private AbstractContainerScreen<?> savedScreen;
33+
private int savedSyncId = -1;
34+
private Level lastWorld;
35+
private BlockPos potentialEChestPos;
36+
37+
public RemoteEnderChestHack()
38+
{
39+
super("RemoteEChest");
40+
instance = this;
41+
setCategory(Category.OTHER);
42+
}
43+
44+
// ---- Mixin API (X button, E/ESC/Alt interception) ----
45+
46+
public static boolean isLinkedScreen(AbstractContainerScreen<?> screen)
47+
{
48+
RemoteEnderChestHack self = instance;
49+
if(self == null || !self.isEnabled())
50+
return false;
51+
52+
return self.savedScreen == screen
53+
&& self.isSavedContainerMenuStillActive();
54+
}
55+
56+
public static void hideLinkedGui()
57+
{
58+
RemoteEnderChestHack self = instance;
59+
if(self == null || !self.isEnabled() || self.savedScreen == null)
60+
return;
61+
62+
if(!self.isSavedContainerMenuStillActive())
63+
{
64+
self.breakLink("Ender chest handler invalid. EChest link broken.");
65+
return;
66+
}
67+
68+
if(!self.guiHidden)
69+
{
70+
self.MC.setScreen(null);
71+
self.guiHidden = true;
72+
}
73+
}
74+
75+
public static void toggleLinkedGui()
76+
{
77+
RemoteEnderChestHack self = instance;
78+
if(self == null || !self.isEnabled() || self.savedScreen == null)
79+
return;
80+
81+
// Prevent double-toggle when both a mixin and onUpdate() see Left Alt.
82+
self.lastKeyState = true;
83+
84+
if(!self.isSavedContainerMenuStillActive())
85+
{
86+
self.breakLink("Ender chest handler invalid. EChest link broken.");
87+
return;
88+
}
89+
90+
if(self.guiHidden)
91+
{
92+
self.MC.setScreen(self.savedScreen);
93+
self.guiHidden = false;
94+
}else
95+
{
96+
self.MC.setScreen(null);
97+
self.guiHidden = true;
98+
}
99+
}
100+
101+
// Use this from packet/screen mixins to detect the saved container.
102+
public static boolean shouldKeepContainerOpen(int syncId)
103+
{
104+
RemoteEnderChestHack self = instance;
105+
if(self == null || !self.isEnabled())
106+
return false;
107+
108+
return self.savedScreen != null && self.savedSyncId == syncId
109+
&& self.isSavedContainerMenuStillActive();
110+
}
111+
112+
// ---------------------------------------------------------------
113+
114+
@Override
115+
protected void onEnable()
116+
{
117+
EVENTS.add(UpdateListener.class, this);
118+
resetStuff();
119+
120+
if(MC.player == null || MC.level == null)
121+
{
122+
setEnabled(false);
123+
return;
124+
}
125+
126+
lastWorld = MC.level;
127+
}
128+
129+
@Override
130+
protected void onDisable()
131+
{
132+
EVENTS.remove(UpdateListener.class, this);
133+
resetStuff();
134+
}
135+
136+
@Override
137+
public void onUpdate()
138+
{
139+
if(MC.player == null || MC.level == null)
140+
{
141+
resetStuff(false);
142+
return;
143+
}
144+
145+
if(MC.hitResult instanceof BlockHitResult bhr)
146+
{
147+
potentialEChestPos = bhr.getBlockPos();
148+
149+
if(MC.screen == null
150+
&& MC.level.getBlockState(bhr.getBlockPos())
151+
.getBlock() == Blocks.ENDER_CHEST
152+
&& MC.options.keyUse.isDown()
153+
&& !isEnderChestScreen(potentialEChestPos) && !guiHidden)
154+
{
155+
MC.startUseItem();
156+
MC.startUseItem();
157+
}
158+
}
159+
160+
// Opening another container, chest, player inventory, etc. invalidates
161+
// the saved link. Clear it immediately so Alt cannot reopen a stale
162+
// ghost GUI later.
163+
if(savedScreen != null
164+
&& MC.screen instanceof AbstractContainerScreen<?> screen
165+
&& screen != savedScreen)
166+
{
167+
breakLink(
168+
"Opened another inventory/container. EChest link broken.");
169+
}
170+
171+
// If the server/client has already returned to the player inventory
172+
// menu,
173+
// the saved screen is stale. This prevents ghost clicks like:
174+
// "Ignoring click in mismatching container. Click in 4, player has 0."
175+
if(savedScreen != null && !isSavedContainerMenuStillActive())
176+
{
177+
breakLink("Ender chest handler invalid. EChest link broken.");
178+
return;
179+
}
180+
181+
if(isEnderChestScreen(potentialEChestPos) && savedScreen == null
182+
&& !guiWasOpen && !guiHidden)
183+
{
184+
savedScreen = (AbstractContainerScreen<?>)MC.screen;
185+
186+
savedSyncId = savedScreen.getMenu().containerId;
187+
188+
MC.setScreen(null);
189+
guiHidden = true;
190+
guiWasOpen = true;
191+
lastWorld = MC.level;
192+
ChatUtils.message(
193+
"EChest link created! Press Left Alt to toggle the GUI.");
194+
}
195+
196+
long window = MC.getWindow().handle();
197+
boolean keyDown = org.lwjgl.glfw.GLFW.glfwGetKey(window,
198+
org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_ALT) == 1;
199+
boolean keyJustPressed = keyDown && !lastKeyState;
200+
lastKeyState = keyDown;
201+
202+
if(keyJustPressed && savedScreen != null)
203+
{
204+
205+
if(!isSavedContainerMenuStillActive())
206+
{
207+
breakLink("Ender chest handler invalid. EChest link broken.");
208+
return;
209+
}
210+
211+
if(guiHidden)
212+
{
213+
MC.setScreen(savedScreen);
214+
guiHidden = false;
215+
}else
216+
{
217+
MC.setScreen(null);
218+
guiHidden = true;
219+
}
220+
}
221+
222+
if(savedScreen != null && MC.screen == null && !guiHidden && guiWasOpen)
223+
{
224+
225+
// ESC/X will hide the GUI.
226+
guiHidden = true;
227+
return;
228+
}
229+
230+
if(savedScreen != null && MC.level != lastWorld)
231+
{
232+
breakLink("World changed. EChest link broken.");
233+
lastWorld = MC.level;
234+
return;
235+
}
236+
237+
lastWorld = MC.level;
238+
}
239+
240+
private boolean isEnderChestScreen(BlockPos echest)
241+
{
242+
if(!(MC.screen instanceof AbstractContainerScreen<?> screen))
243+
return false;
244+
if(echest == null || MC.level == null)
245+
return false;
246+
if(MC.level.getBlockState(echest).getBlock() != Blocks.ENDER_CHEST)
247+
return false;
248+
try
249+
{
250+
return screen.getMenu().getType() == MenuType.GENERIC_9x3;
251+
}catch(UnsupportedOperationException e)
252+
{
253+
return false;
254+
}
255+
}
256+
257+
private boolean isSavedContainerMenuStillActive()
258+
{
259+
if(MC.player == null || MC.player.containerMenu == null)
260+
return false;
261+
if(savedScreen == null || savedSyncId == -1)
262+
return false;
263+
264+
return MC.player.containerMenu.containerId == savedSyncId;
265+
}
266+
267+
private void breakLink(String message)
268+
{
269+
resetStuff(false);
270+
ChatUtils.error(message);
271+
}
272+
273+
private void resetStuff()
274+
{
275+
resetStuff(true);
276+
}
277+
278+
private void resetStuff(boolean sendClosePacket)
279+
{
280+
int syncId = savedSyncId;
281+
AbstractContainerScreen<?> oldScreen = savedScreen;
282+
boolean oldScreenVisible = MC.screen == oldScreen;
283+
284+
guiHidden = false;
285+
guiWasOpen = false;
286+
lastKeyState = false;
287+
potentialEChestPos = null;
288+
savedSyncId = -1;
289+
savedScreen = null;
290+
291+
if(oldScreenVisible)
292+
MC.setScreen(null);
293+
294+
if(sendClosePacket && !oldScreenVisible && syncId != -1
295+
&& MC.getConnection() != null)
296+
{
297+
MC.getConnection()
298+
.send(new ServerboundContainerClosePacket(syncId));
299+
}
300+
}
301+
}

src/main/java/net/wurstclient/mixin/ContainerScreenMixin.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
1717
import net.minecraft.client.gui.GuiGraphicsExtractor;
1818
import net.minecraft.client.gui.components.Button;
19+
import net.minecraft.client.input.KeyEvent;
1920
import net.minecraft.ChatFormatting;
2021
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
2122
import net.minecraft.client.gui.screens.inventory.ContainerScreen;
@@ -45,7 +46,9 @@
4546
import net.wurstclient.hacks.AutoStealHack;
4647
import net.wurstclient.hacks.ChestSearchHack;
4748
import net.wurstclient.hacks.QuickShulkerHack;
49+
import net.wurstclient.hacks.RemoteEnderChestHack;
4850
import net.wurstclient.util.ChatUtils;
51+
import org.lwjgl.glfw.GLFW;
4952

5053
@Mixin(ContainerScreen.class)
5154
public abstract class ContainerScreenMixin
@@ -101,6 +104,27 @@ public ContainerScreenMixin(WurstClient wurst, ChestMenu container,
101104
super(container, playerInventory, name);
102105
}
103106

107+
@Override
108+
public boolean keyPressed(KeyEvent event)
109+
{
110+
if(RemoteEnderChestHack
111+
.isLinkedScreen((AbstractContainerScreen<?>)(Object)this))
112+
{
113+
int key = event.key();
114+
if(key == GLFW.GLFW_KEY_ESCAPE || key == GLFW.GLFW_KEY_E)
115+
{
116+
RemoteEnderChestHack.hideLinkedGui();
117+
return true;
118+
}
119+
if(key == GLFW.GLFW_KEY_LEFT_ALT)
120+
{
121+
RemoteEnderChestHack.toggleLinkedGui();
122+
return true;
123+
}
124+
}
125+
return super.keyPressed(event);
126+
}
127+
104128
@Override
105129
public void init()
106130
{
@@ -190,6 +214,19 @@ public void init()
190214
addRenderableWidget(quickButton);
191215
}
192216

217+
// RemoteEChest close button — on the title line, right-aligned
218+
// next to the container name
219+
RemoteEnderChestHack remoteEChest =
220+
WurstClient.INSTANCE.getHax().remoteEnderChestHack;
221+
if(remoteEChest != null && RemoteEnderChestHack
222+
.isLinkedScreen((AbstractContainerScreen<?>)(Object)this))
223+
{
224+
addRenderableWidget(Button
225+
.builder(Component.literal("\u2715"),
226+
b -> RemoteEnderChestHack.hideLinkedGui())
227+
.bounds(leftPos + imageWidth - 17, topPos + 2, 14, 14).build());
228+
}
229+
193230
// Add a manual scan button to help on plugin-protected servers when
194231
// chest search is in manual mode. The toggle is provided in the
195232
// ChestSearch hack settings.

src/main/resources/assets/wurst/translations/en_us.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@
318318
"description.wurst.setting.remoteview.filter_allays": "Won't view allays.",
319319
"description.wurst.setting.remoteview.filter_invisible": "Won't view invisible entities.",
320320
"description.wurst.setting.remoteview.filter_armor_stands": "Won't view armor stands.",
321+
"description.wurst.hack.remoteechest": "Access your ender chest anywhere and move freely while it is open. Originally by etianl.",
321322
"description.wurst.hack.restock": "Automatically restocks your hand with the selected items from your inventory. Works better with FastPlace.",
322323
"description.wurst.hack.roofesp": "Scans the Nether roof (Y=128 and above) for player-placed blocks, structures, and dropped items, then highlights them with ESP/tracers.",
323324
"description.wurst.hack.safetp": "Activates Blink for five seconds and sends your teleport command move and attack while the teleport countdown is running",

0 commit comments

Comments
 (0)