From 29927985b1fd51e436c685ed989bb5931d039a4a Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 16 Dec 2023 18:21:33 +0100 Subject: [PATCH] =?UTF-8?q?Initial=20directional=20quadrant=2045=C2=B0=20h?= =?UTF-8?q?ullhider=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lixfel --- .../de/steamwar/fightsystem/FightSystem.java | 1 + .../steamwar/fightsystem/utils/HullHider.java | 295 ++++++++++++++++++ .../de/steamwar/fightsystem/utils/Region.java | 12 +- 3 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java 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); }