Merge pull request 'Hider core' (#519) from HiderCore into main

Reviewed-on: SteamWar/SteamWar#519
Reviewed-by: FessmLP <72+fessmlp@noreply.localhost>
This commit is contained in:
2026-11-01 09:36:00 +01:00
69 changed files with 2385 additions and 1482 deletions
@@ -30,6 +30,7 @@ dependencies {
compileOnly(libs.classindex)
annotationProcessor(libs.classindex)
compileOnly(project(":SpigotCore", "default"))
implementation(project(":HiderCore"))
compileOnly(libs.netty)
compileOnly(libs.paperapi)
@@ -32,8 +32,8 @@ import de.steamwar.fightsystem.record.GlobalRecorder;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.utils.FightHullHider;
import de.steamwar.fightsystem.utils.FightUI;
import de.steamwar.fightsystem.utils.HullHider;
import de.steamwar.fightsystem.utils.SWSound;
import de.steamwar.fightsystem.utils.TechHiderWrapper;
import de.steamwar.linkage.AbstractLinker;
@@ -58,7 +58,7 @@ public class FightSystem extends JavaPlugin {
private FightTeam lastWinner;
private String lastWinreason;
private TechHiderWrapper techHider;
private HullHider hullHider;
private FightHullHider hullHider;
private SpigotLinker spigotLinker;
@@ -116,9 +116,9 @@ public class FightSystem extends JavaPlugin {
new OneShotStateDependent(ArenaMode.Test, FightState.All, SteamEdit::init);
Config.world.setGameRule(GameRule.REDUCED_DEBUG_INFO, ArenaMode.AntiTest.contains(Config.mode));
hullHider = new HullHider();
hullHider = new FightHullHider();
techHider = new TechHiderWrapper(hullHider);
FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true));
hullHider.fillAll(true);
FileSource.startReplay();
@@ -170,7 +170,7 @@ public class FightSystem extends JavaPlugin {
return plugin.techHider;
}
public static HullHider getHullHider() {
public static FightHullHider getHullHider() {
return plugin.hullHider;
}
@@ -56,7 +56,7 @@ public class TechhiderbugCommand extends FightSWCommand {
writer.append('\n');
writer.append("HullHider regions: ");
FightSystem.getHullHider().getHullMap().forEach((t, h) -> writer.append(t.getName()).append(':').append(h.getRegion().toString()).append(' '));
FightSystem.getHullHider().forEachHull((t, h) -> writer.append(t.getName()).append(':').append(h.getRegion().toString()).append(' '));
writer.append('\n');
writer.append("Hidden regions: ");
@@ -84,7 +84,7 @@ public class FightWorld extends StateDependent {
entities.forEach(Entity::remove);
entities.clear();
FightSystem.getHullHider().getHullMap().values().forEach(hull -> hull.fill(true));
FightSystem.getHullHider().fillAll(true);
Config.ArenaRegion.forEachChunk((x, z) -> {
for (Player p : Bukkit.getOnlinePlayers()) {
@@ -20,6 +20,7 @@
package de.steamwar.fightsystem.record;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import de.steamwar.core.BlockIds;
import de.steamwar.core.worldedit.WorldEditWrapper;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
@@ -40,7 +41,6 @@ import de.steamwar.fightsystem.utils.TechHiderWrapper;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team;
import de.steamwar.techhider.BlockIds;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
@@ -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)));
});
}
@@ -30,61 +30,62 @@ import de.steamwar.fightsystem.listener.Recording;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.states.StateDependentTask;
import de.steamwar.hullhider.Hull;
import de.steamwar.hullhider.HullHider;
import de.steamwar.hullhider.Viewers;
import de.steamwar.techhider.ObfuscationConfig;
import lombok.Getter;
import org.bukkit.Bukkit;
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;
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;
import java.util.function.BiConsumer;
public class HullHider implements Listener {
/**
* Fight specific part of the hull hider: one hull per team schematic region, and who may view which hull.
*/
public class FightHullHider {
private final HullHider<FightTeam> hullHider = new HullHider<>(true);
// Shared with the TechHiderWrapper
@Getter
private final Map<FightTeam, Hull> hullMap = new HashMap<>();
private final Hull[] hulls;
private final ObfuscationConfig obfuscation = ObfuscationConfig.of(Config.GameModeConfig.Techhider);
public HullHider() {
if (!TechHiderWrapper.ENABLED) {
hulls = new Hull[0];
return;
}
Fight.teams().forEach(team -> hullMap.put(team, new Hull(team)));
hulls = hullMap.values().toArray(new Hull[0]);
public FightHullHider() {
if (!TechHiderWrapper.ENABLED) return;
Fight.teams().forEach(team -> {
Region region = team.getSchemRegion();
hullHider.addHull(team, new Hull(FightSystem.getPlugin(), Config.world, region.toHullRegion(), region.getMinY() != Config.PlayerRegion.getMinY(), obfuscation, new Viewers.HiddenByDefault()));
});
new StateDependentListener(TechHiderWrapper.ENABLED, FightState.All, new MembershipListener());
new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, this);
new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, hullHider);
new StateDependentTask(TechHiderWrapper.ENABLED, FightState.Schem, this::onTick, 0, 1);
}
public void initialize(FightTeam team) {
if (!TechHiderWrapper.ENABLED) return;
Hull hull = hullMap.get(team);
hull.initialize();
// Chunks encoded while the hull was filled as hidden contain obfuscated blocks that are visible now, but no block update is generated for them
for (Player player : Bukkit.getOnlinePlayers()) {
if (!hull.isViewer(player)) {
FightSystem.getTechHider().reloadChunks(player, team.getExtendRegion(), Region.EMPTY);
}
}
hullHider.initialize(team);
}
public void fill(FightTeam team, boolean visible) {
if (!TechHiderWrapper.ENABLED) return;
hullMap.get(team).fill(visible);
hullHider.fill(team, visible);
}
public void fillAll(boolean visible) {
hullHider.fillAll(visible);
}
public void forEachHull(BiConsumer<FightTeam, Hull> consumer) {
hullHider.forEach(consumer);
}
// Registered in every state: players must be hidden from the hulls by default, no matter when they join.
@@ -96,7 +97,7 @@ public class HullHider implements Listener {
@EventHandler
public void onLeave(PlayerQuitEvent e) {
forgetViewer(e.getPlayer());
hullHider.forgetViewer(e.getPlayer());
}
// A dead player loses the right to see the enemy hull again
@@ -114,82 +115,33 @@ public class HullHider implements Listener {
FightTeam team = Fight.getPlayerTeam(player);
FightPlayer fp = Fight.getFightPlayer(player);
for (Map.Entry<FightTeam, Hull> hull : hullMap.entrySet()) {
if (Config.isReferee(player) || hull.getKey() == team || (fp != null && fp.canEntern())) {
hull.getValue().addViewer(player);
hullHider.forEach((hullTeam, hull) -> {
if (Config.isReferee(player) || hullTeam == team || (fp != null && fp.canEntern())) {
hullHider.addViewer(player, hullTeam);
} else {
hull.getValue().removeViewer(player);
hullHider.removeViewer(player, hullTeam);
}
}
});
}
private void forgetViewer(Player player) {
for (Hull hull : hulls) {
hull.forgetViewer(player);
}
}
@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());
}
}
public void blockUpdate(Block block, Material changedType) {
for (Hull hull : hulls) {
hull.updateBlockVisibility(block, changedType);
}
public void blockUpdate(Block block, BlockData changedData) {
hullHider.blockUpdate(block, changedData);
}
public boolean isBlockHidden(Player player, int x, int y, int z) {
if (!TechHiderWrapper.ENABLED) return false;
for (Hull hull : hulls) {
if (hull.isBlockHidden(player, x, y, z)) {
return true;
}
}
return false;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSpawn(EntitySpawnEvent e) {
for (Hull hull : hulls) {
hull.checkEntity(e.getEntity());
}
return hullHider.isBlockHidden(player, x, y, z);
}
private void onTick() {
Recording.iterateOverEntities(Objects::nonNull, entity -> {
for (Hull hull : hulls) {
hull.checkEntity(entity);
}
});
for (Hull hull : hulls) {
hull.sendUncoveredBlocks();
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onDeath(EntityDeathEvent e) {
for (Hull hull : hulls) {
hull.removeEntity(e.getEntity());
}
Recording.iterateOverEntities(Objects::nonNull, hullHider::checkEntity);
hullHider.sendUncoveredBlocks();
}
public void updateREntity(REntity e) {
for (Hull hull : hulls) {
hull.checkREntity(e);
}
hullHider.updateREntity(e);
}
public void despawnREntity(REntity e) {
for (Hull hull : hulls) {
hull.removeREntity(e);
}
hullHider.despawnREntity(e);
}
}
@@ -1,392 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.fightsystem.utils;
import com.comphenix.tinyprotocol.TinyProtocol;
import com.mojang.authlib.GameProfile;
import de.steamwar.core.ProtocolWrapper;
import de.steamwar.entity.REntity;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.fight.FightTeam;
import it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.block.data.CraftBlockData;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.logging.Level;
import java.util.stream.IntStream;
public class Hull {
private static final IntVector[] NO_BRANCHES = new IntVector[0];
private static boolean isOccluding(Material material) {
return material.isOccluding() || Config.GameModeConfig.Techhider.HiddenBlocks.contains(material);
}
@Getter
private final Region region;
private final boolean groundVisible;
private final IntVector[] directions;
private final BitSet occluding;
private final BitSet visibility;
private final HashMap<IntVector, BitSet> visibilityDirections = new HashMap<>(); // Contains the visible directions of each occluding visible block
private final HashSet<IntVector> uncoveredSurface = new HashSet<>();
// Players that may see the hull contents (viewers). Everybody else is hidden from it (default deny). Concurrent as it is read on netty threads.
private final Set<UUID> viewers = ConcurrentHashMap.newKeySet();
private final HashSet<Entity> entities = new HashSet<>();
private final HashSet<REntity> 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());
directions = IntStream.range(0, 27)
.mapToObj(v -> new IntVector(v % 3 - 1, (v / 3) % 3 - 1, v / 9 - 1))
.toArray(IntVector[]::new);
for (IntVector direction : directions) {
visibilityDirections.put(direction, new BitSet(region.volume()));
}
}
public boolean isBlockHidden(Player player, int x, int y, int z) {
return region.inRegion(x, y, z) && !isViewer(player) && !visibility.get(((y - region.getMinY()) * region.getSizeZ() + (z - region.getMinZ())) * region.getSizeX() + (x - region.getMinX()));
}
public boolean isViewer(Player player) {
return player != null && viewers.contains(player.getUniqueId());
}
/**
* Hides the hull from this player (removes the viewer), also hiding all entities currently inside it.
*/
public void removeViewer(Player player) {
viewers.remove(player.getUniqueId());
for (Entity entity : entities) {
if (!player.equals(entity)) untrackEntity(player, entity);
}
}
/**
* Allows this player to see the hull (adds a viewer).
*/
public void addViewer(Player player) {
if (viewers.add(player.getUniqueId())) {
for (Entity entity : entities) {
trackEntity(player, entity);
}
// techhider triggers block change sending
}
}
public void forgetViewer(Player player) {
viewers.remove(player.getUniqueId());
}
public void checkEntity(Entity entity) {
Location location = entity.getLocation();
if (region.inRegion(location) && !visibility.get(new IntVector(location).toId(region))) {
if (entities.add(entity)) {
for (Player player : Bukkit.getOnlinePlayers()) {
if (!isViewer(player) && !player.equals(entity)) untrackEntity(player, entity);
}
}
} else {
if (entities.remove(entity)) {
for (Player player : Bukkit.getOnlinePlayers()) {
if (!isViewer(player) && !player.equals(entity)) trackEntity(player, entity);
}
}
}
}
public void trackEntity(Player player, Entity entity) {
if (entity instanceof Player) {
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.REMOVE, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE));
}
player.showEntity(FightSystem.getPlugin(), entity);
}
public void untrackEntity(Player player, Entity entity) {
player.hideEntity(FightSystem.getPlugin(), entity);
if (entity instanceof Player) {
TinyProtocol.instance.sendPacket(player, ProtocolWrapper.playerInfoPacketConstructor(ProtocolWrapper.PlayerInfoAction.ADD, new GameProfile(entity.getUniqueId(), entity.getName()), GameMode.CREATIVE));
}
}
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))) {
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 fill(boolean visible) {
visibility.set(0, visibility.size(), visible);
occluding.set(0, occluding.size(), !visible);
uncoveredSurface.clear();
for (BitSet directionalVisibility : visibilityDirections.values()) {
directionalVisibility.set(0, directionalVisibility.size(), visible);
}
}
public void initialize() {
visibility.clear();
occluding.clear();
uncoveredSurface.clear();
for (BitSet directionalVisibility : visibilityDirections.values()) {
directionalVisibility.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) -> uncoverBlocks(new NullList<>(), root, direction));
FightSystem.getPlugin().getLogger().log(Level.INFO, () -> "[HullHider] initialisation finished: " + (System.currentTimeMillis() - start) + " ms, visible blocks: " + visibility.cardinality());
}
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;
occluding.clear(id);
if (!visibility.get(id)) return;
List<IntVector> uncovered = new ArrayList<>();
for (Map.Entry<IntVector, BitSet> directionalVisibility : visibilityDirections.entrySet()) {
if (directionalVisibility.getValue().get(id)) {
directionalVisibility.getValue().clear(id);
uncoverBlocks(uncovered, root, directionalVisibility.getKey());
}
}
uncoveredSurface.addAll(new HashSet<>(uncovered));
uncoveredSurface.remove(root);
}
public void sendUncoveredBlocks() {
Map<IntVector, List<IntVector>> sectionWise = new HashMap<>();
for (IntVector uncovered : uncoveredSurface) {
sectionWise.computeIfAbsent(new IntVector(uncovered.x >> 4, uncovered.y >> 4, uncovered.z >> 4), section -> new ArrayList<>()).add(uncovered);
}
uncoveredSurface.clear();
for (Map.Entry<IntVector, List<IntVector>> entry : sectionWise.entrySet()) {
Object result;
List<IntVector> changes = entry.getValue();
Object[] blockdata = new Object[changes.size()];
for (int i = 0; i < blockdata.length; i++) {
IntVector change = changes.get(i);
blockdata[i] = ((CraftBlockData) Config.world.getBlockData(change.getX(), change.getY(), change.getZ())).getState();
}
if (changes.size() > 1) {
IntVector section = changes.get(0);
section = new IntVector(section.getX() >> 4, section.getY() >> 4, section.getZ() >> 4);
int xOffset = 16 * section.getX();
int yOffset = 16 * section.getY();
int zOffset = 16 * section.getZ();
short[] pos = new short[changes.size()];
for (int i = 0; i < changes.size(); i++) {
IntVector change = changes.get(i);
pos[i] = (short) (((change.getX() - xOffset) << 8) + ((change.getZ() - zOffset) << 4) + (change.getY() - yOffset));
}
result = new ClientboundSectionBlocksUpdatePacket(SectionPos.of(section.getX(), section.getY(), section.getZ()), new Short2ObjectArrayMap<>(pos, blockdata, blockdata.length));
} else {
IntVector pos = changes.get(0);
result = new ClientboundBlockUpdatePacket(new BlockPos(pos.getX(), pos.getY(), pos.getZ()), (BlockState) blockdata[0]);
}
Object packet = result;
Bukkit.getOnlinePlayers().stream().filter(player -> !isViewer(player)).forEach(player -> TinyProtocol.instance.sendPacket(player, packet));
}
}
private void forEachBorder(BiConsumer<IntVector, IntVector> 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 uncoverBlocks(List<IntVector> 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
);
}
private void uncoverBlocks(List<IntVector> uncovered, IntVector block, IntVector direction, BitSet directionalVisibility, IntVector[] branchDirections) {
if (block.notInRegion(region)) return;
int id = block.toId(region);
if (directionalVisibility.get(id)) return;
directionalVisibility.set(id);
if (!visibility.get(id)) {
visibility.set(id);
uncovered.add(block);
}
if (occluding.get(id)) return;
uncoverBlocks(uncovered, block.add(direction), direction, directionalVisibility, branchDirections);
for (IntVector branchDirection : branchDirections) {
uncoverBlocks(uncovered, block.add(branchDirection), branchDirection, visibilityDirections.get(branchDirection), NO_BRANCHES);
}
}
@Getter
@AllArgsConstructor
public static class IntVector {
private final int x;
private final int y;
private final int 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);
}
public int sqLength() {
return x * x + y * y + z * 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<E> extends AbstractList<E> {
@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;
}
}
}
@@ -19,7 +19,8 @@
package de.steamwar.fightsystem.utils;
import de.steamwar.techhider.ProtocolUtils;
import de.steamwar.core.ProtocolUtils;
import de.steamwar.hullhider.HullRegion;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.bukkit.Location;
@@ -155,6 +156,10 @@ public class Region {
return in2dRegion(x, z) && minY <= y && y < maxY;
}
public HullRegion toHullRegion() {
return new HullRegion(minX, minY, minZ, maxX, maxY, maxZ);
}
public Region intersection(Region other) {
int x = Math.max(minX, other.minX);
int y = Math.max(minY, other.minY);
@@ -32,32 +32,21 @@ import de.steamwar.fightsystem.states.StateDependent;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.techhider.AccessPrivilegeProvider;
import de.steamwar.techhider.AccessPrivilegeProvider.ForPlayer;
import de.steamwar.techhider.ObfuscationConfig;
import de.steamwar.techhider.TechHider;
import lombok.Getter;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TechHiderWrapper extends StateDependent implements Listener {
@@ -67,9 +56,9 @@ public class TechHiderWrapper extends StateDependent implements Listener {
@Getter
private final ConcurrentHashMap<Player, Region[]> hiddenRegion = new ConcurrentHashMap<>();
private final HullHider hullHider;
private final FightHullHider hullHider;
public TechHiderWrapper(HullHider hullHider) {
public TechHiderWrapper(FightHullHider hullHider) {
super(ENABLED, FightState.All);
this.hullHider = hullHider;
@@ -80,29 +69,14 @@ public class TechHiderWrapper extends StateDependent implements Listener {
@Override
public void enable() {
Set<BlockState> blockStatesToObfuscate = getBlockStatesToHideFromMaterials(Config.GameModeConfig.Techhider.HiddenBlocks);
Set<Block> blocksToObfuscate = Config.GameModeConfig.Techhider.HiddenBlocks.stream()
.map(CraftMagicNumbers::getBlock)
.collect(Collectors.toUnmodifiableSet());
Set<BlockEntityType<?>> blockEntityTypeToObfuscate = Config.GameModeConfig.Techhider.HiddenBlockEntities.stream()
.map(id -> {
ResourceLocation loc = ResourceLocation.parse(id);
return BuiltInRegistries.BLOCK_ENTITY_TYPE.get(loc).orElse(null);
})
.filter(Objects::nonNull)
.map(Holder.Reference::value)
.collect(Collectors.toUnmodifiableSet());
new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith), newProvider(blockStatesToObfuscate, blocksToObfuscate, blockEntityTypeToObfuscate));
new TechHider(newProvider(hullHider.getObfuscation()));
}
private AccessPrivilegeProvider newProvider(Set<BlockState> blockStatesToObfuscate, Set<Block> blocksToObfuscate, Set<BlockEntityType<?>> blockEntityTypeToObfuscate) {
private AccessPrivilegeProvider newProvider(ObfuscationConfig obfuscation) {
return new AccessPrivilegeProvider() {
@Override
public ForPlayer forPlayer(Player p) {
return new FightPlayerAccess(p, getHiddenRegions(p), blockStatesToObfuscate, blocksToObfuscate, blockEntityTypeToObfuscate);
return new FightPlayerAccess(p, getHiddenRegions(p), obfuscation);
}
@Override
@@ -118,16 +92,12 @@ public class TechHiderWrapper extends StateDependent implements Listener {
private class FightPlayerAccess implements ForPlayer {
private final Player player;
private final Region[] hiddenRegions;
private final Set<BlockState> blockStatesToObfuscate;
private final Set<Block> blocksToObfuscate;
private final Set<BlockEntityType<?>> blockEntityTypeToObfuscate;
private final ObfuscationConfig obfuscation;
private FightPlayerAccess(Player player, Region[] hiddenRegions, Set<BlockState> blockStatesToObfuscate, Set<Block> blocksToObfuscate, Set<BlockEntityType<?>> blockEntityTypeToObfuscate) {
private FightPlayerAccess(Player player, Region[] hiddenRegions, ObfuscationConfig obfuscation) {
this.player = player;
this.hiddenRegions = hiddenRegions;
this.blockStatesToObfuscate = blockStatesToObfuscate;
this.blocksToObfuscate = blocksToObfuscate;
this.blockEntityTypeToObfuscate = blockEntityTypeToObfuscate;
this.obfuscation = obfuscation;
}
private boolean isHidden(int blockX, int blockY, int blockZ) {
@@ -155,12 +125,12 @@ public class TechHiderWrapper extends StateDependent implements Listener {
@Override
public boolean canAccessBlock(int blockX, int blockY, int blockZ, Block block) {
return !isHidden(blockX, blockY, blockZ) || !blocksToObfuscate.contains(block);
return !isHidden(blockX, blockY, blockZ) || !obfuscation.hidesBlock(block);
}
@Override
public boolean canAccessBlockState(int blockX, int blockY, int blockZ, BlockState blockState) {
return !isHidden(blockX, blockY, blockZ) || !blockStatesToObfuscate.contains(blockState);
return !isHidden(blockX, blockY, blockZ) || !obfuscation.hidesBlockState(blockState);
}
// TODO will require entity tracking on the netty thread to prevent future race conditions
@@ -177,7 +147,12 @@ public class TechHiderWrapper extends StateDependent implements Listener {
@Override
public boolean canAccessBlockEntity(int blockX, int blockY, int blockZ, BlockEntityType<?> type) {
return !isHidden(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type);
return !isHidden(blockX, blockY, blockZ) || !obfuscation.hidesBlockEntity(type);
}
@Override
public BlockState getObfuscation(int blockX, int blockY, int blockZ) {
return obfuscation.getObfuscationState();
}
}
@@ -235,27 +210,4 @@ public class TechHiderWrapper extends StateDependent implements Listener {
return new Region[]{Fight.getOpposite(fightPlayer.getTeam()).getExtendRegion()};
}
private Stream<BlockState> getWaterloggedBlockStates() {
FluidState waterFluidState = Fluids.WATER.getSource(false);
return BuiltInRegistries.BLOCK.stream()
.map((block) -> block.getStateDefinition().getPossibleStates())
.flatMap(Collection::stream)
.filter((blockState -> blockState.getFluidState() == waterFluidState));
}
private Set<BlockState> getBlockStatesToHideFromMaterials(Set<Material> materials) {
Stream<BlockState> allStatesFromMaterials = materials.stream()
.map(CraftMagicNumbers::getBlock)
.map((block) -> block.getStateDefinition().getPossibleStates())
.flatMap(Collection::stream);
if(materials.contains(Material.WATER)) {
return Stream.concat(allStatesFromMaterials, getWaterloggedBlockStates()).collect(Collectors.toUnmodifiableSet());
}
else {
return allStatesFromMaterials.collect(Collectors.toUnmodifiableSet());
}
}
}