diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java
index c5adc13..fcbafbc 100644
--- a/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java
+++ b/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java
@@ -108,6 +108,7 @@ public class FightSystem extends JavaPlugin {
new EnterHandler();
techHider = new TechHiderWrapper();
+ new HullHider();
new FightWorld();
new FightUI();
new FightStatistics();
diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java b/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java
new file mode 100644
index 0000000..08ae9c7
--- /dev/null
+++ b/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java
@@ -0,0 +1,295 @@
+/*
+ This file is a part of the SteamWar software.
+
+ Copyright (C) 2022 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.fightsystem.utils;
+
+import de.steamwar.fightsystem.ArenaMode;
+import de.steamwar.fightsystem.Config;
+import de.steamwar.fightsystem.FightSystem;
+import de.steamwar.fightsystem.fight.Fight;
+import de.steamwar.fightsystem.fight.FightTeam;
+import de.steamwar.fightsystem.states.FightState;
+import de.steamwar.fightsystem.states.OneShotStateDependent;
+import de.steamwar.fightsystem.states.StateDependentListener;
+import de.steamwar.techhider.BlockIds;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockPhysicsEvent;
+
+import java.util.*;
+import java.util.function.BiConsumer;
+
+public class HullHider implements Listener {
+
+ private final Hull blue = new Hull(Fight.getBlueTeam());
+ private final Hull red = new Hull(Fight.getRedTeam());
+
+ //SpawnPackets: PacketPlayOutSpawnEntity, PacketPlayOutSpawnEntityWeather, PacketPlayOutSpawnEntityLiving, PacketPlayOutSpawnEntityPainting, PacketPlayOutSpawnEntityPlayer
+ //One-timePackets: PacketPlayOutEntityAnimation, PacketPlayOutBlockBreakAnimation, PacketPlayOutEntityStatus, PacketPlayOutEntityPosition, PacketPlayOutEntityPositionAndRotation, PacketPlayOutEntityRotation, PacketPlayOutEntityMovement, EntityHeadLook, EntitySoundEffect, CollectItem, EntityTeleport,
+ //Permanent: EntityMetadata, AttachEntity, EntityEquipment, SetPassengers, EntityProperties, EntityEffect, RemoveEntityEffect
+ //Other: Effect, Particle, Explosion
+ //Death: DestroyEntities
+ public HullHider() {
+ //TODO player enters/leaves team
+ new OneShotStateDependent(ArenaMode.AntiTest, FightState.Schem, () -> Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> {
+ blue.newSchem();
+ red.newSchem();
+ }, 1)); //TODO better paste timer, SYNC PASTE
+ new StateDependentListener(ArenaMode.AntiTest, FightState.Schem, this);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlockPhysic(BlockPhysicsEvent e) {
+ Block b = e.getBlock();
+ //TODO adapt to 1.8 etc., Look out for Occluding->NotOccluding change
+ if(e.getSourceBlock() == b && b.getType().isOccluding() && !e.getChangedType().isOccluding()) {
+ if(blue.region.inRegion(b)) {
+ blue.updateBlockVisibility(new IntVector(b.getX(), b.getY(), b.getZ()));
+ blue.printDebug(b.getX(), b.getY(), b.getZ());
+ }
+ if(red.region.inRegion(b)) {
+ red.updateBlockVisibility(new IntVector(b.getX(), b.getY(), b.getZ()));
+ red.printDebug(b.getX(), b.getY(), b.getZ());
+ }
+ }
+ }
+
+ private static class Hull {
+ private final Region region;
+
+ private final BitSet visibility;
+ private final Map> blockVisibility = new HashMap<>();
+
+ private final boolean groundVisible;
+
+ public Hull(FightTeam team) {
+ this.region = team.getSchemRegion();
+ this.groundVisible = region.getMinY() != Config.PlayerRegion.getMinY();
+ this.visibility = new BitSet(region.volume());
+
+ IntVector[] directions;
+ if(groundVisible) {
+ directions = new IntVector[] {
+ new IntVector(1, 0, 0),
+ new IntVector(-1, 0, 0),
+ new IntVector(0, 1, 0),
+ new IntVector(0, -1, 0),
+ new IntVector(0, 0, 1),
+ new IntVector(0, 0, -1)
+ };
+ } else {
+ directions = new IntVector[] {
+ new IntVector(1, 0, 0),
+ new IntVector(-1, 0, 0),
+ new IntVector(0, -1, 0),
+ new IntVector(0, 0, 1),
+ new IntVector(0, 0, -1)
+ };
+ }
+
+ for(IntVector direction : directions) {
+ Map map = new HashMap<>();
+ for(int z = (direction.z == 0 ? -1 : 0); z <= 1; z+=2) {
+ for(int y = (direction.y == 0 ? -1 : 0); y <= 1; y+=2) {
+ for(int x = (direction.x == 0 ? -1 : 0); x <= 1; x+=2) {
+ map.put(new IntVector(x, y, z), new BitSet(region.volume()));
+ }
+ }
+ }
+ blockVisibility.put(direction, map);
+ }
+ }
+
+ private final int air = BlockIds.impl.materialToId(Material.AIR);
+ private final int stone = BlockIds.impl.materialToId(Material.STONE);
+ private final int red = BlockIds.impl.materialToId(Material.RED_CONCRETE);
+ public void printDebug(int x, int y, int z) {
+ int id = coordToId(x, y, z);
+
+ BlockIdWrapper.impl.setBlock(Config.world, x, y + Config.BlueExtendRegion.getSizeY(), z, visibility.get(id) ? (new IntVector(x, y, z).isOccluding() ? red : air) : stone);
+ }
+
+ public void forEachBorder(BiConsumer f) {
+ for(int x = region.getMinX(); x < region.getMaxX(); x++) {
+ for(int z = region.getMinZ(); z < region.getMaxZ(); z++) {
+ if(groundVisible)
+ f.accept(new IntVector(x, region.getMinY(), z), new IntVector(0, 1, 0));
+ f.accept(new IntVector(x, region.getMaxY()-1, z), new IntVector(0, -1, 0));
+ }
+ }
+
+ for(int x = region.getMinX(); x < region.getMaxX(); x++) {
+ for(int y = region.getMinY(); y < region.getMaxY(); y++) {
+ f.accept(new IntVector(x, y, region.getMinZ()), new IntVector(0, 0, 1));
+ f.accept(new IntVector(x, y, region.getMaxZ()-1), new IntVector(0, 0, -1));
+ }
+ }
+
+ for(int z = region.getMinZ(); z < region.getMaxZ(); z++) {
+ for(int y = region.getMinY(); y < region.getMaxY(); y++) {
+ f.accept(new IntVector(region.getMinX(), y, z), new IntVector(1, 0, 0));
+ f.accept(new IntVector(region.getMaxX()-1, y, z), new IntVector(-1, 0, 0));
+ }
+ }
+ }
+
+ public void newSchem() {
+ visibility.clear();
+ for(Map direction : blockVisibility.values()) {
+ for(BitSet set : direction.values())
+ set.clear();
+ }
+
+ long start = System.currentTimeMillis();
+ forEachBorder(this::sideInit);
+ System.out.println("finish " + (System.currentTimeMillis() - start) + " ms " + visibility.stream().count());
+
+ region.forEach(this::printDebug);
+ }
+
+ private void sideInit(IntVector root, IntVector direction) {
+ for(Map.Entry quadrant : blockVisibility.get(direction).entrySet()) {
+ checkBlock(root, direction, quadrant.getKey(), quadrant.getValue());
+ }
+ }
+
+ private void checkBlock(IntVector root, IntVector direction, IntVector quadrant, BitSet quadVisibility) {
+ Set checkSet = new HashSet<>();
+ checkSet.add(root);
+
+ while(!checkSet.isEmpty()) {
+ Set nextSet = new HashSet<>();
+
+ for(IntVector block : checkSet) {
+ if(!block.inRegion(region))
+ continue;
+
+ int id = block.toId(region);
+ if(quadVisibility.get(id))
+ continue;
+
+ quadVisibility.set(id);
+ visibility.set(id);
+ if(block.isOccluding())
+ continue;
+
+ IntVector neighbour = block.add(direction);
+ nextSet.add(neighbour);
+ boolean neigbourTransparent = !neighbour.isOccluding();
+ if(direction.x == 0 && (neigbourTransparent || !block.add(quadrant.x, 0, 0).isOccluding())) {
+ nextSet.add(neighbour.add(quadrant.x, 0, 0));
+ if(!neighbour.add(quadrant.x, 0, 0).isOccluding())
+ nextSet.add(neighbour.add(quadrant));
+ }
+
+ if(direction.y == 0 && (neigbourTransparent || !block.add(0, quadrant.y, 0).isOccluding())){
+ nextSet.add(neighbour.add(0, quadrant.y, 0));
+ if(!neighbour.add(0, quadrant.y, 0).isOccluding())
+ nextSet.add(neighbour.add(quadrant));
+ }
+
+ if(direction.z == 0 && (neigbourTransparent || !block.add(0, 0, quadrant.z).isOccluding())) {
+ nextSet.add(neighbour.add(0, 0, quadrant.z));
+ if(!neighbour.add(0, 0, quadrant.z).isOccluding())
+ nextSet.add(neighbour.add(quadrant));
+ }
+ }
+
+ checkSet = nextSet;
+ }
+ }
+
+ private void updateBlockVisibility(IntVector root) {
+ if(!root.inRegion(region))
+ return;
+
+ int id = root.toId(region);
+ for(Map.Entry> direction : blockVisibility.entrySet()) {
+ for(Map.Entry quadrant : direction.getValue().entrySet()) {
+ if(quadrant.getValue().get(id)) {
+ quadrant.getValue().clear(id);
+ checkBlock(root, direction.getKey(), quadrant.getKey(), quadrant.getValue());
+ }
+ }
+ }
+ }
+
+ private int coordToId(int x, int y, int z) {
+ return regionCoordToId(x - region.getMinX(), y - region.getMinY(), z - region.getMinZ());
+ }
+ private int regionCoordToId(int x, int y, int z) {
+ return (y * region.getSizeZ() + z) * region.getSizeX() + x;
+ }
+ }
+
+ private static class IntVector {
+ private final int x;
+ private final int y;
+ private final int z;
+
+ public IntVector(int x, int y, int z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public boolean inRegion(Region region) {
+ return region.inRegion(x, y, z);
+ }
+
+ public int toId(Region region) {
+ return ((y - region.getMinY()) * region.getSizeZ() + (z - region.getMinZ())) * region.getSizeX() + (x - region.getMinX());
+ }
+
+ public boolean isOccluding() {
+ return Config.world.getBlockAt(x, y, z).getType().isOccluding(); //TODO techhiderblocks?
+ }
+
+ public IntVector add(int x, int y, int z) {
+ return new IntVector(this.x + x, this.y + y, this.z + z);
+ }
+
+ public IntVector add(IntVector v) {
+ return add(v.x, v.y, v.z);
+ }
+
+ @Override
+ public int hashCode() {
+ return y << 24 ^ x << 12 ^ z;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if(o == null || this.getClass() != o.getClass())
+ return false;
+
+ IntVector v = (IntVector) o;
+ return x == v.x && y == v.y && z == v.z;
+ }
+
+ @Override
+ public String toString() {
+ return x + "," + y + "," + z;
+ }
+ }
+}
diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java b/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java
index 6789d14..1308f40 100644
--- a/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java
+++ b/FightSystem_Core/src/de/steamwar/fightsystem/utils/Region.java
@@ -76,6 +76,10 @@ public class Region {
return maxX - minX;
}
+ public int getSizeY() {
+ return maxY - minY;
+ }
+
public int getSizeZ() {
return maxZ - minZ;
}
@@ -110,7 +114,7 @@ public class Region {
public void forEach(TriConsumer executor) {
for(int x = minX; x < maxX; x++) {
for(int y = minY; y < maxY; y++) {
- for (int z = minZ; z <= maxZ; z++) {
+ for (int z = minZ; z < maxZ; z++) {
executor.accept(x, y, z);
}
}
@@ -146,13 +150,17 @@ public class Region {
}
public boolean in2dRegion(int x, int z) {
- return minX <= x && x < maxX && minZ <= z && z <= maxZ;
+ return minX <= x && x < maxX && minZ <= z && z < maxZ;
}
public boolean inRegion(Block block){
return in2dRegion(block) && minY <= block.getY() && block.getY() < maxY;
}
+ public boolean inRegion(int x, int y, int z) {
+ return in2dRegion(x, z) && minY <= y && y < maxY;
+ }
+
public interface TriConsumer{
void accept(T x, V y, U z);
}