Skip to content

Commit 63a5e87

Browse files
committed
ProjectileESP
2 parents 0fe60fb + baae851 commit 63a5e87

4 files changed

Lines changed: 395 additions & 1 deletion

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ public final class HackList implements UpdateListener
250250
public final PotEspHack potEspHack = new PotEspHack();
251251
public final PortalGuiHack portalGuiHack = new PortalGuiHack();
252252
public final PotionSaverHack potionSaverHack = new PotionSaverHack();
253+
public final ProjectileEspHack projectileEspHack = new ProjectileEspHack();
253254
public final ProphuntEspHack prophuntEspHack = new ProphuntEspHack();
254255
public final ProtectHack protectHack = new ProtectHack();
255256
public final QuickShulkerHack quickShulkerHack = new QuickShulkerHack();
Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
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 com.mojang.blaze3d.vertex.PoseStack;
11+
import com.mojang.math.Axis;
12+
import java.awt.Color;
13+
import java.util.ArrayList;
14+
import java.util.LinkedHashMap;
15+
import java.util.List;
16+
import java.util.Map;
17+
import net.minecraft.client.gui.Font;
18+
import net.minecraft.client.renderer.MultiBufferSource;
19+
import net.minecraft.core.registries.BuiltInRegistries;
20+
import net.minecraft.world.entity.Entity;
21+
import net.minecraft.world.entity.player.Player;
22+
import net.minecraft.world.entity.projectile.Projectile;
23+
import net.minecraft.world.entity.projectile.arrow.AbstractArrow;
24+
import net.minecraft.world.phys.AABB;
25+
import net.minecraft.world.phys.BlockHitResult;
26+
import net.minecraft.world.phys.HitResult;
27+
import net.minecraft.world.phys.Vec3;
28+
import net.wurstclient.Category;
29+
import net.wurstclient.SearchTags;
30+
import net.wurstclient.events.CameraTransformViewBobbingListener;
31+
import net.wurstclient.events.RenderListener;
32+
import net.wurstclient.events.UpdateListener;
33+
import net.wurstclient.hack.Hack;
34+
import net.wurstclient.settings.CheckboxSetting;
35+
import net.wurstclient.settings.ColorSetting;
36+
import net.wurstclient.settings.EnumSetting;
37+
import net.wurstclient.settings.EspBoxSizeSetting;
38+
import net.wurstclient.settings.EspStyleSetting;
39+
import net.wurstclient.settings.Setting;
40+
import net.wurstclient.settings.SettingGroup;
41+
import net.wurstclient.settings.SliderSetting;
42+
import net.wurstclient.settings.SliderSetting.ValueDisplay;
43+
import net.wurstclient.util.BlockUtils;
44+
import net.wurstclient.util.EntityUtils;
45+
import net.wurstclient.util.RenderUtils;
46+
import net.wurstclient.util.text.WText;
47+
48+
@SearchTags({"projectile esp", "ProjectileESP", "snowball esp", "egg esp",
49+
"projectile tracers", "ProjectileTracers"})
50+
public final class ProjectileEspHack extends Hack implements UpdateListener,
51+
CameraTransformViewBobbingListener, RenderListener
52+
{
53+
private final EspStyleSetting style = new EspStyleSetting();
54+
private final EspBoxSizeSetting boxSize =
55+
new EspBoxSizeSetting("§lAccurate§r mode shows the exact hitbox.\n"
56+
+ "§lFancy§r mode shows larger boxes that look better.");
57+
58+
private final CheckboxSetting includeArrows = new CheckboxSetting(
59+
"Include arrows",
60+
"Also highlight arrows and tridents. Turn off to cut the clutter and"
61+
+ " only show thrown items like snowballs, eggs and pearls.",
62+
true);
63+
64+
private final EnumSetting<ColorMode> colorMode = new EnumSetting<>("Colors",
65+
"How projectiles are colored.\n\n"
66+
+ "§lFixed§r - one color for everything.\n"
67+
+ "§lRainbow§r - cycling rainbow.\n"
68+
+ "§lBy owner§r - who threw it (you / player / mob).\n"
69+
+ "§lBy type§r - a separate color per projectile type, set in the"
70+
+ " §lType colors§r menu.",
71+
ColorMode.values(), ColorMode.BY_TYPE);
72+
73+
private final ColorSetting fixedColor = new ColorSetting("Fixed color",
74+
"Color used in §lFixed§r mode.", Color.CYAN);
75+
76+
private final ColorSetting selfColor = new ColorSetting("Your projectiles",
77+
"§lBy owner§r: projectiles you threw.", new Color(0x55FF55));
78+
private final ColorSetting otherPlayerColor = new ColorSetting(
79+
"Other players", "§lBy owner§r: projectiles from other players.",
80+
new Color(0xFF5555));
81+
private final ColorSetting mobColor = new ColorSetting("Mobs",
82+
"§lBy owner§r: projectiles from mobs or with no known owner.",
83+
new Color(0xFFFF55));
84+
private final SettingGroup ownerColors = new SettingGroup("Owner colors",
85+
WText.literal("Colors used by the §lBy owner§r mode."), false, true)
86+
.addChildren(selfColor, otherPlayerColor, mobColor);
87+
88+
private final ColorSetting snowballColor = new ColorSetting("Snowball",
89+
"Color for snowballs.", new Color(0xFFFFFF));
90+
private final ColorSetting eggColor =
91+
new ColorSetting("Egg", "Color for eggs.", new Color(0xF2D9A0));
92+
private final ColorSetting pearlColor = new ColorSetting("Ender pearl",
93+
"Color for ender pearls.", new Color(0xA000E0));
94+
private final ColorSetting potionColor = new ColorSetting("Potion",
95+
"Color for splash and lingering potions.", new Color(0xFF55FF));
96+
private final ColorSetting xpBottleColor = new ColorSetting("XP bottle",
97+
"Color for bottles o' enchanting.", new Color(0x77FF33));
98+
private final ColorSetting arrowColor =
99+
new ColorSetting("Arrow", "Color for arrows.", new Color(0xCCCCCC));
100+
private final ColorSetting tridentColor = new ColorSetting("Trident",
101+
"Color for thrown tridents.", new Color(0x33FFFF));
102+
private final ColorSetting fireballColor = new ColorSetting("Fireball",
103+
"Color for fireballs, wind charges, wither skulls, etc.",
104+
new Color(0xFF6000));
105+
private final ColorSetting fireworkColor = new ColorSetting("Firework",
106+
"Color for firework rockets.", new Color(0xFFC000));
107+
private final ColorSetting otherColor = new ColorSetting("Other",
108+
"Color for any other projectile.", new Color(0x999999));
109+
private final SettingGroup typeColors = new SettingGroup("Type colors",
110+
WText.literal("A separate color per projectile type, used by the"
111+
+ " §lBy type§r mode."),
112+
false, true).addChildren(snowballColor, eggColor, pearlColor,
113+
potionColor, xpBottleColor, arrowColor, tridentColor, fireballColor,
114+
fireworkColor, otherColor);
115+
116+
private final CheckboxSetting showNames = new CheckboxSetting("Show names",
117+
"Draw a name tag above each projectile showing what it is.", false);
118+
private final SliderSetting nameScale =
119+
new SliderSetting("Name scale", "Size of the projectile name tags.", 1,
120+
0.5, 2, 0.05, ValueDisplay.PERCENTAGE);
121+
122+
private final CheckboxSetting trajectory = new CheckboxSetting("Trajectory",
123+
"Draw the predicted flight path of each in-flight projectile, in its"
124+
+ " color.",
125+
false);
126+
private final SliderSetting trajectoryThickness = new SliderSetting(
127+
"Trajectory thickness", 2, 0.5, 8, 0.1, ValueDisplay.DECIMAL);
128+
private final CheckboxSetting landingBox =
129+
new CheckboxSetting("Landing box",
130+
"Mark where each projectile is predicted to land/hit.", true);
131+
132+
private final CheckboxSetting tracerFlash = new CheckboxSetting(
133+
"Tracer flash", "Make tracers pulse with a smooth fade.", false);
134+
135+
private final ArrayList<Projectile> projectiles = new ArrayList<>();
136+
137+
public ProjectileEspHack()
138+
{
139+
super("ProjectileESP");
140+
setCategory(Category.RENDER);
141+
for(Setting setting : new Setting[]{style, boxSize, includeArrows,
142+
colorMode, fixedColor, selfColor, otherPlayerColor, mobColor,
143+
ownerColors, snowballColor, eggColor, pearlColor, potionColor,
144+
xpBottleColor, arrowColor, tridentColor, fireballColor,
145+
fireworkColor, otherColor, typeColors, showNames, nameScale,
146+
trajectory, trajectoryThickness, landingBox, tracerFlash})
147+
addSetting(setting);
148+
}
149+
150+
@Override
151+
protected void onEnable()
152+
{
153+
EVENTS.add(UpdateListener.class, this);
154+
EVENTS.add(CameraTransformViewBobbingListener.class, this);
155+
EVENTS.add(RenderListener.class, this);
156+
}
157+
158+
@Override
159+
protected void onDisable()
160+
{
161+
EVENTS.remove(UpdateListener.class, this);
162+
EVENTS.remove(CameraTransformViewBobbingListener.class, this);
163+
EVENTS.remove(RenderListener.class, this);
164+
projectiles.clear();
165+
}
166+
167+
@Override
168+
public void onUpdate()
169+
{
170+
projectiles.clear();
171+
if(MC.level == null)
172+
return;
173+
174+
for(Entity e : MC.level.entitiesForRendering())
175+
{
176+
if(!(e instanceof Projectile projectile))
177+
continue;
178+
if(!includeArrows.isChecked() && e instanceof AbstractArrow)
179+
continue;
180+
projectiles.add(projectile);
181+
}
182+
}
183+
184+
@Override
185+
public void onCameraTransformViewBobbing(
186+
CameraTransformViewBobbingEvent event)
187+
{
188+
if(style.hasLines() || trajectory.isChecked())
189+
event.cancel();
190+
}
191+
192+
@Override
193+
public void onRender(PoseStack matrixStack, float partialTicks)
194+
{
195+
if(projectiles.isEmpty())
196+
return;
197+
boolean boxes = style.hasBoxes();
198+
boolean lines = style.hasLines();
199+
boolean names = showNames.isChecked();
200+
boolean paths = trajectory.isChecked();
201+
if(!boxes && !lines && !names && !paths)
202+
return;
203+
204+
Map<Integer, List<AABB>> boxesByColor = new LinkedHashMap<>();
205+
Map<Integer, List<Vec3>> endsByColor = new LinkedHashMap<>();
206+
Map<Integer, List<AABB>> landingByColor = new LinkedHashMap<>();
207+
208+
for(Projectile p : projectiles)
209+
{
210+
int color = colorFor(p);
211+
AABB box =
212+
applyExtraSize(EntityUtils.getLerpedBox(p, partialTicks));
213+
214+
if(boxes)
215+
boxesByColor.computeIfAbsent(color, k -> new ArrayList<>())
216+
.add(box);
217+
if(lines)
218+
endsByColor.computeIfAbsent(color, k -> new ArrayList<>())
219+
.add(box.getCenter());
220+
if(names)
221+
drawNameTag(matrixStack,
222+
p.getType().getDescription().getString(), box.getCenter().x,
223+
box.maxY + 0.3, box.getCenter().z, color);
224+
225+
if(paths)
226+
{
227+
List<Vec3> path = buildPath(p);
228+
if(path.size() >= 2)
229+
{
230+
RenderUtils.drawCurvedLine(matrixStack, path, color, false,
231+
trajectoryThickness.getValue());
232+
if(landingBox.isChecked())
233+
{
234+
Vec3 hit = path.get(path.size() - 1);
235+
landingByColor
236+
.computeIfAbsent(color, k -> new ArrayList<>())
237+
.add(new AABB(hit.x - 0.25, hit.y - 0.25,
238+
hit.z - 0.25, hit.x + 0.25, hit.y + 0.25,
239+
hit.z + 0.25));
240+
}
241+
}
242+
}
243+
}
244+
245+
for(Map.Entry<Integer, List<AABB>> e : boxesByColor.entrySet())
246+
RenderUtils.drawOutlinedBoxes(matrixStack, e.getValue(), e.getKey(),
247+
false);
248+
for(Map.Entry<Integer, List<Vec3>> e : endsByColor.entrySet())
249+
RenderUtils.drawTracers(matrixStack, partialTicks, e.getValue(),
250+
e.getKey(), false);
251+
for(Map.Entry<Integer, List<AABB>> e : landingByColor.entrySet())
252+
{
253+
int c = e.getKey();
254+
RenderUtils.drawSolidBoxes(matrixStack, e.getValue(),
255+
c & 0x00FFFFFF | 0x40000000, false);
256+
RenderUtils.drawOutlinedBoxes(matrixStack, e.getValue(), c, false);
257+
}
258+
}
259+
260+
private List<Vec3> buildPath(Projectile p)
261+
{
262+
ArrayList<Vec3> path = new ArrayList<>();
263+
Vec3 pos = p.position();
264+
Vec3 vel = p.getDeltaMovement();
265+
path.add(pos);
266+
if(MC.level == null)
267+
return path;
268+
269+
double gravity =
270+
p.isNoGravity() ? 0.0 : p instanceof AbstractArrow ? 0.05 : 0.03;
271+
double drag = p.isInWater() ? 0.8 : 0.99;
272+
273+
for(int i = 0; i < 200; i++)
274+
{
275+
Vec3 next = pos.add(vel);
276+
BlockHitResult hit = BlockUtils.raycast(pos, next);
277+
if(hit.getType() != HitResult.Type.MISS)
278+
{
279+
path.add(hit.getLocation());
280+
break;
281+
}
282+
283+
path.add(next);
284+
pos = next;
285+
vel = vel.scale(drag).add(0, -gravity, 0);
286+
287+
if(pos.y < MC.level.getMinY() - 4 || vel.lengthSqr() < 1e-4)
288+
break;
289+
}
290+
291+
return path;
292+
}
293+
294+
private int colorFor(Projectile p)
295+
{
296+
int color = switch(colorMode.getSelected())
297+
{
298+
case FIXED -> fixedColor.getColorI(0x80);
299+
case RAINBOW -> RenderUtils
300+
.toIntColor(RenderUtils.getRainbowColor(), 0.5F);
301+
case BY_OWNER -> ownerColor(p);
302+
case BY_TYPE -> typeColorSetting(p).getColorI(0x80);
303+
};
304+
return tracerFlash.isChecked() ? RenderUtils.flashColor(color) : color;
305+
}
306+
307+
private int ownerColor(Projectile p)
308+
{
309+
Entity owner = p.getOwner();
310+
if(owner == MC.player)
311+
return selfColor.getColorI(0x80);
312+
if(owner instanceof Player)
313+
return otherPlayerColor.getColorI(0x80);
314+
return mobColor.getColorI(0x80);
315+
}
316+
317+
private ColorSetting typeColorSetting(Projectile p)
318+
{
319+
String id = BuiltInRegistries.ENTITY_TYPE.getKey(p.getType()).getPath();
320+
return switch(id)
321+
{
322+
case "snowball" -> snowballColor;
323+
case "egg" -> eggColor;
324+
case "ender_pearl" -> pearlColor;
325+
case "splash_potion", "lingering_potion", "potion" -> potionColor;
326+
case "experience_bottle" -> xpBottleColor;
327+
case "arrow", "spectral_arrow" -> arrowColor;
328+
case "trident" -> tridentColor;
329+
case "fireball", "small_fireball", "large_fireball", "dragon_fireball", "wither_skull", "wind_charge", "breeze_wind_charge" -> fireballColor;
330+
case "firework_rocket" -> fireworkColor;
331+
default -> otherColor;
332+
};
333+
}
334+
335+
private AABB applyExtraSize(AABB box)
336+
{
337+
double extra = boxSize.getExtraSize() / 2.0;
338+
return box.move(0, extra, 0).inflate(extra);
339+
}
340+
341+
private void drawNameTag(PoseStack matrices, String text, double x,
342+
double y, double z, int argb)
343+
{
344+
matrices.pushPose();
345+
Vec3 cam = RenderUtils.getCameraPos();
346+
matrices.translate(x - cam.x, y - cam.y, z - cam.z);
347+
348+
Entity camEntity = MC.getCameraEntity();
349+
if(camEntity != null)
350+
{
351+
matrices.mulPose(Axis.YP.rotationDegrees(-camEntity.getYRot()));
352+
matrices.mulPose(Axis.XP.rotationDegrees(camEntity.getXRot()));
353+
}
354+
355+
matrices.mulPose(Axis.YP.rotationDegrees(180.0F));
356+
float s = 0.025F * nameScale.getValueF();
357+
matrices.scale(s, -s, s);
358+
359+
Font font = MC.font;
360+
MultiBufferSource.BufferSource vcp = RenderUtils.getVCP();
361+
float w = font.width(text) / 2F;
362+
int bgAlpha = (int)(MC.options.getBackgroundOpacity(0.25F) * 255) << 24;
363+
var matrix = matrices.last().pose();
364+
font.drawInBatch(text, -w, 0, argb | 0xFF000000, false, matrix, vcp,
365+
Font.DisplayMode.SEE_THROUGH, bgAlpha, 0xF000F0);
366+
vcp.endBatch();
367+
matrices.popPose();
368+
}
369+
370+
private enum ColorMode
371+
{
372+
FIXED("Fixed"),
373+
RAINBOW("Rainbow"),
374+
BY_OWNER("By owner"),
375+
BY_TYPE("By type");
376+
377+
private final String name;
378+
379+
ColorMode(String name)
380+
{
381+
this.name = name;
382+
}
383+
384+
@Override
385+
public String toString()
386+
{
387+
return name;
388+
}
389+
}
390+
}

0 commit comments

Comments
 (0)