diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java index c0b6738f..c50eb60d 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java @@ -51,7 +51,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import org.bukkit.*; import org.bukkit.craftbukkit.CraftWorld; -import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; @@ -473,7 +473,7 @@ public class PacketProcessor implements Listener { level.removeBlockEntity(pos); level.setBlock(pos, blockData, blockState1); level.getChunkSource().blockChanged(pos); - FightSystem.getHullHider().blockUpdate(Config.world.getBlockAt(x, y, z), CraftMagicNumbers.getMaterial(Block.stateById(blockState)).getItemType()); + FightSystem.getHullHider().blockUpdate(Config.world.getBlockAt(x, y, z), CraftBlockData.fromData(Block.stateById(blockState))); }); } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java index d667ba45..8c7eee13 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightHullHider.java @@ -33,8 +33,8 @@ import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.hullhider.Hull; import de.steamwar.hullhider.HullHider; import de.steamwar.hullhider.Viewers; -import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -119,8 +119,8 @@ public class FightHullHider { }); } - public void blockUpdate(Block block, Material changedType) { - hullHider.blockUpdate(block, changedType); + public void blockUpdate(Block block, BlockData changedData) { + hullHider.blockUpdate(block, changedData); } public boolean isBlockHidden(Player player, int x, int y, int z) { diff --git a/HiderCore/src/de/steamwar/hullhider/Hull.java b/HiderCore/src/de/steamwar/hullhider/Hull.java index 745d5d88..b39a215e 100644 --- a/HiderCore/src/de/steamwar/hullhider/Hull.java +++ b/HiderCore/src/de/steamwar/hullhider/Hull.java @@ -37,8 +37,11 @@ import org.bukkit.Chunk; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Slab; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; @@ -64,6 +67,7 @@ public class Hull { private final BitSet occluding; private final BitSet visibility; private final HashMap visibilityDirections = new HashMap<>(); // Contains the visible directions of each occluding visible block + private final HashMap branches = new HashMap<>(); // The diagonal directions a ray along a single axis branches into private final HashSet uncoveredSurface = new HashSet<>(); private final Viewers viewers; @@ -91,6 +95,9 @@ public class Hull { for (IntVector direction : directions) { visibilityDirections.put(direction, new BitSet(region.volume())); + branches.put(direction, direction.sqLength() == 1 + ? Arrays.stream(directions).filter(v -> v.sqLength() >= 2 && dot(v, direction) == 1).toArray(IntVector[]::new) + : NO_BRANCHES); } } @@ -98,6 +105,15 @@ public class Hull { return material.isOccluding() || hiddenBlocks.contains(material); } + private boolean isOccluding(BlockData data) { + return isOccluding(data.getMaterial()) || isDoubleSlab(data); + } + + // Double slabs are full blocks, but slab materials do not occlude + private static boolean isDoubleSlab(BlockData data) { + return data instanceof Slab slab && slab.getType() == Slab.Type.DOUBLE; + } + public boolean isBlockHidden(Player player, int x, int y, int z) { return region.contains(x, y, z) && !isViewer(player) && !visibility.get(region.toIndex(x, y, z)); } @@ -248,37 +264,88 @@ public class Hull { long start = System.currentTimeMillis(); region.forEach((x, y, z) -> { - IntVector block = new IntVector(x, y, z); - if (isOccluding(world.getBlockAt(x, y, z).getType())) { - occluding.set(block.toId(region)); + Block block = world.getBlockAt(x, y, z); + Material type = block.getType(); + // Only slabs need their block data + if (isOccluding(type) || Tag.SLABS.isTagged(type) && isDoubleSlab(block.getBlockData())) { + occluding.set(region.toIndex(x, y, z)); } }); forEachBorder((root, direction) -> uncoverBlocks(new NullList<>(), root, direction)); plugin.getLogger().log(Level.INFO, () -> "[HullHider] initialisation finished: " + (System.currentTimeMillis() - start) + " ms, visible blocks: " + visibility.cardinality()); } - public void updateBlockVisibility(Block b, Material changedType) { + public void updateBlockVisibility(Block b, BlockData changedData) { IntVector root = new IntVector(b.getX(), b.getY(), b.getZ()); if (root.notInRegion(region)) return; int id = root.toId(region); - if (!occluding.get(id) || isOccluding(changedType)) return; + if (!occluding.get(id) || isOccluding(changedData)) return; occluding.clear(id); - if (!visibility.get(id)) return; List uncovered = new ArrayList<>(); - for (Map.Entry directionalVisibility : visibilityDirections.entrySet()) { - if (directionalVisibility.getValue().get(id)) { - directionalVisibility.getValue().clear(id); - uncoverBlocks(uncovered, root, directionalVisibility.getKey()); + if (visibility.get(id)) { + for (Map.Entry directionalVisibility : visibilityDirections.entrySet()) { + if (directionalVisibility.getValue().get(id)) { + directionalVisibility.getValue().clear(id); + uncoverBlocks(uncovered, root, directionalVisibility.getKey()); + } } } + // Also if the block was not visible itself, it might have blocked diagonal rays passing it + uncoverDiagonalsBeside(uncovered, root); uncoveredSurface.addAll(new HashSet<>(uncovered)); uncoveredSurface.remove(root); } + /** + * Continues the diagonal rays that could not pass the block while it occluded. + * The block lies between the start and the end of a diagonal step if it is the start moved along some, but not all, axes of the direction. + */ + private void uncoverDiagonalsBeside(List uncovered, IntVector root) { + for (IntVector diagonal : directions) { + if (diagonal.sqLength() < 2) continue; + + for (IntVector part : directions) { + if (part.sqLength() == 0 || part.sqLength() >= diagonal.sqLength() || !isPartOf(part, diagonal)) continue; + + IntVector from = root.add(-part.x, -part.y, -part.z); + if (from.notInRegion(region)) continue; + + int fromId = from.toId(region); + if (!occluding.get(fromId) && reachedBy(fromId, diagonal) && canPass(from, diagonal)) { + uncoverBlocks(uncovered, from.add(diagonal), diagonal, visibilityDirections.get(diagonal), NO_BRANCHES); + } + } + } + } + + private static boolean isPartOf(IntVector part, IntVector direction) { + return (part.x == 0 || part.x == direction.x) + && (part.y == 0 || part.y == direction.y) + && (part.z == 0 || part.z == direction.z); + } + + /** + * @return whether a ray moving in the diagonal direction reached the block: a diagonal ray or a branch of a cardinal ray + */ + private boolean reachedBy(int id, IntVector diagonal) { + if (visibilityDirections.get(diagonal).get(id)) return true; + + for (IntVector cardinal : directions) { + if (cardinal.sqLength() == 1 && dot(cardinal, diagonal) == 1 && visibilityDirections.get(cardinal).get(id)) { + return true; + } + } + return false; + } + + private static int dot(IntVector a, IntVector b) { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + public void sendUncoveredBlocks() { Map> sectionWise = new HashMap<>(); @@ -346,14 +413,7 @@ public class Hull { } private void uncoverBlocks(List uncovered, IntVector block, IntVector direction) { - uncoverBlocks( - uncovered, block, direction, visibilityDirections.get(direction), - direction.sqLength() == 1 - ? Arrays.stream(directions) - .filter(v -> v.sqLength() >= 2 && v.x * direction.x + v.y * direction.y + v.z * direction.z == 1) - .toArray(IntVector[]::new) - : NO_BRANCHES - ); + uncoverBlocks(uncovered, block, direction, visibilityDirections.get(direction), branches.get(direction)); } private void uncoverBlocks(List uncovered, IntVector block, IntVector direction, BitSet directionalVisibility, IntVector[] branchDirections) { @@ -370,12 +430,42 @@ public class Hull { if (occluding.get(id)) return; - uncoverBlocks(uncovered, block.add(direction), direction, directionalVisibility, branchDirections); + uncoverNext(uncovered, block, direction, directionalVisibility, branchDirections); for (IntVector branchDirection : branchDirections) { - uncoverBlocks(uncovered, block.add(branchDirection), branchDirection, visibilityDirections.get(branchDirection), NO_BRANCHES); + uncoverNext(uncovered, block, branchDirection, visibilityDirections.get(branchDirection), NO_BRANCHES); } } + private void uncoverNext(List uncovered, IntVector block, IntVector direction, BitSet directionalVisibility, IntVector[] branchDirections) { + int x = block.x + direction.x; + int y = block.y + direction.y; + int z = block.z + direction.z; + // The cheap checks first: most blocks were already reached from this direction + if (!region.contains(x, y, z) || directionalVisibility.get(region.toIndex(x, y, z)) || !canPass(block, direction)) return; + + uncoverBlocks(uncovered, new IntVector(x, y, z), direction, directionalVisibility, branchDirections); + } + + /** + * A ray can only move diagonally if it could also get there by moving along the single axes of the direction one after another, + * in any order, without passing an occluding block. Otherwise, it would pass between blocks that only touch at an edge or corner. + */ + private boolean canPass(IntVector block, IntVector direction) { + if (direction.sqLength() <= 1) return true; + // The ray ends there anyway; inside the region, all blocks in between are inside as well + if (!region.contains(block.x + direction.x, block.y + direction.y, block.z + direction.z)) return true; + + return canPass(block.x, block.y, block.z, direction.x, direction.y, direction.z); + } + + private boolean canPass(int x, int y, int z, int dx, int dy, int dz) { + if (Math.abs(dx) + Math.abs(dy) + Math.abs(dz) <= 1) return true; + + return (dx != 0 && !occluding.get(region.toIndex(x + dx, y, z)) && canPass(x + dx, y, z, 0, dy, dz)) + || (dy != 0 && !occluding.get(region.toIndex(x, y + dy, z)) && canPass(x, y + dy, z, dx, 0, dz)) + || (dz != 0 && !occluding.get(region.toIndex(x, y, z + dz)) && canPass(x, y, z + dz, dx, dy, 0)); + } + @Getter @AllArgsConstructor diff --git a/HiderCore/src/de/steamwar/hullhider/HullHider.java b/HiderCore/src/de/steamwar/hullhider/HullHider.java index 177d9ffe..c2a5d1b7 100644 --- a/HiderCore/src/de/steamwar/hullhider/HullHider.java +++ b/HiderCore/src/de/steamwar/hullhider/HullHider.java @@ -23,6 +23,7 @@ import de.steamwar.entity.REntity; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -148,13 +149,13 @@ public class HullHider implements Listener { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onBlockPhysic(BlockPhysicsEvent e) { if (e.getBlock() == e.getSourceBlock() || e.getChangedType() == Material.AIR) { - blockUpdate(e.getBlock(), e.getChangedType()); + blockUpdate(e.getBlock(), e.getChangedBlockData()); } } - public void blockUpdate(Block block, Material changedType) { + public void blockUpdate(Block block, BlockData changedData) { for (Hull hull : hulls) { - hull.updateBlockVisibility(block, changedType); + hull.updateBlockVisibility(block, changedData); } }