@@ -67,6 +67,7 @@ public final class WaypointsHack extends Hack
6767 private static final Pattern END_PORTAL_NAME_PATTERN =
6868 Pattern .compile ("^End Portal (\\ d+)$" , Pattern .CASE_INSENSITIVE );
6969 private static final long PORTAL_RECORD_COOLDOWN_MS = 2000L ;
70+ private static final long OCCLUSION_CACHE_MS = 250L ;
7071 private static final String [] DEATH_MESSAGE_PHRASES =
7172 {"was slain" , "was shot" , "was blown" , "was killed" , "was fireballed" ,
7273 "was roasted" , "was doomed" , "was squashed" , "was pummeled" ,
@@ -93,6 +94,7 @@ public final class WaypointsHack extends Hack
9394 private final Set <UUID > knownDead = new HashSet <>();
9495 // Guard to avoid handling our own injected chat messages
9596 private boolean sendingOwnChat = false ;
97+ private final Map <UUID , OcclusionSample > occlusionCache = new HashMap <>();
9698
9799 private final SliderSetting waypointRenderDistance = new SliderSetting (
98100 "Waypoint render distance" , 127 , 0 , DISTANCE_SLIDER_INFINITE , 1 ,
@@ -232,6 +234,7 @@ protected void onEnable()
232234 EVENTS .add (GUIRenderListener .class , this );
233235 otherDeathCooldown .clear ();
234236 knownDead .clear ();
237+ occlusionCache .clear ();
235238 }
236239
237240 @ Override
@@ -245,6 +248,7 @@ protected void onDisable()
245248 EVENTS .remove (GUIRenderListener .class , this );
246249 otherDeathCooldown .clear ();
247250 knownDead .clear ();
251+ occlusionCache .clear ();
248252 }
249253
250254 // Add a temporary waypoint that should not be persisted. Returns the
@@ -775,6 +779,13 @@ public void onRender(PoseStack matrices, float partialTicks)
775779 return ;
776780
777781 var list = new ArrayList <>(manager .all ());
782+ long nowMs = System .currentTimeMillis ();
783+ double configuredLabelRange = waypointRenderDistance .getValue ();
784+ boolean configuredInfiniteLabels =
785+ configuredLabelRange >= DISTANCE_SLIDER_INFINITE ;
786+ double occlusionMaxDistSq =
787+ configuredInfiniteLabels ? Double .POSITIVE_INFINITY
788+ : Math .max (0.0 , configuredLabelRange * configuredLabelRange );
778789 double beaconRange = beaconRenderDistance .getValue ();
779790 boolean beaconInfinite = beaconRange >= DISTANCE_SLIDER_INFINITE ;
780791 boolean beaconsEnabled = beaconInfinite || beaconRange > 0.0 ;
@@ -792,20 +803,7 @@ public void onRender(PoseStack matrices, float partialTicks)
792803
793804 double distSq = MC .player .distanceToSqr (wp .getX () + 0.5 ,
794805 wp .getY () + 0.5 , wp .getZ () + 0.5 );
795- boolean applyObscuredRules =
796- dimObscuredOnScreenWaypoints .isChecked ()
797- || iconOnlyForObscuredOnScreenWaypoints .isChecked ();
798- boolean obscured = applyObscuredRules && isWaypointObscured (wp );
799- boolean directlyLooked =
800- obscured && isDirectlyLookingAtWaypoint (wp , 5.0 );
801- boolean suppressDetails = obscured && !directlyLooked
802- && iconOnlyForObscuredOnScreenWaypoints .isChecked ();
803806 int waypointColor = applyFade (w .getColor (), distSq );
804- if (obscured && !directlyLooked
805- && dimObscuredOnScreenWaypoints .isChecked ())
806- {
807- waypointColor = dimForObscured (waypointColor );
808- }
809807 double dist = Math .sqrt (distSq );
810808 double trd = waypointRenderDistance .getValue ();
811809 boolean infiniteLabels = trd >= DISTANCE_SLIDER_INFINITE ;
@@ -858,6 +856,24 @@ public void onRender(PoseStack matrices, float partialTicks)
858856
859857 if (renderLabel )
860858 {
859+ boolean applyObscuredRules =
860+ dimObscuredOnScreenWaypoints .isChecked ()
861+ || iconOnlyForObscuredOnScreenWaypoints .isChecked ();
862+ int labelColor = waypointColor ;
863+ boolean suppressDetails = false ;
864+ if (applyObscuredRules )
865+ {
866+ boolean directlyLooked =
867+ isDirectlyLookingAtWaypoint (wp , 5.0 );
868+ boolean obscured =
869+ !directlyLooked && isWaypointObscuredCached (w , wp ,
870+ distSq , occlusionMaxDistSq , nowMs );
871+ suppressDetails = obscured
872+ && iconOnlyForObscuredOnScreenWaypoints .isChecked ();
873+ if (obscured && dimObscuredOnScreenWaypoints .isChecked ())
874+ labelColor = dimForObscured (labelColor );
875+ }
876+
861877 String title = w .getName () == null ? "" : w .getName ();
862878 String icon = iconChar (w .getIcon ());
863879 if (suppressDetails )
@@ -944,11 +960,11 @@ public void onRender(PoseStack matrices, float partialTicks)
944960 }
945961 // Keep a constant 10px separation using local pixel offset
946962 float sepPx = 10.0f ;
947- drawWorldLabel (matrices , title , lx , ly , lz , waypointColor ,
948- scale , -sepPx );
963+ drawWorldLabel (matrices , title , lx , ly , lz , labelColor , scale ,
964+ -sepPx );
949965 if (!suppressDetails )
950966 drawWorldLabel (matrices , distanceText , lx , ly , lz ,
951- waypointColor , (float )(scale * 0.9f ), 0f );
967+ labelColor , (float )(scale * 0.9f ), 0f );
952968 }
953969 }
954970 }
@@ -1650,13 +1666,40 @@ private boolean isWaypointObscured(BlockPos waypointPos)
16501666 {
16511667 if (MC .player == null || MC .level == null || waypointPos == null )
16521668 return false ;
1669+
1670+ // If the waypoint's chunk isn't loaded, LOS is unknown. Treat it as
1671+ // obscured to avoid far waypoints popping back to full text.
1672+ if (!MC .level .hasChunkAt (waypointPos ))
1673+ return true ;
16531674
16541675 Vec3 eyes = MC .player .getEyePosition (1.0F );
16551676 Vec3 target = new Vec3 (waypointPos .getX () + 0.5 ,
16561677 waypointPos .getY () + 1.2 , waypointPos .getZ () + 0.5 );
16571678 return !BlockUtils .hasLineOfSight (eyes , target );
16581679 }
16591680
1681+ private boolean isWaypointObscuredCached (Waypoint waypoint ,
1682+ BlockPos waypointPos , double distSq , double maxOcclusionDistSq ,
1683+ long nowMs )
1684+ {
1685+ if (waypoint == null || waypointPos == null )
1686+ return false ;
1687+
1688+ // Respect user's waypoint render distance setting.
1689+ if (distSq > maxOcclusionDistSq )
1690+ return false ;
1691+
1692+ OcclusionSample cached = occlusionCache .get (waypoint .getUuid ());
1693+ if (cached != null && cached .pos .equals (waypointPos )
1694+ && nowMs - cached .timestampMs <= OCCLUSION_CACHE_MS )
1695+ return cached .obscured ;
1696+
1697+ boolean obscured = isWaypointObscured (waypointPos );
1698+ occlusionCache .put (waypoint .getUuid (),
1699+ new OcclusionSample (waypointPos .immutable (), obscured , nowMs ));
1700+ return obscured ;
1701+ }
1702+
16601703 private boolean isDirectlyLookingAtWaypoint (BlockPos waypointPos ,
16611704 double maxAngleDeg )
16621705 {
@@ -1702,6 +1745,20 @@ private static final class WaypointEntry
17021745 }
17031746 }
17041747
1748+ private static final class OcclusionSample
1749+ {
1750+ final BlockPos pos ;
1751+ final boolean obscured ;
1752+ final long timestampMs ;
1753+
1754+ OcclusionSample (BlockPos pos , boolean obscured , long timestampMs )
1755+ {
1756+ this .pos = pos ;
1757+ this .obscured = obscured ;
1758+ this .timestampMs = timestampMs ;
1759+ }
1760+ }
1761+
17051762 private enum PortalKind
17061763 {
17071764 NETHER ,
0 commit comments