diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullCalc.java b/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullCalc.java
new file mode 100644
index 00000000..6510df16
--- /dev/null
+++ b/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullCalc.java
@@ -0,0 +1,310 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.bausystem.features.hullhider;
+
+import de.steamwar.bausystem.region.Point;
+import de.steamwar.bausystem.region.Region;
+import de.steamwar.entity.REntity;
+import de.steamwar.entity.REntityServer;
+import de.steamwar.entity.RFallingBlockEntity;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.block.data.type.Slab;
+import org.bukkit.entity.Player;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class HullCalc {
+
+
+
+ private static final World WORLD = Bukkit.getWorlds().get(0);
+
+ private static final int[] c1 = new int[]{1, -1, 0, 0};
+ private static final int[] c2 = new int[]{0, 0, 1, -1};
+
+ private int minX, minY, minZ, maxX, maxY, maxZ;
+ private REntityServer entityServer = new REntityServer();
+
+ private Set nonTechHideBlock = new HashSet<>();
+ private Set hullBlocks = new HashSet<>();
+
+ public HullCalc(Region region) {
+ this.minX = region.getMinPointBuild().getX();
+ this.minY = region.getMinPointBuild().getY();
+ this.minZ = region.getMinPointBuild().getZ();
+ this.maxX = region.getMaxPointBuild().getX();
+ this.maxY = region.getMaxPointBuild().getY();
+ this.maxZ = region.getMaxPointBuild().getZ();
+ }
+
+ public void calc() {
+ entityServer.getEntities().forEach(REntity::die);
+ long time = System.currentTimeMillis();
+ hullBlocks.clear();
+
+ calc(minX, minY, minZ, maxX, maxY, minZ, 0, 0, 1, maxZ - minZ);
+ calc(minX, minY, maxZ, maxX, maxY, maxZ, 0, 0, -1, maxZ - minZ);
+ calc(minX, minY, minZ, minX, maxY, maxZ, 1, 0, 0, maxX - minX);
+ calc(maxX, minY, minZ, maxX, maxY, maxZ, -1, 0, 0, maxX - minX);
+ calc(minX, minY, minZ, maxX, minY, maxZ, 0, 1, 0, maxY - minY);
+ calc(minX, maxY, minZ, maxX, maxY, maxZ, 0, -1, 0, maxY - minY);
+
+ hullBlocks.removeIf(point -> {
+ return point.getX() < minX || point.getX() > maxX || point.getY() < minY || point.getY() > maxY || point.getZ() < minZ || point.getZ() > maxZ;
+ });
+ nonTechHideBlock.addAll(hullBlocks);
+
+ System.out.println(System.currentTimeMillis() - time + "ms " + hullBlocks.size());
+
+ hullBlocks.forEach(point -> {
+ RFallingBlockEntity rFallingBlockEntity = new RFallingBlockEntity(entityServer, point.toLocation(WORLD).add(0.5, 0, 0.5), Material.WHITE_STAINED_GLASS);
+ rFallingBlockEntity.setNoGravity(true);
+ });
+ }
+
+ private void calc(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, int dirX, int dirY, int dirZ, int steps) {
+ boolean sideWays = dirY == 0;
+ Set points = new HashSet<>();
+ for (int x = minX; x <= maxX; x++) {
+ for (int y = minY; y <= maxY; y++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ points.add(new Point(dirX == 0 ? x : minX - dirX, dirY == 0 ? y : minY - dirY, dirZ == 0 ? z : minZ - dirZ));
+ }
+ }
+ }
+ for (int i = 0; i <= steps; i++) {
+ Set nextLayer = new HashSet<>();
+
+ for (Point point : points) {
+ Point next = point.add(dirX, dirY, dirZ);
+ if (!empty(next, sideWays)) {
+ hullBlocks.add(next);
+
+ if (dirX != 0) {
+ for (int c = 0; c < 4; c++) {
+ if (empty(point.add(0, c1[c], c2[c]), sideWays) && empty(point.add(dirX, c1[c], c2[c]), sideWays)) {
+ nextLayer.add(point.add(dirX, c1[c], c2[c]));
+ }
+ }
+ } else if (dirY != 0) {
+ for (int c = 0; c < 4; c++) {
+ if (empty(point.add(c1[c], 0, c2[c]), sideWays) && empty(point.add(c1[c], dirY, c2[c]), sideWays)) {
+ nextLayer.add(point.add(c1[c], dirY, c2[c]));
+ }
+ }
+ } else {
+ for (int c = 0; c < 4; c++) {
+ if (empty(point.add(c1[c], c2[c], 0), sideWays) && empty(point.add(c1[c], c2[c], dirZ), sideWays)) {
+ nextLayer.add(point.add(c1[c], c2[c], dirZ));
+ }
+ }
+ }
+ } else {
+ nextLayer.add(next);
+ nonTechHideBlock.add(next);
+
+ if (dirX != 0) {
+ for (int c = 0; c < 4; c++) {
+ if (!empty(point.add(0, c1[c], c2[c]), sideWays)) {
+ hullBlocks.add(point.add(0, c1[c], c2[c]));
+ }
+ if (empty(point.add(dirX, c1[c], c2[c]), sideWays)) {
+ nextLayer.add(point.add(dirX, c1[c], c2[c]));
+ } else {
+ hullBlocks.add(point.add(dirX, c1[c], c2[c]));
+ }
+ }
+ } else if (dirY != 0) {
+ for (int c = 0; c < 4; c++) {
+ if (!empty(point.add(c1[c], 0, c2[c]), sideWays)) {
+ hullBlocks.add(point.add(c1[c], 0, c2[c]));
+ }
+ if (empty(point.add(c1[c], dirY, c2[c]), sideWays)) {
+ nextLayer.add(point.add(c1[c], dirY, c2[c]));
+ } else {
+ hullBlocks.add(point.add(c1[c], dirY, c2[c]));
+ }
+ }
+ } else {
+ for (int c = 0; c < 4; c++) {
+ if (!empty(point.add(c1[c], c2[c], 0), sideWays)) {
+ hullBlocks.add(point.add(c1[c], c2[c], 0));
+ }
+ if (empty(point.add(c1[c], c2[c], dirZ), sideWays)) {
+ nextLayer.add(point.add(c1[c], c2[c], dirZ));
+ } else {
+ hullBlocks.add(point.add(c1[c], c2[c], dirZ));
+ }
+ }
+ }
+ }
+ }
+ points.clear();
+ for (Point point : nextLayer) {
+ if (point.getX() < this.minX || point.getX() > this.maxX) {
+ continue;
+ }
+ if (point.getY() < this.minY || point.getY() > this.maxY) {
+ continue;
+ }
+ if (point.getZ() < this.minZ || point.getZ() > this.maxZ) {
+ continue;
+ }
+ points.add(point);
+ }
+ if (points.isEmpty()) {
+ break;
+ }
+ }
+ }
+
+ private boolean empty(Point point, boolean side) {
+ Block block = point.toLocation(WORLD).getBlock();
+ if (block.getBlockData() instanceof Slab slab && slab.getType() == Slab.Type.DOUBLE) {
+ return false;
+ }
+ Material material = block.getType();
+ if (material.isAir()) {
+ return true;
+ }
+ if (material == Material.WATER) {
+ return true;
+ }
+ if (material == Material.SCAFFOLDING) {
+ return true;
+ }
+ if (material == Material.LADDER) {
+ return true;
+ }
+ if (material == Material.COBWEB) {
+ return true;
+ }
+ if (material == Material.IRON_BARS) {
+ return true;
+ }
+ if (material == Material.PLAYER_HEAD || material == Material.PLAYER_WALL_HEAD) {
+ return true;
+ }
+ if (material == Material.END_ROD) {
+ return true;
+ }
+ if (material == Material.CHAIN) {
+ return true;
+ }
+ if (material == Material.LANTERN) {
+ return true;
+ }
+ if (material == Material.LIGHTNING_ROD) {
+ return true;
+ }
+ if (material == Material.SOUL_LANTERN) {
+ return true;
+ }
+ if (material == Material.VINE) {
+ return true;
+ }
+ if (material == Material.SCULK_VEIN) {
+ return true;
+ }
+ if (material == Material.LEVER) {
+ return true;
+ }
+ if (material == Material.END_STONE_BRICK_WALL) {
+ return false;
+ }
+ if (material == Material.END_STONE_BRICK_SLAB) {
+ return false;
+ }
+ if (material == Material.END_STONE_BRICK_STAIRS) {
+ return false;
+ }
+ String name = material.name();
+ if (name.contains("GLASS")) {
+ return true;
+ }
+ if (name.contains("BANNER")) {
+ return true;
+ }
+ if (side && name.contains("CARPET")) {
+ return true;
+ }
+ if (name.contains("DOOR")) {
+ return true;
+ }
+ if (name.contains("SIGN")) {
+ return true;
+ }
+ if (name.contains("FENCE_GATE")) {
+ return true;
+ }
+ if (name.endsWith("_FENCE")) {
+ return true;
+ }
+ if (name.endsWith("_SKULL")) {
+ return true;
+ }
+ if (name.endsWith("_WALL")) {
+ return true;
+ }
+ if (side && name.endsWith("_SLAB")) {
+ return true;
+ }
+ if (name.endsWith("_STAIRS")) {
+ return true;
+ }
+ if (name.endsWith("_CANDLE")) {
+ return true;
+ }
+ if (name.endsWith("_BUTTON")) {
+ return true;
+ }
+ return false;
+ }
+
+ public void show(Player player) {
+ entityServer.addPlayer(player);
+ }
+
+ public void set() {
+ hullBlocks.forEach(point -> {
+ point.toLocation(WORLD).getBlock().setType(Material.END_STONE);
+ });
+ entityServer.getPlayers().forEach(player -> {
+ entityServer.removePlayer(player);
+ });
+ }
+
+ public void hide() {
+ for (int x = minX; x <= maxX; x++) {
+ for (int y = minY; y <= maxY; y++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ if (nonTechHideBlock.contains(new Point(x, y, z))) {
+ continue;
+ }
+ WORLD.getBlockAt(x, y, z).setType(Material.END_STONE);
+ }
+ }
+ }
+ }
+}
diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullCommand.java b/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullCommand.java
new file mode 100644
index 00000000..3ff4cddf
--- /dev/null
+++ b/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullCommand.java
@@ -0,0 +1,67 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.bausystem.features.hullhider;
+
+import de.steamwar.bausystem.region.Region;
+import de.steamwar.command.SWCommand;
+import de.steamwar.linkage.Linked;
+import org.bukkit.entity.Player;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Linked
+public class HullCommand extends SWCommand {
+
+ private Map hullhiderMap = new HashMap<>();
+
+ public HullCommand() {
+ super("hullhider");
+ }
+
+ @Register
+ public void toggle(Player p) {
+ Region region = Region.getRegion(p.getLocation());
+ if (hullhiderMap.containsKey(region)) {
+ Hullhider hullhider = hullhiderMap.get(region);
+ if (hullhider.togglePlayer(p)) {
+ hullhider.close();
+ hullhiderMap.remove(region);
+ }
+ } else {
+ Hullhider hullhider = new Hullhider(region.getMinPointTestblock().getX(), region.getMaxPointTestblock().getX(),
+ region.getMinPointTestblock().getY(), region.getMaxPointTestblock().getY(),
+ region.getMinPointTestblock().getZ(), region.getMaxPointTestblock().getZ());
+ hullhider.init(region.getFloorLevel() == 0);
+ hullhider.togglePlayer(p);
+ hullhiderMap.put(region, hullhider);
+ }
+ }
+
+ @Register("recalc")
+ public void recalc(Player p) {
+ Region region = Region.getRegion(p.getLocation());
+ if (hullhiderMap.containsKey(region)) {
+ Hullhider hullhider = hullhiderMap.get(region);
+ hullhider.close();
+ hullhider.init(region.getFloorLevel() == 0);
+ }
+ }
+}
diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullDir.java b/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullDir.java
new file mode 100644
index 00000000..5503b1a2
--- /dev/null
+++ b/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/HullDir.java
@@ -0,0 +1,51 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.bausystem.features.hullhider;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+@Getter
+public enum HullDir {
+ UP(0, 1, 0, false),
+ DOWN(0, -1, 0, false),
+ NORTH(0, 0, -1, true),
+ SOUTH(0, 0, 1, true),
+ EAST(1, 0, 0, true),
+ WEST(-1, 0, 0, true);
+
+ private final int dx;
+ private final int dy;
+ private final int dz;
+ private final boolean sideWays;
+
+ private static final HullDir[] Y = new HullDir[]{NORTH, SOUTH, EAST, WEST};
+ private static final HullDir[] Z = new HullDir[]{UP, DOWN, EAST, WEST};
+ private static final HullDir[] X = new HullDir[]{UP, DOWN, NORTH, SOUTH};
+
+ public HullDir[] getSurrounding() {
+ return switch (this) {
+ case UP, DOWN -> Y;
+ case NORTH, SOUTH -> Z;
+ case EAST, WEST -> X;
+ };
+ }
+}
diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/Hullhider.java b/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/Hullhider.java
new file mode 100644
index 00000000..42ba6daa
--- /dev/null
+++ b/BauSystem_Main/src/de/steamwar/bausystem/features/hullhider/Hullhider.java
@@ -0,0 +1,452 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.bausystem.features.hullhider;
+
+import de.steamwar.bausystem.BauSystem;
+import de.steamwar.bausystem.region.Point;
+import de.steamwar.bausystem.utils.TickEndEvent;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+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.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.entity.EntityExplodeEvent;
+
+import java.util.*;
+
+public class Hullhider implements Listener {
+
+ // TODO:
+ // - Slab y calc diagonal
+ // - Carpet y calc diagonal upwards only!
+
+ private static final World WORLD = Bukkit.getWorlds().get(0);
+
+ private final int minX;
+ private final int maxX;
+ private final int minY;
+ private final int maxY;
+ private final int minZ;
+ private final int maxZ;
+
+ private final int width;
+ private final int height;
+ private final int depth;
+
+ private final Set players = new HashSet<>();
+
+ private final BitSet airBlocks;
+ private final BitSet hullDirections;
+ private final BitSet invisible;
+
+ public Hullhider(int minX, int maxX, int minY, int maxY, int minZ, int maxZ) {
+ this.minX = minX;
+ this.maxX = maxX;
+ this.minY = minY;
+ this.maxY = maxY;
+ this.minZ = minZ;
+ this.maxZ = maxZ;
+
+ this.width = maxX - minX + 1;
+ this.height = maxY - minY + 1;
+ this.depth = maxZ - minZ + 1;
+
+ this.airBlocks = new BitSet(width * height * depth);
+ this.invisible = new BitSet(width * height * depth);
+ this.hullDirections = new BitSet(width * height * depth * 6);
+ }
+
+ private int toIndex(int x, int y, int z) {
+ return (x - minX) * height * depth + (y - minY) * depth + (z - minZ);
+ }
+
+ private int toIndex(Point point) {
+ return toIndex(point.getX(), point.getY(), point.getZ());
+ }
+
+ public void init(boolean noFloor) {
+ Bukkit.getPluginManager().registerEvents(this, BauSystem.getInstance());
+ for (int x = minX; x <= maxX; x++) {
+ for (int y = minY; y <= maxY; y++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ Point p = new Point(x, y, z);
+ invisible.set(toIndex(p));
+ Block block = p.toLocation(WORLD).getBlock();
+ if (block.isEmpty()) {
+ airBlocks.set(toIndex(p));
+ }
+ }
+ }
+ }
+
+ long time = System.currentTimeMillis();
+ init(minX, minY, minZ, maxX, maxY, minZ, HullDir.SOUTH);
+ init(minX, minY, maxZ, maxX, maxY, maxZ, HullDir.NORTH);
+ init(minX, minY, minZ, minX, maxY, maxZ, HullDir.EAST);
+ init(maxX, minY, minZ, maxX, maxY, maxZ, HullDir.WEST);
+ if (noFloor) {
+ init(minX, minY, minZ, maxX, minY, maxZ, HullDir.UP);
+ }
+ init(minX, maxY, minZ, maxX, maxY, maxZ, HullDir.DOWN);
+ long timeDiff = System.currentTimeMillis() - time;
+ Bukkit.getOnlinePlayers().forEach(player -> {
+ player.sendMessage("Init took: " + timeDiff + "ms");
+ });
+
+ BlockData endStone = Material.END_STONE.createBlockData();
+ for (int x = minX; x <= maxX; x++) {
+ for (int y = minY; y <= maxY; y++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ if (invisible.get(toIndex(x, y, z))) {
+ for (Player player : players) {
+ player.sendBlockChange(new Location(WORLD, x, y, z), endStone);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void init(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, HullDir dir) {
+ Set points = new HashSet<>();
+ for (int x = minX; x <= maxX; x++) {
+ for (int y = minY; y <= maxY; y++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ points.add(new Point(dir.getDx() == 0 ? x : minX - dir.getDx(), dir.getDy() == 0 ? y : minY - dir.getDy(), dir.getDz() == 0 ? z : minZ - dir.getDz()));
+ }
+ }
+ }
+ calc(points, dir);
+ }
+
+ private void calc(Set points, HullDir dir) {
+ while (true) {
+ Set nextLayer = new HashSet<>();
+
+ for (Point point : points) {
+ Point next = point.add(dir.getDx(), dir.getDy(), dir.getDz());
+
+ if (next.getX() >= minX && next.getX() <= maxX && next.getY() >= minY && next.getY() <= maxY && next.getZ() >= minZ && next.getZ() <= maxZ) {
+ invisible.clear(toIndex(next));
+ }
+ if (!empty(next, dir)) {
+ if (next.getX() >= minX && next.getX() <= maxX && next.getY() >= minY && next.getY() <= maxY && next.getZ() >= minZ && next.getZ() <= maxZ) {
+ hullDirections.set(toIndex(next) * 6 + dir.ordinal());
+ }
+
+ for (HullDir hullDir : dir.getSurrounding()) {
+ Point p1 = point.add(hullDir.getDx(), hullDir.getDy(), hullDir.getDz());
+ Point p2 = p1.add(dir.getDx(), dir.getDy(), dir.getDz());
+ if (empty(p1, hullDir) && empty(p2, dir)) {
+ nextLayer.add(p2);
+ }
+ if (p1.getX() >= minX && p1.getX() <= maxX && p1.getY() >= minY && p1.getY() <= maxY && p1.getZ() >= minZ && p1.getZ() <= maxZ) {
+ invisible.clear(toIndex(p1));
+ }
+ if (p2.getX() >= minX && p2.getX() <= maxX && p2.getY() >= minY && p2.getY() <= maxY && p2.getZ() >= minZ && p2.getZ() <= maxZ) {
+ invisible.clear(toIndex(p2));
+ }
+ }
+ } else {
+ nextLayer.add(next);
+
+ for (HullDir hullDir : dir.getSurrounding()) {
+ Point p1 = point.add(hullDir.getDx(), hullDir.getDy(), hullDir.getDz());
+ Point p2 = p1.add(dir.getDx(), dir.getDy(), dir.getDz());
+ if (empty(p2, dir)) {
+ nextLayer.add(p2);
+ }
+ if (p1.getX() >= minX && p1.getX() <= maxX && p1.getY() >= minY && p1.getY() <= maxY && p1.getZ() >= minZ && p1.getZ() <= maxZ) {
+ invisible.clear(toIndex(p1));
+ }
+ if (p2.getX() >= minX && p2.getX() <= maxX && p2.getY() >= minY && p2.getY() <= maxY && p2.getZ() >= minZ && p2.getZ() <= maxZ) {
+ invisible.clear(toIndex(p2));
+ }
+ }
+ }
+ }
+
+ points.clear();
+ for (Point point : nextLayer) {
+ if (point.getX() < minX || point.getX() > maxX) {
+ continue;
+ }
+ if (point.getY() < minY || point.getY() > maxY) {
+ continue;
+ }
+ if (point.getZ() < minZ || point.getZ() > maxZ) {
+ continue;
+ }
+ points.add(point);
+ }
+ if (points.isEmpty()) {
+ break;
+ }
+ }
+ }
+
+ private boolean empty(Point point, HullDir dir) {
+ if (point.getX() >= minX && point.getX() <= maxX && point.getY() >= minY && point.getY() <= maxY && point.getZ() >= minZ && point.getZ() <= maxZ && airBlocks.get(toIndex(point))) {
+ return true;
+ }
+ Block block = point.toLocation(WORLD).getBlock();
+ if (block.getBlockData() instanceof Slab slab && slab.getType() == Slab.Type.DOUBLE) {
+ return false;
+ }
+ Material material = block.getType();
+ if (material.isAir()) {
+ return true;
+ }
+ if (material == Material.WATER) {
+ return true;
+ }
+ if (material == Material.SCAFFOLDING) {
+ return true;
+ }
+ if (material == Material.LADDER) {
+ return true;
+ }
+ if (material == Material.COBWEB) {
+ return true;
+ }
+ if (material == Material.IRON_BARS) {
+ return true;
+ }
+ if (material == Material.PLAYER_HEAD || material == Material.PLAYER_WALL_HEAD) {
+ return true;
+ }
+ if (material == Material.END_ROD) {
+ return true;
+ }
+ if (material == Material.CHAIN) {
+ return true;
+ }
+ if (material == Material.LANTERN) {
+ return true;
+ }
+ if (material == Material.LIGHTNING_ROD) {
+ return true;
+ }
+ if (material == Material.SOUL_LANTERN) {
+ return true;
+ }
+ if (material == Material.VINE) {
+ return true;
+ }
+ if (material == Material.SCULK_VEIN) {
+ return true;
+ }
+ if (material == Material.LEVER) {
+ return true;
+ }
+ if (material == Material.END_STONE_BRICK_WALL) {
+ return false;
+ }
+ if (material == Material.END_STONE_BRICK_SLAB) {
+ return false;
+ }
+ if (material == Material.END_STONE_BRICK_STAIRS) {
+ return false;
+ }
+ String name = material.name();
+ if (name.contains("GLASS")) {
+ return true;
+ }
+ if (name.contains("BANNER")) {
+ return true;
+ }
+ if (dir.isSideWays() && name.contains("CARPET")) {
+ return true;
+ }
+ if (name.contains("DOOR")) {
+ return true;
+ }
+ if (name.contains("SIGN")) {
+ return true;
+ }
+ if (name.contains("FENCE_GATE")) {
+ return true;
+ }
+ if (name.endsWith("_FENCE")) {
+ return true;
+ }
+ if (name.endsWith("_SKULL")) {
+ return true;
+ }
+ if (name.endsWith("_WALL")) {
+ return true;
+ }
+ if (dir.isSideWays() && name.endsWith("_SLAB")) {
+ return true;
+ }
+ if (name.endsWith("_STAIRS")) {
+ return true;
+ }
+ if (name.endsWith("_CANDLE")) {
+ return true;
+ }
+ if (name.endsWith("_BUTTON")) {
+ return true;
+ }
+ return false;
+ }
+
+ public void close() {
+ HandlerList.unregisterAll(this);
+ airBlocks.clear();
+ invisible.clear();
+ hullDirections.clear();
+
+ for (int x = minX; x <= maxX; x++) {
+ for (int y = minY; y <= maxY; y++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ Block block = WORLD.getBlockAt(x, y, z);
+ players.forEach(player -> {
+ player.sendBlockChange(block.getLocation(), block.getBlockData());
+ });
+ }
+ }
+ }
+ }
+
+ public boolean togglePlayer(Player player) {
+ if (players.contains(player)) {
+ players.remove(player);
+
+ for (int x = minX; x <= maxX; x++) {
+ for (int y = minY; y <= maxY; y++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ Block block = WORLD.getBlockAt(x, y, z);
+ player.sendBlockChange(block.getLocation(), block.getBlockData());
+ }
+ }
+ }
+ return players.isEmpty();
+ } else {
+ players.add(player);
+
+ BlockData endStone = Material.END_STONE.createBlockData();
+ for (int x = minX; x <= maxX; x++) {
+ for (int y = minY; y <= maxY; y++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ if (invisible.get(toIndex(x, y, z))) {
+ player.sendBlockChange(new Location(WORLD, x, y, z), endStone);
+ }
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private Set pointsToReshow = new HashSet<>();
+ private Map> changedInTick = new HashMap<>();
+
+ @EventHandler
+ public void onBlockBreak(BlockBreakEvent event) {
+ Point point = new Point(event.getBlock().getX(), event.getBlock().getY(), event.getBlock().getZ());
+ airBlocks.set(toIndex(point));
+ Set hullDirs = new HashSet<>();
+ for (HullDir hullDir : HullDir.values()) {
+ if (hullDirections.get(toIndex(point) * 6 + hullDir.ordinal())) {
+ hullDirs.add(hullDir);
+ }
+ }
+
+ pointsToReshow.add(point);
+ hullDirs.forEach(dir -> {
+ changedInTick.computeIfAbsent(dir, __ -> new HashSet<>()).add(point);
+ });
+ }
+
+ @EventHandler
+ public void onEntityExplode(EntityExplodeEvent event) {
+ for (Block block : event.blockList()) {
+ Point point = new Point(block.getX(), block.getY(), block.getZ());
+ airBlocks.set(toIndex(point));
+ pointsToReshow.add(point);
+ Set hullDirs = new HashSet<>();
+ for (HullDir hullDir : HullDir.values()) {
+ if (hullDirections.get(toIndex(point) * 6 + hullDir.ordinal())) {
+ hullDirs.add(hullDir);
+ }
+ }
+ hullDirs.forEach(dir -> {
+ changedInTick.computeIfAbsent(dir, __ -> new HashSet<>()).add(point);
+ });
+ }
+ }
+
+ @EventHandler
+ public void onTickEnd(TickEndEvent event) {
+ if (!changedInTick.isEmpty()) {
+ BitSet oldInvisible = BitSet.valueOf(invisible.toLongArray());
+ long time = System.currentTimeMillis();
+ changedInTick.forEach((dir, points) -> {
+ calc(points, dir);
+ });
+ long timeDiff = System.currentTimeMillis() - time;
+ players.forEach(player -> {
+ player.sendMessage("Calculated in " + timeDiff + "ms");
+ });
+
+ for (int x = minX; x <= maxX; x++) {
+ for (int y = minY; y <= maxY; y++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ if (invisible.get(toIndex(x, y, z))) continue;
+ if (!oldInvisible.get(toIndex(x, y, z))) continue;
+ Location location = new Location(WORLD, x, y, z);
+ BlockData blockData = location.getBlock().getBlockData();
+ players.forEach(player -> {
+ player.sendBlockChange(location, blockData);
+ });
+ }
+ }
+ }
+ changedInTick.clear();
+ }
+
+ if (!pointsToReshow.isEmpty()) {
+ BlockData endStone = Material.END_STONE.createBlockData();
+ pointsToReshow.forEach(point -> {
+ for (int x = point.getX() - 1; x <= point.getX() + 1; x++) {
+ for (int y = point.getY() - 1; y <= point.getY() + 1; y++) {
+ for (int z = point.getZ() - 1; z <= point.getZ() + 1; z++) {
+ Location location = point.toLocation(WORLD);
+ if (x >= minX && x <= maxX && y >= minY && y <= maxY && z >= minZ && z <= maxZ && invisible.get(toIndex(x, y, z))) {
+ players.forEach(player -> {
+ player.sendBlockChange(location, endStone);
+ });
+ }
+ }
+ }
+ }
+ });
+ pointsToReshow.clear();
+ }
+ }
+}