diff --git a/FightSystem_14/src/de/steamwar/fightsystem/utils/BlockIdWrapper14.java b/FightSystem_14/src/de/steamwar/fightsystem/utils/BlockIdWrapper14.java index 916bfb6..934d9b6 100644 --- a/FightSystem_14/src/de/steamwar/fightsystem/utils/BlockIdWrapper14.java +++ b/FightSystem_14/src/de/steamwar/fightsystem/utils/BlockIdWrapper14.java @@ -21,6 +21,7 @@ package de.steamwar.fightsystem.utils; import com.comphenix.tinyprotocol.Reflection; import de.steamwar.core.Core; +import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; @@ -56,4 +57,11 @@ public class BlockIdWrapper14 implements BlockIdWrapper { getTypeAndData.invoke(nworld, pos, blockData, 1042); flagDirty.invoke(getChunkProvider.invoke(nworld), pos); } + + private static final Reflection.MethodInvoker getMaterialByBlock = Reflection.getTypedMethod(Reflection.getClass("{obc}.util.CraftMagicNumbers"), "getMaterial", Material.class, block); + private static final Reflection.MethodInvoker getBlockByBlockData = Reflection.getTypedMethod(iBlockData, null, block); + @Override + public Material idToMaterial(int blockState) { + return (Material)getMaterialByBlock.invoke(null, getBlockByBlockData.invoke(getByCombinedId.invoke(null, blockState))); + } } diff --git a/FightSystem_8/src/de/steamwar/fightsystem/utils/BlockIdWrapper8.java b/FightSystem_8/src/de/steamwar/fightsystem/utils/BlockIdWrapper8.java index aedd2e8..cbf186a 100644 --- a/FightSystem_8/src/de/steamwar/fightsystem/utils/BlockIdWrapper8.java +++ b/FightSystem_8/src/de/steamwar/fightsystem/utils/BlockIdWrapper8.java @@ -19,6 +19,7 @@ package de.steamwar.fightsystem.utils; +import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; @@ -37,4 +38,13 @@ public class BlockIdWrapper8 implements BlockIdWrapper { world.getBlockAt(x, y, z).setTypeIdAndData(blockState >> 4, (byte)(blockState & 0b1111), false); } + + @Override + @SuppressWarnings("deprecation") + public Material idToMaterial(int blockState) { + if((blockState >> 4) > 256) // Illegal blockstate / corrupted replay + blockState = 0; + + return Material.getMaterial(blockState >> 4); + } } diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/Config.java b/FightSystem_Core/src/de/steamwar/fightsystem/Config.java index 331491f..b1030f7 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/Config.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/Config.java @@ -120,7 +120,7 @@ public class Config { //tech hider parameter public static final boolean TechhiderActive; - public static final Set HiddenBlocks; + public static final Set HiddenBlocks; public static final Set HiddenBlockEntities; public static final String ObfuscateWith; @@ -214,7 +214,7 @@ public class Config { TechhiderActive = config.getBoolean("Techhider.Active", false); ObfuscateWith = config.getString("Techhider.ObfuscateWith", "end_stone").toUpperCase(); - HiddenBlocks = Collections.unmodifiableSet(new HashSet<>(config.getStringList("Techhider.HiddenBlocks"))); + HiddenBlocks = config.getStringList("Techhider.HiddenBlocks").stream().map(String::toUpperCase).map(Material::getMaterial).collect(Collectors.toSet()); HiddenBlockEntities = Collections.unmodifiableSet(new HashSet<>(config.getStringList("Techhider.HiddenBlockEntities"))); if(schemsizeX < 0){ diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index c5adc13..48fe546 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -54,6 +54,7 @@ public class FightSystem extends JavaPlugin { private FightTeam lastWinner; private String lastWinreason; private TechHiderWrapper techHider; + private HullHider hullHider; @Override public void onLoad() { @@ -108,6 +109,7 @@ public class FightSystem extends JavaPlugin { new EnterHandler(); techHider = new TechHiderWrapper(); + hullHider = new HullHider(); new FightWorld(); new FightUI(); new FightStatistics(); @@ -218,6 +220,10 @@ public class FightSystem extends JavaPlugin { return plugin.techHider; } + public static HullHider getHullHider() { + return plugin.hullHider; + } + public static void shutdown() { //Staggered kick to prevent lobby overloading if(Bukkit.getOnlinePlayers().isEmpty()){ diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/commands/GUI.java b/FightSystem_Core/src/de/steamwar/fightsystem/commands/GUI.java index cbb392b..438bd1a 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/commands/GUI.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/commands/GUI.java @@ -19,6 +19,7 @@ package de.steamwar.fightsystem.commands; +import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.*; @@ -189,7 +190,7 @@ public class GUI { return; } - if (type.checkType() != null && type.checkType() != type && !SchematicNode.getAllAccessibleSchematicsOfType(SteamwarUser.get(p.getUniqueId()).getId(), type.checkType().toDB()).isEmpty()) { + if (type.checkType() != null && type.checkType() != type && ArenaMode.AntiEvent.contains(Config.mode) && !SchematicNode.getAllAccessibleSchematicsOfType(SteamwarUser.get(p.getUniqueId()).getId(), type.checkType().toDB()).isEmpty()) { inv.setItem(row * 9 + 4, Material.ANVIL, msg.parse("SCHEM_UNCHECKED", p, type.name()), (ClickType click) -> { p.closeInventory(); schemDialog(p, type, false, true); diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java b/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java index d8c78d9..157c296 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java @@ -56,7 +56,10 @@ public class EnternCountdown extends Countdown { @Override public void countdownFinished() { FightSystem.getMessage().sendPrefixless("ENTERN_ALLOWED", fightPlayer.getEntity(), ChatMessageType.ACTION_BAR); - fightPlayer.ifPlayer(player -> FightSystem.getTechHider().reloadChunks(player, chunkPos, false)); + fightPlayer.ifPlayer(player -> { + FightSystem.getHullHider().updatePlayer(player); + FightSystem.getTechHider().reloadChunks(player, chunkPos, false); + }); } @Override diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java b/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java index 426c66e..93e6e5f 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java @@ -45,7 +45,6 @@ import java.io.IOException; import java.util.List; import java.util.Random; import java.util.logging.Level; -import java.util.stream.Stream; public class FightSchematic extends StateDependent { @@ -149,6 +148,7 @@ public class FightSchematic extends StateDependent { ).add(new Vector(rotate ? 1 : 0, 0, rotate ? 1 : 0)), new AffineTransform().rotateY(rotate ? 180 : 0) ); + FightSystem.getHullHider().initialize(team); Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), freezer::disable, 3); Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), team::teleportToSpawn, 40); diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java b/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java index 54cad8e..7aad62d 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java @@ -275,6 +275,7 @@ public class FightTeam { BountifulWrapper.impl.setAttackSpeed(player); player.setFoodLevel(20); player.getInventory().clear(); + FightSystem.getHullHider().updatePlayer(player); if(FightState.Spectate.contains(FightState.getFightState())) { Fight.setPlayerGamemode(player, GameMode.SPECTATOR); @@ -318,6 +319,7 @@ public class FightTeam { player.getInventory().clear(); if(player.isOnline()){ + FightSystem.getHullHider().updatePlayer(player); FightSystem.getTechHider().reloadChunks(player, chunksToReload, true); if(ArenaMode.VariableTeams.contains(Config.mode)) diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/listener/ArrowStopper.java b/FightSystem_Core/src/de/steamwar/fightsystem/listener/ArrowStopper.java index e7b4d01..3fd7f7d 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/listener/ArrowStopper.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/listener/ArrowStopper.java @@ -85,7 +85,7 @@ public class ArrowStopper { } private boolean checkBlock(Block block) { - return Config.HiddenBlocks.contains(block.getType().name().toLowerCase()); + return Config.HiddenBlocks.contains(block.getType()); } private boolean invalidEntity(Projectile entity) { diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java b/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java index d7c31fd..cb1dddb 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java @@ -88,7 +88,7 @@ public class PacketProcessor implements Listener { private final PacketSource source; private final BukkitTask task; private final LinkedList syncList = new LinkedList<>(); - private final Set hiddenBlockIds = Config.HiddenBlocks.stream().map(String::toUpperCase).map(Material::getMaterial).flatMap(m -> BlockIds.impl.materialToAllIds(m).stream()).collect(Collectors.toSet()); + private final Set hiddenBlockIds = Config.HiddenBlocks.stream().flatMap(m -> BlockIds.impl.materialToAllIds(m).stream()).collect(Collectors.toSet()); private final int obfuscateWith = BlockIds.impl.materialToId(Material.getMaterial(Config.ObfuscateWith.toUpperCase())); private final FreezeWorld freezer = new FreezeWorld(); private final REntityServer entityServer = new REntityServer(); @@ -165,6 +165,11 @@ public class PacketProcessor implements Listener { entityServer.addPlayer(player); } + private void addREntity(int entityId, REntity entity) { + entities.put(entityId, entity); + FightSystem.getHullHider().updateREntity(entity); + } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPlayerJoin(PlayerJoinEvent e) { entityServer.addPlayer(e.getPlayer()); @@ -235,7 +240,7 @@ public class PacketProcessor implements Listener { execSync(() -> { SteamwarUser user = SteamwarUser.get(userId); - entities.put(entityId, new RPlayer(entityServer, user.getUUID(), user.getUserName(), Config.SpecSpawn)); + addREntity(entityId, new RPlayer(entityServer, user.getUUID(), user.getUserName(), Config.SpecSpawn)); team.addEntry(user.getUserName()); }); } @@ -257,8 +262,10 @@ public class PacketProcessor implements Listener { execSync(() -> { REntity entity = entities.get(entityId); - if(entity != null) + if(entity != null) { entity.move(locX, locY, locZ, pitch, yaw, headYaw); + FightSystem.getHullHider().updateREntity(entity); + } }); } @@ -267,8 +274,10 @@ public class PacketProcessor implements Listener { execSync(() -> { REntity entity = entities.remove(entityId); - if(entity != null) + if(entity != null) { + FightSystem.getHullHider().despawnREntity(entity); entity.die(); + } }); } @@ -289,7 +298,7 @@ public class PacketProcessor implements Listener { private void tntSpawn() throws IOException { int entityId = source.readInt(); - execSync(() -> entities.put(entityId, new REntity(entityServer, EntityType.PRIMED_TNT, Config.SpecSpawn))); + execSync(() -> addREntity(entityId, new REntity(entityServer, EntityType.PRIMED_TNT, Config.SpecSpawn))); } private void entityVelocity() throws IOException { @@ -344,13 +353,13 @@ public class PacketProcessor implements Listener { private void arrowSpawn() throws IOException { int entityId = source.readInt(); - execSync(() -> entities.put(entityId, new REntity(entityServer, EntityType.ARROW, Config.SpecSpawn))); + execSync(() -> addREntity(entityId, new REntity(entityServer, EntityType.ARROW, Config.SpecSpawn))); } private void fireballSpawn() throws IOException { int entityId = source.readInt(); - execSync(() -> entities.put(entityId, new REntity(entityServer, EntityType.FIREBALL, Config.SpecSpawn))); + execSync(() -> addREntity(entityId, new REntity(entityServer, EntityType.FIREBALL, Config.SpecSpawn))); } private void send(ChatMessageType type) throws IOException { @@ -437,7 +446,10 @@ public class PacketProcessor implements Listener { if(!Config.ArenaRegion.in2dRegion(x, z)) return; //Outside of the arena - execSync(() -> BlockIdWrapper.impl.setBlock(Config.world, x, y, z, TechHiderWrapper.ENABLED && hiddenBlockIds.contains(blockState) ? obfuscateWith : blockState)); + execSync(() -> { + BlockIdWrapper.impl.setBlock(Config.world, x, y, z, TechHiderWrapper.ENABLED && hiddenBlockIds.contains(blockState) ? obfuscateWith : blockState); + FightSystem.getHullHider().blockUpdate(Config.world.getBlockAt(x, y, z), BlockIdWrapper.impl.idToMaterial(blockState)); + }); } private void particle() throws IOException { @@ -578,6 +590,7 @@ public class PacketProcessor implements Listener { private void endReplay() { HandlerList.unregisterAll(this); entityServer.close(); + entities.values().forEach(FightSystem.getHullHider()::despawnREntity); entities.clear(); freezer.disable(); diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/utils/BlockIdWrapper.java b/FightSystem_Core/src/de/steamwar/fightsystem/utils/BlockIdWrapper.java index 6cda218..399e63d 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/utils/BlockIdWrapper.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/utils/BlockIdWrapper.java @@ -21,12 +21,14 @@ package de.steamwar.fightsystem.utils; import de.steamwar.core.VersionDependent; import de.steamwar.fightsystem.FightSystem; +import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; public interface BlockIdWrapper { BlockIdWrapper impl = VersionDependent.getVersionImpl(FightSystem.getPlugin()); + Material idToMaterial(int blockState); int blockToId(Block block); void setBlock(World world, int x, int y, int z, int blockState); } diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java b/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java new file mode 100644 index 0000000..57cfa29 --- /dev/null +++ b/FightSystem_Core/src/de/steamwar/fightsystem/utils/Hull.java @@ -0,0 +1,348 @@ +/* + * 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.fightsystem.utils; + +import de.steamwar.entity.REntity; +import de.steamwar.fightsystem.Config; +import de.steamwar.fightsystem.FightSystem; +import de.steamwar.fightsystem.fight.FightTeam; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.logging.Level; + +public class Hull { + + private static boolean isOccluding(Material material) { + return material.isOccluding() || Config.HiddenBlocks.contains(material); + } + + private final Region region; + private final boolean groundVisible; + + private final BitSet occluding; + private final BitSet visibility; + private final Map> blockVisibility = new HashMap<>(); + + private final Set players = new HashSet<>(); + private final Set entities = new HashSet<>(); + private final Set rentities = new HashSet<>(); + + public Hull(FightTeam team) { + this.region = team.getSchemRegion(); + this.groundVisible = region.getMinY() != Config.PlayerRegion.getMinY(); + this.occluding = new BitSet(region.volume()); + 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) + }; + } + + // Generate quadrants for each direction + 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); + } + } + + @SuppressWarnings("deprecation") + public void addPlayer(Player player) { + if(players.add(player)) { + for(Entity entity : entities) + player.hideEntity(FightSystem.getPlugin(), entity); + } + } + + @SuppressWarnings("deprecation") + public void removePlayer(Player player, boolean activeRemoval) { + if(players.remove(player) && activeRemoval) { + for(Entity entity : entities) + player.showEntity(FightSystem.getPlugin(), entity); + } + } + + public void checkEntity(Entity entity) { + Location location = entity.getLocation(); + if(region.inRegion(location) && !visibility.get(new IntVector(location).toId(region))) { //TODO more precise + if(entities.add(entity)) { + for(Player player : players) + player.hideEntity(FightSystem.getPlugin(), entity); + } + } else { + if(entities.remove(entity)) { + for(Player player : players) + player.showEntity(FightSystem.getPlugin(), entity); + } + } + } + + public void removeEntity(Entity entity) { + entities.remove(entity); + } + + public void checkREntity(REntity entity) { + Location location = new Location(Config.world, entity.getX(), entity.getY(), entity.getZ()); + if(region.inRegion(location) && !visibility.get(new IntVector(location).toId(region))) { //TODO more precise + if(rentities.add(entity)) + entity.hide(true); + } else { + if(rentities.remove(entity)) + entity.hide(false); + } + } + + public void removeREntity(REntity entity) { + rentities.remove(entity); + } + + public void initialize() { + visibility.clear(); + occluding.clear(); + for (Map direction : blockVisibility.values()) { + for (BitSet set : direction.values()) + set.clear(); + } + + long start = System.currentTimeMillis(); + region.forEach((x, y, z) -> { + IntVector block = new IntVector(x, y, z); + if (isOccluding(Config.world.getBlockAt(x, y, z).getType())) + occluding.set(block.toId(region)); + }); + forEachBorder((root, direction) -> { + for (Map.Entry quadrant : blockVisibility.get(direction).entrySet()) { + checkBlock(new NullList<>(), root, direction, quadrant.getKey(), quadrant.getValue()); + } + }); + FightSystem.getPlugin().getLogger().log(Level.INFO, () -> "[HullHider] initialisation finished: " + (System.currentTimeMillis() - start) + " ms, visible blocks: " + visibility.cardinality()); + } + + @SuppressWarnings("deprecation") + public void updateBlockVisibility(Block b, Material changedType) { + 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; + + List uncovered = new ArrayList<>(); + occluding.clear(id); + for (Map.Entry> direction : blockVisibility.entrySet()) { + for (Map.Entry quadrant : direction.getValue().entrySet()) { + if (quadrant.getValue().get(id)) { + quadrant.getValue().clear(id); + checkBlock(uncovered, root, direction.getKey(), quadrant.getKey(), quadrant.getValue()); + } + } + } + + if(uncovered.isEmpty()) + return; + + Set uncoveredSet = new HashSet<>(uncovered); + Iterator it = entities.iterator(); + while(it.hasNext()) { + Entity entity = it.next(); + if(uncoveredSet.contains(new IntVector(entity.getLocation()))) { //TODO more precise + it.remove(); + for(Player player : players) + player.showEntity(FightSystem.getPlugin(), entity); + } + } + + Iterator rit = rentities.iterator(); + while(rit.hasNext()) { + REntity entity = rit.next(); + if(uncoveredSet.contains(new IntVector(new Location(Config.world, entity.getX(), entity.getY(), entity.getZ())))) { //TODO more precise + it.remove(); + entity.hide(false); + } + } + } + + private 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)); + } + } + } + + private void checkBlock(List uncovered, IntVector block, IntVector direction, IntVector quadrant, BitSet quadVisibility) { + if (block.notInRegion(region)) + return; + + int id = block.toId(region); + if (quadVisibility.get(id)) + return; + + quadVisibility.set(id); + if (!visibility.get(id)) { + visibility.set(id); + uncovered.add(block); + } + + if (occluding.get(id)) + return; + + IntVector neighbour = block.add(direction); + checkBlock(uncovered, neighbour, direction, quadrant, quadVisibility); + boolean neigbourTransparent = boundedNonOccluding(neighbour); + boolean diagonalReachable = false; + if (direction.x == 0 && (neigbourTransparent || boundedNonOccluding(block.add(quadrant.x, 0, 0)))) { + checkBlock(uncovered, neighbour.add(quadrant.x, 0, 0), direction, quadrant, quadVisibility); + diagonalReachable = boundedNonOccluding(neighbour.add(quadrant.x, 0, 0)); + } + + if (direction.y == 0 && (neigbourTransparent || boundedNonOccluding(block.add(0, quadrant.y, 0)))) { + checkBlock(uncovered, neighbour.add(0, quadrant.y, 0), direction, quadrant, quadVisibility); + diagonalReachable = diagonalReachable || boundedNonOccluding(neighbour.add(0, quadrant.y, 0)); + } + + if (direction.z == 0 && (neigbourTransparent || boundedNonOccluding(block.add(0, 0, quadrant.z)))) { + checkBlock(uncovered, neighbour.add(0, 0, quadrant.z), direction, quadrant, quadVisibility); + diagonalReachable = diagonalReachable || boundedNonOccluding(neighbour.add(0, 0, quadrant.z)); + } + + if (diagonalReachable) + checkBlock(uncovered, neighbour.add(quadrant), direction, quadrant, quadVisibility); + } + + private boolean boundedNonOccluding(IntVector block) { + return !(block.notInRegion(region) || occluding.get(block.toId(region))); + } + + + 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 IntVector(Location location) { + this.x = location.getBlockX(); + this.y = location.getBlockY(); + this.z = location.getBlockZ(); + } + + public boolean notInRegion(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 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; + } + } + + private static class NullList extends AbstractList { + @Override + public void add(int index, E element) { + // Straight to /dev/null! + } + + @Override + public E get(int index) { + return null; + } + + @Override + public int size() { + return 0; + } + } +} 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..1d95eef --- /dev/null +++ b/FightSystem_Core/src/de/steamwar/fightsystem/utils/HullHider.java @@ -0,0 +1,147 @@ +/* + 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.core.Core; +import de.steamwar.entity.REntity; +import de.steamwar.fightsystem.fight.Fight; +import de.steamwar.fightsystem.fight.FightPlayer; +import de.steamwar.fightsystem.fight.FightTeam; +import de.steamwar.fightsystem.listener.Recording; +import de.steamwar.fightsystem.states.FightState; +import de.steamwar.fightsystem.states.StateDependent; +import de.steamwar.fightsystem.states.StateDependentListener; +import de.steamwar.fightsystem.states.StateDependentTask; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntitySpawnEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class HullHider implements Listener { + + private static final boolean ENABLED = TechHiderWrapper.ENABLED && Core.getVersion() >= 18; + private final Map hulls = new HashMap<>(); + + public HullHider() { + if(ENABLED) + Fight.teams().forEach(team -> hulls.put(team, new Hull(team))); + + new StateDependentListener(ENABLED, FightState.Schem, this); + new StateDependent(ENABLED, FightState.Schem) { + @Override + public void enable() { + Bukkit.getOnlinePlayers().forEach(HullHider.this::updatePlayer); + } + + @Override + public void disable() { + Bukkit.getOnlinePlayers().forEach(player -> removePlayer(player, true)); + } + }.register(); + new StateDependentTask(ENABLED, FightState.Schem, this::onTick, 0, 1); + } + + public void initialize(FightTeam team) { + if(!ENABLED) + return; + + hulls.get(team).initialize(); + } + + + @EventHandler(priority = EventPriority.HIGH) + public void onJoin(PlayerJoinEvent e) { + updatePlayer(e.getPlayer()); + } + + @EventHandler + public void onLeave(PlayerQuitEvent e) { + removePlayer(e.getPlayer(), false); + } + + public void updatePlayer(Player player) { + if(!ENABLED) + return; + + FightTeam team = Fight.getPlayerTeam(player); + FightPlayer fp = Fight.getFightPlayer(player); + for(Map.Entry hull : hulls.entrySet()) { + if(hull.getKey() == team || (fp != null && fp.canEntern())) { + hull.getValue().removePlayer(player, true); + } else { + hull.getValue().addPlayer(player); + } + } + } + + private void removePlayer(Player player, boolean activeRemoval) { + hulls.values().forEach(hull -> hull.removePlayer(player, activeRemoval)); + } + + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockPhysic(BlockPhysicsEvent e) { + if(FlatteningWrapper.impl.doRecord(e)) + blockUpdate(e.getBlock(), e.getChangedType()); + } + + public void blockUpdate(Block block, Material changedType) { + for (Hull hull : hulls.values()) + hull.updateBlockVisibility(block, changedType); + } + + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onSpawn(EntitySpawnEvent e) { + hulls.values().forEach(hull -> hull.checkEntity(e.getEntity())); + } + + private void onTick() { + Recording.iterateOverEntities(Objects::nonNull, entity -> { + for (Hull hull : hulls.values()) + hull.checkEntity(entity); + }); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onDeath(EntityDeathEvent e) { + hulls.values().forEach(hull -> hull.removeEntity(e.getEntity())); + } + + public void updateREntity(REntity e) { + hulls.values().forEach(hull -> hull.checkREntity(e)); + } + + public void despawnREntity(REntity e) { + hulls.values().forEach(hull -> hull.removeREntity(e)); + } +} 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); } diff --git a/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java b/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java index 5b443da..9638292 100644 --- a/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java +++ b/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java @@ -36,7 +36,6 @@ import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; public class TechHiderWrapper extends StateDependent { @@ -45,7 +44,7 @@ public class TechHiderWrapper extends StateDependent { public TechHiderWrapper() { super(ENABLED, FightState.Schem); - techHider = new TechHider(this::bypass, Material.getMaterial(Config.ObfuscateWith), Config.HiddenBlocks.stream().map(String::toUpperCase).map(Material::getMaterial).collect(Collectors.toSet()), Config.HiddenBlockEntities); + techHider = new TechHider(this::bypass, Material.getMaterial(Config.ObfuscateWith), Config.HiddenBlocks, Config.HiddenBlockEntities); register(); }