2626public class FakeBlockVisualization extends BlockBoundaryVisualization
2727{
2828
29- protected final boolean waterTransparent ;
30-
3129 /**
3230 * Construct a new {@code FakeBlockVisualization}.
3331 *
@@ -37,9 +35,6 @@ public class FakeBlockVisualization extends BlockBoundaryVisualization
3735 */
3836 public FakeBlockVisualization (@ NotNull World world , @ NotNull IntVector visualizeFrom , int height ) {
3937 super (world , visualizeFrom , height );
40-
41- // Water is considered transparent based on whether the visualization is initiated in water.
42- waterTransparent = visualizeFrom .toBlock (world ).getType () == Material .WATER ;
4338 }
4439
4540 @ Override
@@ -49,9 +44,9 @@ public FakeBlockVisualization(@NotNull World world, @NotNull IntVector visualize
4944 return switch (boundary .type ())
5045 {
5146 case SUBDIVISION_3D -> addExactBlockElement (Material .IRON_BLOCK .createBlockData ());
52- case SUBDIVISION -> addBlockElement (Material .IRON_BLOCK .createBlockData ());
47+ case SUBDIVISION -> addExactBlockElement (Material .IRON_BLOCK .createBlockData ());
5348 case ADMIN_CLAIM -> {
54- BlockData orangeGlass = Material .ORANGE_STAINED_GLASS .createBlockData ();
49+ BlockData orangeGlass = Material .GLOWSTONE .createBlockData ();
5550 yield addBlockElement (orangeGlass );
5651 }
5752 case INITIALIZE_ZONE -> addBlockElement (Material .DIAMOND_BLOCK .createBlockData ());
@@ -72,7 +67,7 @@ public FakeBlockVisualization(@NotNull World world, @NotNull IntVector visualize
7267 return switch (boundary .type ())
7368 {
7469 case ADMIN_CLAIM -> addBlockElement (Material .PUMPKIN .createBlockData ());
75- case SUBDIVISION -> addBlockElement (Material .WHITE_WOOL .createBlockData ());
70+ case SUBDIVISION -> addExactBlockElement (Material .WHITE_WOOL .createBlockData ());
7671 case SUBDIVISION_3D -> addExactBlockElement (Material .WHITE_WOOL .createBlockData ()); // exact placement for 3D sides
7772 case INITIALIZE_ZONE -> addBlockElement (Material .DIAMOND_BLOCK .createBlockData ());
7873 case CONFLICT_ZONE -> addBlockElement (Material .NETHERRACK .createBlockData ());
@@ -174,7 +169,7 @@ private void drawMainClaim(@NotNull Player player, @NotNull Boundary boundary) {
174169 }
175170
176171 // Add side markers along Z axis (east and west sides) - following terrain
177- for (int z = Math .max (minZ + STEP , minZ + STEP ); z < maxZ - STEP / 2 && z < maxZ - STEP / 2 ; z += STEP ) {
172+ for (int z = Math .max (minZ + STEP , minZ + STEP ); z < maxZ - STEP / 2 && z < maxZ - 2 ; z += STEP ) {
178173 if (z > minZ + 1 && z < maxZ - 1 ) {
179174 int terrainY = getSurfaceYAt (minX , z , player );
180175 addDisplayedForMainClaim (displayZone , new IntVector (minX , terrainY , z ), addSide , boundary .type ()); // West side
@@ -261,58 +256,108 @@ private void draw2DSubdivision(@NotNull Player player, @NotNull Boundary boundar
261256
262257 // From each corner, place one white wool block along the interior X side and one along the interior Z side
263258 // NW corner interior directions: +X, +Z
264- if (minX + 1 <= maxX ) addDisplayLocation (new IntVector (minX + 1 , nwY , minZ ), wool , boundary .type ());
265- if (minZ + 1 <= maxZ ) addDisplayLocation (new IntVector (minX , nwY , minZ + 1 ), wool , boundary .type ());
259+ if (minX + 1 <= maxX ) {
260+ int stubY = getSurfaceYAt (minX + 1 , minZ , player );
261+ addDisplayLocation (new IntVector (minX + 1 , stubY , minZ ), wool , boundary .type ());
262+ }
263+ if (minZ + 1 <= maxZ ) {
264+ int stubY = getSurfaceYAt (minX , minZ + 1 , player );
265+ addDisplayLocation (new IntVector (minX , stubY , minZ + 1 ), wool , boundary .type ());
266+ }
266267
267268 // SW corner interior directions: +X, -Z
268- if (minX + 1 <= maxX ) addDisplayLocation (new IntVector (minX + 1 , swY , maxZ ), wool , boundary .type ());
269- if (maxZ - 1 >= minZ ) addDisplayLocation (new IntVector (minX , swY , maxZ - 1 ), wool , boundary .type ());
269+ if (minX + 1 <= maxX ) {
270+ int stubY = getSurfaceYAt (minX + 1 , maxZ , player );
271+ addDisplayLocation (new IntVector (minX + 1 , stubY , maxZ ), wool , boundary .type ());
272+ }
273+ if (maxZ - 1 >= minZ ) {
274+ int stubY = getSurfaceYAt (minX , maxZ - 1 , player );
275+ addDisplayLocation (new IntVector (minX , stubY , maxZ - 1 ), wool , boundary .type ());
276+ }
270277
271278 // NE corner interior directions: -X, +Z
272- if (maxX - 1 >= minX ) addDisplayLocation (new IntVector (maxX - 1 , neY , minZ ), wool , boundary .type ());
273- if (minZ + 1 <= maxZ ) addDisplayLocation (new IntVector (maxX , neY , minZ + 1 ), wool , boundary .type ());
279+ if (maxX - 1 >= minX ) {
280+ int stubY = getSurfaceYAt (maxX - 1 , minZ , player );
281+ addDisplayLocation (new IntVector (maxX - 1 , stubY , minZ ), wool , boundary .type ());
282+ }
283+ if (minZ + 1 <= maxZ ) {
284+ int stubY = getSurfaceYAt (maxX , minZ + 1 , player );
285+ addDisplayLocation (new IntVector (maxX , stubY , minZ + 1 ), wool , boundary .type ());
286+ }
274287
275288 // SE corner interior directions: -X, -Z
276- if (maxX - 1 >= minX ) addDisplayLocation (new IntVector (maxX - 1 , seY , maxZ ), wool , boundary .type ());
277- if (maxZ - 1 >= minZ ) addDisplayLocation (new IntVector (maxX , seY , maxZ - 1 ), wool , boundary .type ());
289+ if (maxX - 1 >= minX ) {
290+ int stubY = getSurfaceYAt (maxX - 1 , maxZ , player );
291+ addDisplayLocation (new IntVector (maxX - 1 , stubY , maxZ ), wool , boundary .type ());
292+ }
293+ if (maxZ - 1 >= minZ ) {
294+ int stubY = getSurfaceYAt (maxX , maxZ - 1 , player );
295+ addDisplayLocation (new IntVector (maxX , stubY , maxZ - 1 ), wool , boundary .type ());
296+ }
278297 }
279298
280299 /**
281- * Gets the surface Y coordinate at a specific x,z position
282- * For main claims, allows 1-block occurrences to pass through to the next block below
300+ * Gets the surface Y coordinate at a specific x,z position with grass block handling
301+ * This ensures visualizations snap to grass_block instead of grass, matching GlowingVisualization
283302 */
284303 private int getSurfaceYAt (int x , int z , Player player ) {
285304 if (!world .isChunkLoaded (x >> 4 , z >> 4 )) {
286305 return player != null ? player .getLocation ().getBlockY () - 1 : world .getMinHeight () + 63 ;
287306 }
288-
307+
289308 // Start from the highest block at this x,z position
290309 int y = world .getMaxHeight () - 1 ;
291- boolean foundSolid = false ;
292-
293- // Find the highest non-air, non-transparent block
310+
311+ // Find the highest non-air block (including water as valid surface)
294312 while (y >= world .getMinHeight ()) {
295313 Block block = world .getBlockAt (x , y , z );
296-
297- // If we find a solid block after finding air, we've found the surface
298- if (foundSolid && !isTransparent (block )) {
299- // For main claims, we want to allow 1-block occurrences to pass through
300- // So we return the block below the surface
301- return y ;
302- }
303-
304- // If we find air, mark that we've found the surface
305- if (block .getType () == Material .AIR ) {
306- foundSolid = true ;
314+
315+ // If we find a non-air block, we've found the surface
316+ if (block .getType () != Material .AIR ) {
317+ // For transparent blocks (except water), find the actual surface below
318+ if (isTransparent (block ) && block .getType () != Material .WATER ) {
319+ return findSurfaceBelowTransparentBlocks (x , z , y );
320+ }
321+ return y ; // Return the Y of the highest non-air block (e.g., water, glass, or solid)
307322 }
308-
323+
309324 y --;
310325 }
311-
326+
312327 // If we get here, return the minimum height
313328 return world .getMinHeight ();
314329 }
315330
331+ /**
332+ * Finds the actual surface Y coordinate below a stack of transparent blocks
333+ * @param x The x coordinate
334+ * @param z The z coordinate
335+ * @param startY The Y coordinate where transparent blocks start
336+ * @return The Y coordinate of the actual surface below the transparent blocks
337+ */
338+ private int findSurfaceBelowTransparentBlocks (int x , int z , int startY ) {
339+ int y = startY ;
340+
341+ // Count how many transparent blocks are stacked
342+ int transparentBlockCount = 0 ;
343+ while (y >= world .getMinHeight () && isTransparent (world .getBlockAt (x , y , z )) && world .getBlockAt (x , y , z ).getType () != Material .WATER ) {
344+ transparentBlockCount ++;
345+ y --;
346+ }
347+
348+ // The surface is the first non-transparent block we find
349+ Block surfaceBlock = world .getBlockAt (x , y , z );
350+
351+ // If we found a valid surface block, return its Y coordinate
352+ if (surfaceBlock .getType () != Material .AIR ) {
353+ return y ;
354+ }
355+
356+ // If no valid surface found, return the original Y minus the transparent block count
357+ // This handles cases where transparent blocks are floating above air
358+ return startY - transparentBlockCount ;
359+ }
360+
316361 /**
317362 * Add a display element if accessible (override to use terrain-level placement for main claims)
318363 */
@@ -380,10 +425,20 @@ private void addDisplayLocation(@NotNull IntVector coordinate, @NotNull BlockDat
380425 coordinate .x () >= this .displayZone .getMinX () && coordinate .x () <= this .displayZone .getMaxX () &&
381426 coordinate .y () >= this .displayZone .getMinY () && coordinate .y () <= this .displayZone .getMaxY () &&
382427 coordinate .z () >= this .displayZone .getMinZ () && coordinate .z () <= this .displayZone .getMaxZ ()) {
383- // Use the explicit block data provided by the caller (e.g., iron for corners),
384- // so corners render correctly and sides use their own side material via addSideElements.
385- Consumer <@ NotNull IntVector > addElement = addBlockElement (blockData );
386- addElement .accept (coordinate );
428+ // For 2D subdivisions, use exact placement to avoid moving blocks from glass surfaces
429+ // Other visualization types use getVisibleLocation for terrain snapping
430+ if (blockData .getMaterial () == Material .IRON_BLOCK || blockData .getMaterial () == Material .WHITE_WOOL ) {
431+ // This is a 2D subdivision - use exact placement
432+ Block exactLocation = coordinate .toBlock (world );
433+ elements .add (new FakeBlockElement (coordinate , exactLocation .getBlockData (), blockData ));
434+ } else {
435+ // Other visualization types - use terrain snapping
436+ Block visibleLocation = getVisibleLocation (coordinate );
437+ int x = coordinate .x ();
438+ int y = visibleLocation .getY ();
439+ int z = coordinate .z ();
440+ elements .add (new FakeBlockElement (new IntVector (x , y , z ), visibleLocation .getBlockData (), blockData ));
441+ }
387442 }
388443 }
389444
@@ -497,18 +552,20 @@ private void addDisplayed3D(
497552 }
498553
499554 /**
500- * Create a {@link Consumer} that adds an appropriate {@link FakeBlockElement} for the given {@link IntVector}.
555+ * Create a {@link Consumer} that adds a {@link FakeBlockElement} at a terrain-snapped location (not exact coordinates).
556+ * This is used for most visualization types to ensure blocks appear at visible surface levels.
501557 *
502558 * @param fakeData the fake {@link BlockData}
503- * @return the function for determining a visible fake block location
559+ * @return the function for placing a fake block at a terrain-snapped location
504560 */
505561 private @ NotNull Consumer <@ NotNull IntVector > addBlockElement (@ NotNull BlockData fakeData )
506562 {
507563 return vector -> {
508- // Obtain visible location from starting point.
509564 Block visibleLocation = getVisibleLocation (vector );
510- // Create an element using our fake data and the determined block's real data.
511- elements .add (new FakeBlockElement (new IntVector (visibleLocation ), visibleLocation .getBlockData (), fakeData ));
565+ int x = vector .x ();
566+ int y = visibleLocation .getY ();
567+ int z = vector .z ();
568+ elements .add (new FakeBlockElement (new IntVector (x , y , z ), visibleLocation .getBlockData (), fakeData ));
512569 };
513570 }
514571
@@ -528,6 +585,20 @@ private void addDisplayed3D(
528585 };
529586 }
530587
588+ /**
589+ * Checks if a material is a glass block that should be treated as a solid surface
590+ * @param material The material to check
591+ * @return true if the material is a glass block
592+ */
593+ private boolean isGlassBlock (Material material ) {
594+ return material == Material .GLASS ||
595+ material == Material .GLASS_PANE ||
596+ material == Material .TINTED_GLASS ||
597+ material .name ().contains ("GLASS" ) ||
598+ material .name ().endsWith ("_GLASS" ) ||
599+ material .name ().endsWith ("_GLASS_PANE" );
600+ }
601+
531602 /**
532603 * Find a location that should be visible to players. This causes the visualization to "cling" to the ground.
533604 *
@@ -537,11 +608,24 @@ private void addDisplayed3D(
537608 private Block getVisibleLocation (@ NotNull IntVector vector )
538609 {
539610 Block block = vector .toBlock (world );
611+
612+ // Special handling for water surfaces
613+ if (block .getType () == Material .WATER ) {
614+ return block ; // Stay at water surface
615+ }
616+
617+ // Check if the block is glass - if so, treat it as solid and stay on it
618+ if (isGlassBlock (block .getType ())) {
619+ return block ; // Stay at glass surface
620+ }
621+
540622 BlockFace direction = (isTransparent (block )) ? BlockFace .DOWN : BlockFace .UP ;
541623
542624 while (block .getY () >= world .getMinHeight () &&
543625 block .getY () < world .getMaxHeight () - 1 &&
544- (!isTransparent (block .getRelative (BlockFace .UP )) || isTransparent (block )))
626+ (!isTransparent (block .getRelative (BlockFace .UP )) || isTransparent (block )) &&
627+ block .getType () != Material .WATER &&
628+ !isGlassBlock (block .getType ()))
545629 {
546630 block = block .getRelative (direction );
547631 }
@@ -559,13 +643,23 @@ protected boolean isTransparent(@NotNull Block block)
559643 {
560644 Material blockMaterial = block .getType ();
561645
562- // Custom per-material definitions.
646+ // Check if it's glass first - glass should be treated as solid for visualization purposes
647+ if (isGlassBlock (blockMaterial )) {
648+ return false ; // Glass is not transparent for visualization purposes
649+ }
650+
651+ // Custom per-material definitions matching GlowingVisualization
563652 switch (blockMaterial )
564653 {
565654 case WATER :
566- return waterTransparent ;
655+ return true ; // Treat water as transparent so visualizations float on water surface
656+ case SNOW_BLOCK :
657+ return true ;
567658 case SNOW :
568659 return false ;
660+ default :
661+ // Fall through to the general logic below
662+ break ;
569663 }
570664
571665 if (blockMaterial .isAir ()
@@ -576,7 +670,7 @@ protected boolean isTransparent(@NotNull Block block)
576670 || Tag .WALL_SIGNS .isTagged (blockMaterial ))
577671 return true ;
578672
579- return block .getType ().isTransparent ();
673+ return ! block .getType ().isOccluding ();
580674 }
581675
582676}
0 commit comments