1010import com .mojang .blaze3d .vertex .PoseStack ;
1111import java .awt .Color ;
1212import java .util .ArrayList ;
13+ import java .util .UUID ;
1314import java .util .stream .Collectors ;
1415import java .util .stream .Stream ;
1516import java .util .stream .StreamSupport ;
1617import net .minecraft .world .entity .Entity ;
1718import net .minecraft .world .entity .LivingEntity ;
19+ import net .minecraft .world .entity .monster .Creeper ;
1820import net .minecraft .world .entity .player .Player ;
1921import net .minecraft .world .entity .projectile .ShulkerBullet ;
2022import net .minecraft .world .entity .projectile .hurtingprojectile .WitherSkull ;
@@ -52,6 +54,12 @@ public final class MobEspHack extends Hack implements UpdateListener,
5254 "Fill shapes" , "Render filled versions of the ESP shapes." , true );
5355 private final CheckboxSetting tracerFlash = new CheckboxSetting (
5456 "Tracer flash" , "Make tracers pulse with a smooth fade." , false );
57+ private final CheckboxSetting lastAttackerTracer =
58+ new CheckboxSetting ("Last attacker tracer" ,
59+ "Draw a tracer to the mob that last damaged you." , false );
60+ private final CheckboxSetting chargingCreeperTracer = new CheckboxSetting (
61+ "Charging creeper tracer" ,
62+ "Draw a flashing tracer to creepers that are about to explode." , false );
5563
5664 // New color options to match MobSearch
5765 private final CheckboxSetting useRainbow =
@@ -109,6 +117,12 @@ public final class MobEspHack extends Hack implements UpdateListener,
109117 new CheckboxSetting ("Highlight wither projectiles" ,
110118 "Also outline the projectiles fired by withers." , false );
111119
120+ private static final long LAST_ATTACKER_TIMEOUT_MS = 5000 ;
121+ private static final long CREEPER_FLASH_PERIOD_MS = 450L ;
122+ private UUID lastAttackerUuid ;
123+ private long lastAttackerExpiresAt ;
124+ private int lastPlayerHurtTimeSeen ;
125+
112126 private int foundCount ;
113127
114128 public MobEspHack ()
@@ -119,6 +133,8 @@ public MobEspHack()
119133 addSetting (boxSize );
120134 addSetting (fillShapes );
121135 addSetting (tracerFlash );
136+ addSetting (lastAttackerTracer );
137+ addSetting (chargingCreeperTracer );
122138 addSetting (useRainbow );
123139 addSetting (color );
124140 entityFilters .forEach (this ::addSetting );
@@ -135,6 +151,9 @@ protected void onEnable()
135151 EVENTS .add (UpdateListener .class , this );
136152 EVENTS .add (CameraTransformViewBobbingListener .class , this );
137153 EVENTS .add (RenderListener .class , this );
154+ lastAttackerUuid = null ;
155+ lastAttackerExpiresAt = 0 ;
156+ lastPlayerHurtTimeSeen = MC .player == null ? 0 : MC .player .hurtTime ;
138157 }
139158
140159 @ Override
@@ -146,6 +165,9 @@ protected void onDisable()
146165 foundCount = 0 ;
147166 shulkerBullets .clear ();
148167 witherSkulls .clear ();
168+ lastAttackerUuid = null ;
169+ lastAttackerExpiresAt = 0 ;
170+ lastPlayerHurtTimeSeen = 0 ;
149171 }
150172
151173 @ Override
@@ -154,6 +176,7 @@ public void onUpdate()
154176 mobs .clear ();
155177 shulkerBullets .clear ();
156178 witherSkulls .clear ();
179+ updateLastAttackerTracking ();
157180
158181 Stream <LivingEntity > stream = StreamSupport
159182 .stream (MC .level .entitiesForRendering ().spliterator (), false )
@@ -205,7 +228,8 @@ public String getRenderName()
205228 public void onCameraTransformViewBobbing (
206229 CameraTransformViewBobbingEvent event )
207230 {
208- if (style .hasLines ())
231+ if (style .hasLines () || lastAttackerTracer .isChecked ()
232+ || chargingCreeperTracer .isChecked ())
209233 event .cancel ();
210234 }
211235
@@ -216,6 +240,8 @@ public void onRender(PoseStack matrixStack, float partialTicks)
216240 boolean glowMode = shape == MobEspStyleSetting .Shape .GLOW ;
217241 boolean drawShape = !glowMode && shape != MobEspStyleSetting .Shape .NONE ;
218242 boolean drawLines = style .hasLines ();
243+ boolean drawSpecialTracers =
244+ lastAttackerTracer .isChecked () || chargingCreeperTracer .isChecked ();
219245 boolean drawFill = drawShape && fillShapes .isChecked ();
220246 float projectileOutlineAlpha = 0.85F ;
221247 float projectileFillAlpha = 0.35F ;
@@ -231,10 +257,10 @@ public void onRender(PoseStack matrixStack, float partialTicks)
231257 drawShape ? new ArrayList <>(anticipatedSize ) : null ;
232258 ArrayList <ColoredBox > filledShapes =
233259 drawFill ? new ArrayList <>(anticipatedSize ) : null ;
234- ArrayList <ColoredPoint > ends =
235- drawLines ? new ArrayList <>(anticipatedSize ) : null ;
260+ ArrayList <ColoredPoint > ends = ( drawLines || drawSpecialTracers )
261+ ? new ArrayList <>(anticipatedSize ) : null ;
236262
237- if (drawShape || drawLines )
263+ if (drawShape || drawLines || drawSpecialTracers )
238264 {
239265 double extraSize = drawShape ? boxSize .getExtraSize () / 2D : 0 ;
240266
@@ -243,7 +269,6 @@ public void onRender(PoseStack matrixStack, float partialTicks)
243269 AABB lerpedBox = EntityUtils .getLerpedBox (e , partialTicks );
244270 float [] rgb = getColorRgb ();
245271 int outlineColor = RenderUtils .toIntColor (rgb , 0.5F );
246-
247272 if (drawShape )
248273 {
249274 AABB box =
@@ -262,6 +287,9 @@ public void onRender(PoseStack matrixStack, float partialTicks)
262287 new ColoredPoint (lerpedBox .getCenter (), outlineColor ));
263288 }
264289
290+ if (drawSpecialTracers && ends != null )
291+ addSpecialTracers (ends , partialTicks , drawLines );
292+
265293 if (highlightShulkerProjectiles .isChecked ())
266294 {
267295 for (ShulkerBullet bullet : shulkerBullets )
@@ -426,7 +454,10 @@ public RenderStyleInfo getRenderStyleInfo()
426454 double extra = drawShape ? boxSize .getExtraSize () / 2D : 0 ;
427455 boolean fill = drawShape && fillShapes .isChecked ();
428456
429- return new RenderStyleInfo (renderShape , style .hasLines (), fill , extra );
457+ return new RenderStyleInfo (renderShape ,
458+ style .hasLines () || lastAttackerTracer .isChecked ()
459+ || chargingCreeperTracer .isChecked (),
460+ fill , extra );
430461 }
431462
432463 private float [] getColorRgb ()
@@ -449,6 +480,102 @@ public boolean shouldRenderEntity(LivingEntity entity)
449480 return entityFilters .testOne (entity );
450481 }
451482
483+ private boolean isChargingCreeperTracer (LivingEntity entity )
484+ {
485+ if (!chargingCreeperTracer .isChecked () || !(entity instanceof Creeper ))
486+ return false ;
487+
488+ Creeper creeper = (Creeper )entity ;
489+ return creeper .getSwellDir () > 0 || creeper .isIgnited ()
490+ || creeper .isPowered ();
491+ }
492+
493+ private void addSpecialTracers (ArrayList <ColoredPoint > ends ,
494+ float partialTicks , boolean normalMobTracersEnabled )
495+ {
496+ float [] rgb = getColorRgb ();
497+ int baseTracerColor = RenderUtils .toIntColor (rgb , 0.5F );
498+ boolean drawCreeperFlashNow = isCreeperFlashVisibleNow ();
499+
500+ if (lastAttackerTracer .isChecked () && MC .level != null
501+ && lastAttackerUuid != null )
502+ {
503+ Entity tracked = MC .level .getEntity (lastAttackerUuid );
504+ if (tracked instanceof LivingEntity living
505+ && !(living instanceof Player ) && !living .isRemoved ()
506+ && living .getHealth () > 0
507+ && (!normalMobTracersEnabled || !mobs .contains (living )))
508+ {
509+ AABB attackerBox =
510+ EntityUtils .getLerpedBox (living , partialTicks );
511+ ends .add (
512+ new ColoredPoint (attackerBox .getCenter (), baseTracerColor ));
513+ }
514+ }
515+
516+ if (chargingCreeperTracer .isChecked ())
517+ {
518+ for (Entity entity : MC .level .entitiesForRendering ())
519+ {
520+ if (!(entity instanceof Creeper creeper ) || entity .isRemoved ()
521+ || creeper .getHealth () <= 0
522+ || (normalMobTracersEnabled && mobs .contains (creeper )))
523+ continue ;
524+ if (!isChargingCreeperTracer (creeper ))
525+ continue ;
526+ if (!drawCreeperFlashNow )
527+ continue ;
528+
529+ AABB creeperBox =
530+ EntityUtils .getLerpedBox (creeper , partialTicks );
531+ ends .add (
532+ new ColoredPoint (creeperBox .getCenter (), baseTracerColor ));
533+ }
534+ }
535+ }
536+
537+ private boolean isCreeperFlashVisibleNow ()
538+ {
539+ long phase = System .currentTimeMillis () % CREEPER_FLASH_PERIOD_MS ;
540+ return phase < (CREEPER_FLASH_PERIOD_MS / 2L );
541+ }
542+
543+ private void updateLastAttackerTracking ()
544+ {
545+ if (MC .player == null )
546+ {
547+ lastPlayerHurtTimeSeen = 0 ;
548+ return ;
549+ }
550+
551+ int hurtTime = MC .player .hurtTime ;
552+ if (hurtTime > lastPlayerHurtTimeSeen )
553+ {
554+ Entity attacker = null ;
555+ if (MC .player .getLastDamageSource () != null )
556+ {
557+ attacker = MC .player .getLastDamageSource ().getEntity ();
558+ if (attacker == null )
559+ attacker =
560+ MC .player .getLastDamageSource ().getDirectEntity ();
561+ }
562+
563+ if (attacker instanceof LivingEntity living
564+ && !(living instanceof Player ))
565+ {
566+ lastAttackerUuid = living .getUUID ();
567+ lastAttackerExpiresAt =
568+ System .currentTimeMillis () + LAST_ATTACKER_TIMEOUT_MS ;
569+ }
570+ }
571+
572+ lastPlayerHurtTimeSeen = hurtTime ;
573+
574+ if (lastAttackerUuid != null
575+ && System .currentTimeMillis () > lastAttackerExpiresAt )
576+ lastAttackerUuid = null ;
577+ }
578+
452579 public static enum RenderShape
453580 {
454581 NONE ,
0 commit comments