4343import net .wurstclient .settings .SliderSetting ;
4444import net .wurstclient .settings .ColorSetting ;
4545import net .wurstclient .settings .SliderSetting .ValueDisplay ;
46+ import net .wurstclient .util .BlockUtils ;
4647import net .wurstclient .util .RenderUtils ;
4748import net .wurstclient .waypoints .Waypoint ;
4849import net .wurstclient .waypoints .WaypointDimension ;
@@ -66,6 +67,7 @@ public final class WaypointsHack extends Hack
6667 private static final Pattern END_PORTAL_NAME_PATTERN =
6768 Pattern .compile ("^End Portal (\\ d+)$" , Pattern .CASE_INSENSITIVE );
6869 private static final long PORTAL_RECORD_COOLDOWN_MS = 2000L ;
70+ private static final long OCCLUSION_CACHE_MS = 250L ;
6971 private static final String [] DEATH_MESSAGE_PHRASES =
7072 {"was slain" , "was shot" , "was blown" , "was killed" , "was fireballed" ,
7173 "was roasted" , "was doomed" , "was squashed" , "was pummeled" ,
@@ -92,6 +94,7 @@ public final class WaypointsHack extends Hack
9294 private final Set <UUID > knownDead = new HashSet <>();
9395 // Guard to avoid handling our own injected chat messages
9496 private boolean sendingOwnChat = false ;
97+ private final Map <UUID , OcclusionSample > occlusionCache = new HashMap <>();
9598
9699 private final SliderSetting waypointRenderDistance = new SliderSetting (
97100 "Waypoint render distance" , 127 , 0 , DISTANCE_SLIDER_INFINITE , 1 ,
@@ -141,6 +144,14 @@ public void update()
141144 new CheckboxSetting ("Compass text outline" , true );
142145 private final SliderSetting compassOutlineOpacity = new SliderSetting (
143146 "Compass outline opacity" , 100 , 0 , 100 , 1 , ValueDisplay .INTEGER );
147+ private final CheckboxSetting dimObscuredOnScreenWaypoints =
148+ new CheckboxSetting ("Dim obscured on-screen waypoints" ,
149+ "When a waypoint is hidden behind blocks, greatly dim its world label unless you're looking directly at it." ,
150+ false );
151+ private final CheckboxSetting iconOnlyForObscuredOnScreenWaypoints =
152+ new CheckboxSetting ("Icon-only when obscured" ,
153+ "When a waypoint is hidden behind blocks, only show its icon unless you're looking directly at it." ,
154+ false );
144155
145156 // Waypoint world-label outline settings
146157 private final CheckboxSetting waypointOutline =
@@ -196,6 +207,8 @@ public WaypointsHack()
196207 addSetting (compassOpacity );
197208 addSetting (compassOutline );
198209 addSetting (compassOutlineOpacity );
210+ addSetting (dimObscuredOnScreenWaypoints );
211+ addSetting (iconOnlyForObscuredOnScreenWaypoints );
199212 addSetting (waypointOutline );
200213 addSetting (waypointOutlineOpacity );
201214 addSetting (compassBackgroundOpacity );
@@ -221,6 +234,7 @@ protected void onEnable()
221234 EVENTS .add (GUIRenderListener .class , this );
222235 otherDeathCooldown .clear ();
223236 knownDead .clear ();
237+ occlusionCache .clear ();
224238 }
225239
226240 @ Override
@@ -234,6 +248,7 @@ protected void onDisable()
234248 EVENTS .remove (GUIRenderListener .class , this );
235249 otherDeathCooldown .clear ();
236250 knownDead .clear ();
251+ occlusionCache .clear ();
237252 }
238253
239254 // Add a temporary waypoint that should not be persisted. Returns the
@@ -766,6 +781,13 @@ public void onRender(PoseStack matrices, float partialTicks)
766781 return ;
767782
768783 var list = new ArrayList <>(manager .all ());
784+ long nowMs = System .currentTimeMillis ();
785+ double configuredLabelRange = waypointRenderDistance .getValue ();
786+ boolean configuredInfiniteLabels =
787+ configuredLabelRange >= DISTANCE_SLIDER_INFINITE ;
788+ double occlusionMaxDistSq =
789+ configuredInfiniteLabels ? Double .POSITIVE_INFINITY
790+ : Math .max (0.0 , configuredLabelRange * configuredLabelRange );
769791 double beaconRange = beaconRenderDistance .getValue ();
770792 boolean beaconInfinite = beaconRange >= DISTANCE_SLIDER_INFINITE ;
771793 boolean beaconsEnabled = beaconInfinite || beaconRange > 0.0 ;
@@ -783,6 +805,7 @@ public void onRender(PoseStack matrices, float partialTicks)
783805
784806 double distSq = MC .player .distanceToSqr (wp .getX () + 0.5 ,
785807 wp .getY () + 0.5 , wp .getZ () + 0.5 );
808+ int waypointColor = applyFade (w .getColor (), distSq );
786809 double dist = Math .sqrt (distSq );
787810 double trd = waypointRenderDistance .getValue ();
788811 boolean infiniteLabels = trd >= DISTANCE_SLIDER_INFINITE ;
@@ -836,6 +859,24 @@ public void onRender(PoseStack matrices, float partialTicks)
836859
837860 if (renderLabel )
838861 {
862+ boolean applyObscuredRules =
863+ dimObscuredOnScreenWaypoints .isChecked ()
864+ || iconOnlyForObscuredOnScreenWaypoints .isChecked ();
865+ int labelColor = waypointColor ;
866+ boolean suppressDetails = false ;
867+ if (applyObscuredRules )
868+ {
869+ boolean directlyLooked =
870+ isDirectlyLookingAtWaypoint (wp , 5.0 );
871+ boolean obscured =
872+ !directlyLooked && isWaypointObscuredCached (w , wp ,
873+ distSq , occlusionMaxDistSq , nowMs );
874+ suppressDetails = obscured
875+ && iconOnlyForObscuredOnScreenWaypoints .isChecked ();
876+ if (obscured && dimObscuredOnScreenWaypoints .isChecked ())
877+ labelColor = dimForObscured (labelColor );
878+ }
879+
839880 String title = w .getName () == null ? "" : w .getName ();
840881 String icon = iconChar (w .getIcon ());
841882 if (!icon .isEmpty ())
@@ -919,10 +960,11 @@ public void onRender(PoseStack matrices, float partialTicks)
919960 }
920961 // Keep a constant 10px separation using local pixel offset
921962 float sepPx = 10.0f ;
922- drawWorldLabel (matrices , title , lx , ly , lz ,
923- applyFade (w .getColor (), distSq ), scale , -sepPx );
924- drawWorldLabel (matrices , distanceText , lx , ly , lz ,
925- applyFade (w .getColor (), distSq ), (float )(scale * 0.9f ), 0f );
963+ drawWorldLabel (matrices , title , lx , ly , lz , labelColor , scale ,
964+ -sepPx );
965+ if (!suppressDetails )
966+ drawWorldLabel (matrices , distanceText , lx , ly , lz ,
967+ labelColor , (float )(scale * 0.9f ), 0f );
926968 }
927969 }
928970 }
@@ -1618,6 +1660,75 @@ private static String cardinalFromYaw(double yaw)
16181660 }
16191661 }
16201662
1663+ private boolean isWaypointObscured (BlockPos waypointPos )
1664+ {
1665+ if (MC .player == null || MC .level == null || waypointPos == null )
1666+ return false ;
1667+
1668+ // If the waypoint's chunk isn't loaded, LOS is unknown. Treat it as
1669+ // obscured to avoid far waypoints popping back to full text.
1670+ if (!MC .level .hasChunkAt (waypointPos ))
1671+ return true ;
1672+
1673+ Vec3 eyes = MC .player .getEyePosition (1.0F );
1674+ Vec3 target = new Vec3 (waypointPos .getX () + 0.5 ,
1675+ waypointPos .getY () + 1.2 , waypointPos .getZ () + 0.5 );
1676+ return !BlockUtils .hasLineOfSight (eyes , target );
1677+ }
1678+
1679+ private boolean isWaypointObscuredCached (Waypoint waypoint ,
1680+ BlockPos waypointPos , double distSq , double maxOcclusionDistSq ,
1681+ long nowMs )
1682+ {
1683+ if (waypoint == null || waypointPos == null )
1684+ return false ;
1685+
1686+ // Respect user's waypoint render distance setting.
1687+ if (distSq > maxOcclusionDistSq )
1688+ return false ;
1689+
1690+ OcclusionSample cached = occlusionCache .get (waypoint .getUuid ());
1691+ if (cached != null && cached .pos .equals (waypointPos )
1692+ && nowMs - cached .timestampMs <= OCCLUSION_CACHE_MS )
1693+ return cached .obscured ;
1694+
1695+ boolean obscured = isWaypointObscured (waypointPos );
1696+ occlusionCache .put (waypoint .getUuid (),
1697+ new OcclusionSample (waypointPos .immutable (), obscured , nowMs ));
1698+ return obscured ;
1699+ }
1700+
1701+ private boolean isDirectlyLookingAtWaypoint (BlockPos waypointPos ,
1702+ double maxAngleDeg )
1703+ {
1704+ if (MC .player == null || waypointPos == null )
1705+ return false ;
1706+
1707+ Vec3 eyes = MC .player .getEyePosition (1.0F );
1708+ Vec3 target = new Vec3 (waypointPos .getX () + 0.5 ,
1709+ waypointPos .getY () + 1.2 , waypointPos .getZ () + 0.5 );
1710+ Vec3 toWp = target .subtract (eyes );
1711+ double len = toWp .length ();
1712+ if (len < 1.0E-6 )
1713+ return true ;
1714+
1715+ Vec3 dir = toWp .scale (1.0 / len );
1716+ Vec3 look = MC .player .getLookAngle ();
1717+ double dot = look .dot (dir );
1718+ dot = Math .max (-1.0 , Math .min (1.0 , dot ));
1719+ double angle = Math .toDegrees (Math .acos (dot ));
1720+ return angle <= maxAngleDeg ;
1721+ }
1722+
1723+ private int dimForObscured (int argb )
1724+ {
1725+ int a = (argb >>> 24 ) & 0xFF ;
1726+ if (a <= 0 )
1727+ a = 0xFF ;
1728+ int dimmedAlpha = Math .max (14 , (int )Math .round (a * 0.2 ));
1729+ return withAlpha (argb , dimmedAlpha );
1730+ }
1731+
16211732 private static final class WaypointEntry
16221733 {
16231734 final Waypoint w ;
@@ -1632,6 +1743,20 @@ private static final class WaypointEntry
16321743 }
16331744 }
16341745
1746+ private static final class OcclusionSample
1747+ {
1748+ final BlockPos pos ;
1749+ final boolean obscured ;
1750+ final long timestampMs ;
1751+
1752+ OcclusionSample (BlockPos pos , boolean obscured , long timestampMs )
1753+ {
1754+ this .pos = pos ;
1755+ this .obscured = obscured ;
1756+ this .timestampMs = timestampMs ;
1757+ }
1758+ }
1759+
16351760 private enum PortalKind
16361761 {
16371762 NETHER ,
0 commit comments