1010import com .mojang .blaze3d .vertex .PoseStack ;
1111import java .awt .Color ;
1212import java .util .ArrayList ;
13+ import java .util .HashSet ;
1314import java .util .List ;
15+ import java .util .UUID ;
16+ import net .minecraft .core .registries .BuiltInRegistries ;
17+ import net .minecraft .resources .Identifier ;
18+ import net .minecraft .sounds .SoundEvent ;
19+ import net .minecraft .sounds .SoundSource ;
1420import net .minecraft .world .InteractionHand ;
1521import net .minecraft .world .entity .Entity ;
1622import net .minecraft .world .entity .HumanoidArm ;
2632import net .wurstclient .SearchTags ;
2733import net .wurstclient .settings .CheckboxSetting ;
2834import net .wurstclient .settings .ColorSetting ;
35+ import net .wurstclient .settings .EnumSetting ;
36+ import net .wurstclient .settings .SliderSetting ;
37+ import net .wurstclient .settings .SliderSetting .ValueDisplay ;
2938import net .wurstclient .events .CameraTransformViewBobbingListener ;
3039import net .wurstclient .events .CameraTransformViewBobbingListener .CameraTransformViewBobbingEvent ;
3140import java .lang .reflect .Method ;
3241import net .wurstclient .events .RenderListener ;
3342import net .wurstclient .events .UpdateListener ;
3443import net .wurstclient .hack .Hack ;
3544import net .wurstclient .util .BlockUtils ;
45+ import net .wurstclient .util .ChatUtils ;
3646import net .wurstclient .util .EntityUtils ;
3747import net .wurstclient .util .RenderUtils ;
3848
@@ -53,9 +63,25 @@ public class PearlEspHack extends Hack implements UpdateListener,
5363 "Show player prediction" ,
5464 "Show predicted trajectory while the local player is holding a pearl." ,
5565 true );
66+ private final CheckboxSetting showTracerLine =
67+ new CheckboxSetting ("Show tracer line" ,
68+ "Draw trajectory lines for pearl predictions." , true );
69+ private final SliderSetting tracerThickness = new SliderSetting (
70+ "Tracer thickness" , 2 , 0.5 , 8 , 0.1 , ValueDisplay .DECIMAL );
71+ private final CheckboxSetting chatAlerts =
72+ new CheckboxSetting ("Chat alerts" ,
73+ "Show a chat alert when a new ender pearl is detected." , false );
74+ private final CheckboxSetting soundAlerts =
75+ new CheckboxSetting ("Sound alerts" ,
76+ "Play a sound alert when a new ender pearl is detected." , false );
77+ private final EnumSetting <AlertSound > alertSound =
78+ new EnumSetting <>("Alert sound" ,
79+ "Sound used for PearlESP alerts (includes all note-block sounds)." ,
80+ AlertSound .values (), AlertSound .NOTE_BLOCK_CHIME );
5681
5782 private final ArrayList <TrackedPearl > pearls = new ArrayList <>();
5883 private final ArrayList <HeldPearl > holders = new ArrayList <>();
84+ private final HashSet <UUID > alertedPearls = new HashSet <>();
5985 private boolean hasOwnPearl ;
6086 private long ownPearlSuppressUntilMs ;
6187
@@ -66,6 +92,11 @@ public PearlEspHack()
6692 addSetting (color );
6793 addSetting (highlightHeld );
6894 addSetting (showPlayerPrediction );
95+ addSetting (showTracerLine );
96+ addSetting (tracerThickness );
97+ addSetting (chatAlerts );
98+ addSetting (soundAlerts );
99+ addSetting (alertSound );
69100 }
70101
71102 @ Override
@@ -84,6 +115,7 @@ protected void onDisable()
84115 EVENTS .remove (RenderListener .class , this );
85116 pearls .clear ();
86117 holders .clear ();
118+ alertedPearls .clear ();
87119 hasOwnPearl = false ;
88120 ownPearlSuppressUntilMs = 0L ;
89121 }
@@ -94,6 +126,7 @@ public void onUpdate()
94126 pearls .clear ();
95127 holders .clear ();
96128 hasOwnPearl = false ;
129+ HashSet <UUID > seenPearls = new HashSet <>();
97130
98131 if (MC .level == null )
99132 return ;
@@ -103,15 +136,18 @@ public void onUpdate()
103136 if (entity .getType () == EntityType .ENDER_PEARL )
104137 {
105138 Entity pearl = entity ;
139+ seenPearls .add (pearl .getUUID ());
106140 PearlPrediction prediction = buildPrediction (pearl );
107141 pearls .add (new TrackedPearl (pearl , prediction ));
142+ boolean ownPearl = false ;
108143 // Detect ownership via reflection and set suppression window
109144 try
110145 {
111146 Method m = pearl .getClass ().getMethod ("getOwner" );
112147 Object owner = m .invoke (pearl );
113148 if (owner == MC .player )
114149 {
150+ ownPearl = true ;
115151 hasOwnPearl = true ;
116152 ownPearlSuppressUntilMs =
117153 System .currentTimeMillis () + 4000 ; // suppress
@@ -120,12 +156,42 @@ public void onUpdate()
120156 }
121157 }catch (Exception ignored )
122158 {}
159+
160+ if (!ownPearl && alertedPearls .add (pearl .getUUID ()))
161+ triggerPearlAlert (pearl );
123162 continue ;
124163 }
125164
126165 if (entity instanceof Player player )
127166 handleHolder (player );
128167 }
168+
169+ alertedPearls .retainAll (seenPearls );
170+ }
171+
172+ private void triggerPearlAlert (Entity pearl )
173+ {
174+ if (!chatAlerts .isChecked () && !soundAlerts .isChecked ())
175+ return ;
176+
177+ if (chatAlerts .isChecked ())
178+ {
179+ int x = (int )Math .floor (pearl .getX ());
180+ int y = (int )Math .floor (pearl .getY ());
181+ int z = (int )Math .floor (pearl .getZ ());
182+ ChatUtils .message ("PearlESP: Ender pearl detected at " + x + ", "
183+ + y + ", " + z + "." );
184+ }
185+
186+ if (!soundAlerts .isChecked () || MC .level == null || MC .player == null )
187+ return ;
188+
189+ SoundEvent sound = alertSound .getSelected ().resolve ();
190+ if (sound == null )
191+ return ;
192+
193+ MC .level .playLocalSound (MC .player .getX (), MC .player .getY (),
194+ MC .player .getZ (), sound , SoundSource .PLAYERS , 1.0F , 1.0F , false );
129195 }
130196
131197 private void handleHolder (Player player )
@@ -157,6 +223,9 @@ private boolean isHoldingPearlAnyHand(Player player)
157223 public void onCameraTransformViewBobbing (
158224 CameraTransformViewBobbingEvent event )
159225 {
226+ if (!showTracerLine .isChecked ())
227+ return ;
228+
160229 for (TrackedPearl t : pearls )
161230 {
162231 PearlPrediction p = t .prediction ();
@@ -266,8 +335,7 @@ public void onRender(PoseStack matrixStack, float partialTicks)
266335 {
267336 landingBoxes .add (createLandingBox (pred .landing (),
268337 LANDING_BOX_RADIUS ));
269- RenderUtils .drawCurvedLine (matrixStack , pred .path (),
270- lineColor , false );
338+ renderTrajectory (matrixStack , pred .path (), lineColor );
271339 }
272340 }
273341 }
@@ -297,10 +365,11 @@ public void onRender(PoseStack matrixStack, float partialTicks)
297365 private void renderTrajectory (PoseStack matrixStack , List <Vec3 > path ,
298366 int lineColor )
299367 {
300- if (path == null || path .size () < 2 )
368+ if (! showTracerLine . isChecked () || path == null || path .size () < 2 )
301369 return ;
302370
303- RenderUtils .drawCurvedLine (matrixStack , path , lineColor , false );
371+ RenderUtils .drawCurvedLine (matrixStack , path , lineColor , false ,
372+ tracerThickness .getValue ());
304373 }
305374
306375 private PearlPrediction buildPrediction (Entity pearl )
@@ -440,4 +509,68 @@ private record PearlPrediction(List<Vec3> path, Vec3 landing)
440509
441510 private record HeldPearl (Player player , InteractionHand hand )
442511 {}
512+
513+ private enum AlertSound
514+ {
515+ NOTE_BLOCK_HARP ("Note Block Harp" , "minecraft:block.note_block.harp" ),
516+ NOTE_BLOCK_BASS ("Note Block Bass" , "minecraft:block.note_block.bass" ),
517+ NOTE_BLOCK_BASEDRUM ("Note Block Basedrum" ,
518+ "minecraft:block.note_block.basedrum" ),
519+ NOTE_BLOCK_SNARE ("Note Block Snare" ,
520+ "minecraft:block.note_block.snare" ),
521+ NOTE_BLOCK_HAT ("Note Block Hat" , "minecraft:block.note_block.hat" ),
522+ NOTE_BLOCK_GUITAR ("Note Block Guitar" ,
523+ "minecraft:block.note_block.guitar" ),
524+ NOTE_BLOCK_FLUTE ("Note Block Flute" ,
525+ "minecraft:block.note_block.flute" ),
526+ NOTE_BLOCK_BELL ("Note Block Bell" , "minecraft:block.note_block.bell" ),
527+ NOTE_BLOCK_CHIME ("Note Block Chime" ,
528+ "minecraft:block.note_block.chime" ),
529+ NOTE_BLOCK_XYLOPHONE ("Note Block Xylophone" ,
530+ "minecraft:block.note_block.xylophone" ),
531+ NOTE_BLOCK_IRON_XYLOPHONE ("Note Block Iron Xylophone" ,
532+ "minecraft:block.note_block.iron_xylophone" ),
533+ NOTE_BLOCK_COW_BELL ("Note Block Cow Bell" ,
534+ "minecraft:block.note_block.cow_bell" ),
535+ NOTE_BLOCK_DIDGERIDOO ("Note Block Didgeridoo" ,
536+ "minecraft:block.note_block.didgeridoo" ),
537+ NOTE_BLOCK_BIT ("Note Block Bit" , "minecraft:block.note_block.bit" ),
538+ NOTE_BLOCK_BANJO ("Note Block Banjo" ,
539+ "minecraft:block.note_block.banjo" ),
540+ NOTE_BLOCK_PLING ("Note Block Pling" ,
541+ "minecraft:block.note_block.pling" ),
542+ XP_PICKUP ("XP Pickup" , "minecraft:entity.experience_orb.pickup" ),
543+ UI_BUTTON_CLICK ("UI Button Click" , "minecraft:ui.button.click" ),
544+ AMETHYST_CHIME ("Amethyst Chime" ,
545+ "minecraft:block.amethyst_block.chime" ),
546+ BELL_USE ("Bell Use" , "minecraft:block.bell.use" ),
547+ ITEM_PICKUP ("Item Pickup" , "minecraft:entity.item.pickup" );
548+
549+ private final String displayName ;
550+ private final String id ;
551+
552+ private AlertSound (String displayName , String id )
553+ {
554+ this .displayName = displayName ;
555+ this .id = id ;
556+ }
557+
558+ private SoundEvent resolve ()
559+ {
560+ try
561+ {
562+ return BuiltInRegistries .SOUND_EVENT
563+ .getValue (Identifier .parse (id ));
564+ }catch (Exception e )
565+ {
566+ return null ;
567+ }
568+ }
569+
570+ @ Override
571+ public String toString ()
572+ {
573+ return displayName ;
574+ }
575+ }
443576}
0 commit comments