Merge pull request 'Hull hider fixes' (#517) from HullHiderFixes into main

Reviewed-on: SteamWar/SteamWar#517
This commit is contained in:
2026-10-31 12:53:16 +01:00
9 changed files with 302 additions and 185 deletions
@@ -34,9 +34,11 @@ import org.bukkit.util.Vector;
import java.io.File;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.stream.Collectors;
public class Config {
@@ -80,7 +82,7 @@ public class Config {
//event parameter
public static final EventFight EventKampf;
private static final Set<Integer> Referees;
private static final Set<UUID> Referees;
public static final int EventTeamBlueID;
public static final int EventTeamRedID;
public static final boolean BothTeamsPublic;
@@ -237,7 +239,11 @@ public class Config {
BothTeamsPublic = EventTeamRedID == 0 && EventTeamBlueID == 0;
SpectatePort = EventKampf.getSpectatePort() == null ? 0 : EventKampf.getSpectatePort();
LiveReplay = SpectatePort != 0;
Referees = Referee.get(Config.EventKampf.getEventID());
Referees = Referee.get(Config.EventKampf.getEventID()).stream()
.map(SteamwarUser::byId)
.filter(Objects::nonNull)
.map(SteamwarUser::getUUID)
.collect(Collectors.toUnmodifiableSet());
Event event = Event.byId(EventKampf.getEventID());
if (BothTeamsPublic) {
@@ -305,6 +311,6 @@ public class Config {
}
public static boolean isReferee(Player player) {
return Referees.contains(SteamwarUser.get(player.getUniqueId()).getId());
return Referees.contains(player.getUniqueId());
}
}
@@ -60,7 +60,7 @@ public class TechhiderbugCommand extends FightSWCommand {
writer.append('\n');
writer.append("Hidden regions: ");
FightSystem.getTechHider().getHiddenRegion().forEach((p, r) -> writer.append(p.getName()).append(':').append(r.toString()).append(' '));
FightSystem.getTechHider().getHiddenRegion().forEach((p, r) -> writer.append(p.getName()).append(':').append(Arrays.toString(r)).append(' '));
writer.append('\n');
writer.append("TinyProtocol: ");
@@ -69,8 +69,8 @@ public class FightPlayer {
public void setOut() {
isOut = true;
stopEnternCountdown(); // before the event, so listeners already see canEntern() == false
Bukkit.getPluginManager().callEvent(new TeamDeathEvent(this));
stopEnternCountdown();
ifAI(AI::stop);
}
@@ -34,6 +34,7 @@ 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;
@@ -43,6 +44,7 @@ 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;
@@ -65,7 +67,8 @@ public class Hull {
private final HashMap<IntVector, BitSet> visibilityDirections = new HashMap<>(); // Contains the visible directions of each occluding visible block
private final HashSet<IntVector> uncoveredSurface = new HashSet<>();
private final HashSet<Player> players = 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<>();
@@ -75,10 +78,8 @@ public class Hull {
this.occluding = new BitSet(region.volume());
this.visibility = new BitSet(region.volume());
IntVector primaryDirection = new IntVector(0, 0, team.isBlue() == (Config.BlueToRedZ > 0) ? -1 : 1);
directions = IntStream.range(0, 27)
.mapToObj(v -> new IntVector(v % 3 - 1, (v / 3) % 3 - 1, v / 9 - 1))
.filter(v -> v.sqLength() == 1 || v.x * primaryDirection.x + v.y * primaryDirection.y + v.z * primaryDirection.z == 1) // Not pointing away from primary direction
.toArray(IntVector[]::new);
for (IntVector direction : directions) {
@@ -86,28 +87,29 @@ public class Hull {
}
}
public boolean blockPrecise(Player player, int chunkX, int chunkY, int chunkZ) {
return players.contains(player) && !region.chunkSectionOutside(chunkX, chunkY, chunkZ);
}
public boolean isBlockHidden(Player player, int x, int y, int z) {
return region.inRegion(x, y, z) && players.contains(player) && !visibility.get(((y - region.getMinY()) * region.getSizeZ() + (z - region.getMinZ())) * region.getSizeX() + (x - region.getMinX()));
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 isLocationHidden(Player player, Location location) {
return players.contains(player) && region.inRegion(location) && !visibility.get(new IntVector(location).toId(region));
public boolean isViewer(Player player) {
return player != null && viewers.contains(player.getUniqueId());
}
public void addPlayer(Player player) {
if (players.add(player)) {
for (Entity entity : entities) {
untrackEntity(player, entity);
}
/**
* 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);
}
}
public void removePlayer(Player player, boolean activeRemoval) {
if (players.remove(player) && activeRemoval) {
/**
* 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);
}
@@ -115,18 +117,22 @@ public class Hull {
}
}
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 : players) {
untrackEntity(player, entity);
for (Player player : Bukkit.getOnlinePlayers()) {
if (!isViewer(player) && !player.equals(entity)) untrackEntity(player, entity);
}
}
} else {
if (entities.remove(entity)) {
for (Player player : players) {
trackEntity(player, entity);
for (Player player : Bukkit.getOnlinePlayers()) {
if (!isViewer(player) && !player.equals(entity)) trackEntity(player, entity);
}
}
}
@@ -252,7 +258,7 @@ public class Hull {
result = new ClientboundBlockUpdatePacket(new BlockPos(pos.getX(), pos.getY(), pos.getZ()), (BlockState) blockdata[0]);
}
Object packet = result;
players.forEach(player -> TinyProtocol.instance.sendPacket(player, packet));
Bukkit.getOnlinePlayers().stream().filter(player -> !isViewer(player)).forEach(player -> TinyProtocol.instance.sendPacket(player, packet));
}
}
@@ -21,6 +21,8 @@ package de.steamwar.fightsystem.utils;
import de.steamwar.entity.REntity;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.events.TeamDeathEvent;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightPlayer;
import de.steamwar.fightsystem.fight.FightTeam;
@@ -29,6 +31,7 @@ import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.states.StateDependentTask;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
@@ -61,13 +64,22 @@ public class HullHider implements Listener {
hulls = hullMap.values().toArray(new Hull[0]);
new StateDependentListener(TechHiderWrapper.ENABLED, FightState.All, new MembershipListener());
new StateDependentListener(TechHiderWrapper.ENABLED, FightState.Schem, this);
new StateDependentTask(TechHiderWrapper.ENABLED, FightState.Schem, this::onTick, 0, 1);
}
public void initialize(FightTeam team) {
if (!TechHiderWrapper.ENABLED) return;
hullMap.get(team).initialize();
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);
}
}
}
public void fill(FightTeam team, boolean visible) {
@@ -75,14 +87,26 @@ public class HullHider implements Listener {
hullMap.get(team).fill(visible);
}
@EventHandler(priority = EventPriority.HIGH)
public void onJoin(PlayerJoinEvent e) {
updatePlayer(e.getPlayer());
}
// Registered in every state: players must be hidden from the hulls by default, no matter when they join.
private class MembershipListener implements Listener {
@EventHandler(priority = EventPriority.HIGH)
public void onJoin(PlayerJoinEvent e) {
updatePlayer(e.getPlayer());
}
@EventHandler
public void onLeave(PlayerQuitEvent e) {
removePlayer(e.getPlayer(), false);
@EventHandler
public void onLeave(PlayerQuitEvent e) {
forgetViewer(e.getPlayer());
}
// A dead player loses the right to see the enemy hull again
@EventHandler
public void onTeamDeath(TeamDeathEvent e) {
e.getFightPlayer().ifPlayer(player -> {
updatePlayer(player);
FightSystem.getTechHider().reloadChunks(player, Fight.getOpposite(e.getFightPlayer().getTeam()).getExtendRegion(), Region.EMPTY);
});
}
}
public void updatePlayer(Player player) {
@@ -92,16 +116,16 @@ public class HullHider implements Listener {
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().removePlayer(player, true);
hull.getValue().addViewer(player);
} else {
hull.getValue().addPlayer(player);
hull.getValue().removeViewer(player);
}
}
}
private void removePlayer(Player player, boolean activeRemoval) {
private void forgetViewer(Player player) {
for (Hull hull : hulls) {
hull.removePlayer(player, activeRemoval);
hull.forgetViewer(player);
}
}
@@ -25,11 +25,13 @@ import de.steamwar.fightsystem.events.BoardingEvent;
import de.steamwar.fightsystem.events.TeamLeaveEvent;
import de.steamwar.fightsystem.events.TeamSpawnEvent;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightPlayer;
import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.states.FightState;
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.TechHider;
import lombok.Getter;
import net.minecraft.core.Holder;
@@ -49,6 +51,7 @@ 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;
@@ -60,8 +63,10 @@ public class TechHiderWrapper extends StateDependent implements Listener {
public static final boolean ENABLED = !Config.OnlyPublicSchematics && !Config.test() && Config.GameModeConfig.Techhider.Active;
private static final Region[] NO_REGIONS = new Region[0];
@Getter
private final ConcurrentHashMap<Player, Region> hiddenRegion = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Player, Region[]> hiddenRegion = new ConcurrentHashMap<>();
private final HullHider hullHider;
public TechHiderWrapper(HullHider hullHider) {
@@ -90,49 +95,90 @@ public class TechHiderWrapper extends StateDependent implements Listener {
.map(Holder.Reference::value)
.collect(Collectors.toUnmodifiableSet());
new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith), new AccessPrivilegeProvider() {
@Override
public boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ) {
return Fight.teams().stream().map(FightTeam::getExtendRegion).allMatch(region -> region.chunkOutside(chunkX, chunkZ));
}
new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith), newProvider(blockStatesToObfuscate, blocksToObfuscate, blockEntityTypeToObfuscate));
}
private AccessPrivilegeProvider newProvider(Set<BlockState> blockStatesToObfuscate, Set<Block> blocksToObfuscate, Set<BlockEntityType<?>> blockEntityTypeToObfuscate) {
return new AccessPrivilegeProvider() {
@Override
public boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ) {
return !hullHider.isBlockHidden(p, blockX, blockY, blockZ);
}
@Override
public boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block) {
return !getHiddenRegion(p).inRegion(blockX, blockY, blockZ) || !blocksToObfuscate.contains(block);
}
@Override
public boolean isPlayerPrivilegedToAccessBlockState(Player p, int blockX, int blockY, int blockZ, BlockState blockState) {
return !getHiddenRegion(p).inRegion(blockX, blockY, blockZ) || !blockStatesToObfuscate.contains(blockState);
}
// TODO will require entity tracking on the netty thread to prevent future race conditions
@Override
public boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId) {
net.minecraft.world.entity.Entity nmsEntity = ((CraftWorld) p.getWorld()).getHandle().moonrise$getEntityLookup().get(entityId);
if (nmsEntity != null) {
return !hullHider.isBlockHidden(p, nmsEntity.getBlockX(), nmsEntity.getBlockY(), nmsEntity.getBlockZ());
} else {
return true;
}
}
@Override
public boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type) {
return !getHiddenRegion(p).inRegion(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type);
public ForPlayer forPlayer(Player p) {
return new FightPlayerAccess(p, getHiddenRegions(p), blockStatesToObfuscate, blocksToObfuscate, blockEntityTypeToObfuscate);
}
@Override
public boolean isPlayerPrivilegedToPerformAction(Player p) {
return p.getGameMode() != GameMode.SPECTATOR;
}
});
};
}
/**
* Snapshot of what one player may access, taken once per outgoing packet on the netty thread.
*/
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 FightPlayerAccess(Player player, Region[] hiddenRegions, Set<BlockState> blockStatesToObfuscate, Set<Block> blocksToObfuscate, Set<BlockEntityType<?>> blockEntityTypeToObfuscate) {
this.player = player;
this.hiddenRegions = hiddenRegions;
this.blockStatesToObfuscate = blockStatesToObfuscate;
this.blocksToObfuscate = blocksToObfuscate;
this.blockEntityTypeToObfuscate = blockEntityTypeToObfuscate;
}
private boolean isHidden(int blockX, int blockY, int blockZ) {
for (Region region : hiddenRegions) {
if (region.inRegion(blockX, blockY, blockZ)) return true;
}
return false;
}
@Override
public Player getPlayer() {
return player;
}
@Override
public boolean canAccessAllDataWithinChunk(int chunkX, int chunkZ) {
// Hulls the player may not see lie within the hidden regions, everything outside of them is not critical
return Arrays.stream(hiddenRegions).allMatch(region -> region.chunkOutside(chunkX, chunkZ));
}
@Override
public boolean canAccessPosition(int blockX, int blockY, int blockZ) {
return !hullHider.isBlockHidden(player, blockX, blockY, blockZ);
}
@Override
public boolean canAccessBlock(int blockX, int blockY, int blockZ, Block block) {
return !isHidden(blockX, blockY, blockZ) || !blocksToObfuscate.contains(block);
}
@Override
public boolean canAccessBlockState(int blockX, int blockY, int blockZ, BlockState blockState) {
return !isHidden(blockX, blockY, blockZ) || !blockStatesToObfuscate.contains(blockState);
}
// TODO will require entity tracking on the netty thread to prevent future race conditions
@Override
public boolean canAccessEntity(int entityId) {
net.minecraft.world.entity.Entity nmsEntity = ((CraftWorld) player.getWorld()).getHandle().moonrise$getEntityLookup().get(entityId);
if (nmsEntity != null) {
return !hullHider.isBlockHidden(player, nmsEntity.getBlockX(), nmsEntity.getBlockY(), nmsEntity.getBlockZ());
} else {
return true;
}
}
@Override
public boolean canAccessBlockEntity(int blockX, int blockY, int blockZ, BlockEntityType<?> type) {
return !isHidden(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type);
}
}
@Override
@@ -142,19 +188,19 @@ public class TechHiderWrapper extends StateDependent implements Listener {
@EventHandler
public void teamJoin(TeamSpawnEvent e) {
e.getFightPlayer().ifPlayer(player -> hiddenRegion.put(player, getHiddenRegion(player)));
e.getFightPlayer().ifPlayer(player -> hiddenRegion.put(player, getHiddenRegions(player)));
}
@EventHandler
public void boarding(BoardingEvent e) {
e.getFightPlayer().ifPlayer(player -> hiddenRegion.put(player, getHiddenRegion(player)));
e.getFightPlayer().ifPlayer(player -> hiddenRegion.put(player, getHiddenRegions(player)));
}
@EventHandler
public void teamLeave(TeamLeaveEvent e) {
e.getFightPlayer().ifPlayer(player -> {
if (player.isOnline()) {
hiddenRegion.put(player, getHiddenRegion(player));
hiddenRegion.put(player, getHiddenRegions(player));
}
});
}
@@ -175,15 +221,19 @@ public class TechHiderWrapper extends StateDependent implements Listener {
});
}
private Region getHiddenRegion(Player player) {
if (Config.isReferee(player)) return Region.EMPTY;
private Region[] getHiddenRegions(Player player) {
if (Config.isReferee(player)) return NO_REGIONS;
FightTeam team = Fight.getPlayerTeam(player);
if (team == null) return Config.ArenaRegion;
// Single lookup: runs on netty threads, the player can leave the team at any time
FightPlayer fightPlayer = Fight.getFightPlayer(player);
if (fightPlayer == null) {
// Spectators: both teams, the rest of the arena contains nothing critical
return Fight.teams().stream().map(FightTeam::getExtendRegion).toArray(Region[]::new);
}
if (team.canPlayerEntern(player)) return Region.EMPTY;
if (fightPlayer.canEntern()) return NO_REGIONS;
return Fight.getOpposite(team).getExtendRegion();
return new Region[]{Fight.getOpposite(fightPlayer.getTeam()).getExtendRegion()};
}
private Stream<BlockState> getWaterloggedBlockStates() {
@@ -25,17 +25,33 @@ import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.entity.Player;
public interface AccessPrivilegeProvider {
boolean isEveryonePrivilegedToAccessAllDataWithinChunk(int chunkX, int chunkZ);
boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ);
boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block);
boolean isPlayerPrivilegedToAccessBlockState(Player p, int blockX, int blockY, int blockZ, BlockState blockState);
boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId);
boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type);
/**
* Resolves everything that can change concurrently about this player once.
* The result is used for all decisions of one outgoing packet (including all packets of a bundle).
*/
ForPlayer forPlayer(Player p);
boolean isPlayerPrivilegedToPerformAction(Player p);
/**
* What a single player may access, valid for the processing of one outgoing packet.
*/
interface ForPlayer {
Player getPlayer();
/**
* @return true if nothing within this chunk is restricted for this player, so the chunk may be sent unmodified
*/
boolean canAccessAllDataWithinChunk(int chunkX, int chunkZ);
boolean canAccessPosition(int blockX, int blockY, int blockZ);
boolean canAccessBlock(int blockX, int blockY, int blockZ, Block block);
boolean canAccessBlockState(int blockX, int blockY, int blockZ, BlockState blockState);
boolean canAccessEntity(int entityId);
boolean canAccessBlockEntity(int blockX, int blockY, int blockZ, BlockEntityType<?> type);
}
}
@@ -19,6 +19,7 @@
package de.steamwar.techhider;
import de.steamwar.techhider.AccessPrivilegeProvider.ForPlayer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
@@ -44,11 +45,9 @@ public class ChunkHider {
private final int BIOMES_PER_SECTION = 64;
private final int blockIdUsedForHiding;
private final AccessPrivilegeProvider accessPrivilegeProvider;
public ChunkHider(Block blockUsedForObfuscation, AccessPrivilegeProvider accessPrivilegeProvider) {
public ChunkHider(Block blockUsedForObfuscation) {
blockIdUsedForHiding = Block.BLOCK_STATE_REGISTRY.getId(blockUsedForObfuscation.defaultBlockState());
this.accessPrivilegeProvider = accessPrivilegeProvider;
}
private int getLongsRequiredToEncodeEntries(int bitsPerEntry, int entryCount) {
@@ -69,7 +68,8 @@ public class ChunkHider {
return dataArray;
}
public ClientboundLevelChunkWithLightPacket processLevelChunkWithLightPacket(Player player, ClientboundLevelChunkWithLightPacket packet) {
public ClientboundLevelChunkWithLightPacket processLevelChunkWithLightPacket(ForPlayer access, ClientboundLevelChunkWithLightPacket packet) {
Player player = access.getPlayer();
int chunkX = packet.getX();
int chunkZ = packet.getZ();
ClientboundLevelChunkPacketData chunkData = packet.getChunkData();
@@ -104,7 +104,7 @@ public class ChunkHider {
resolvedData[i] = pallet[palletReference];
}
int[] obfuscatedBlockData = obfuscateBlockDataArray(player, chunkX, chunkZ, yOffset, resolvedData);
int[] obfuscatedBlockData = obfuscateBlockDataArray(access, chunkX, chunkZ, yOffset, resolvedData);
long[] reEncodedData = encodeDirectBlockDataArray(obfuscatedBlockData);
@@ -149,7 +149,7 @@ public class ChunkHider {
int[] blockData = resolveDirectBlockDataArray(rawData, bitsPerBlock);
int[] obfuscatedBlockData = obfuscateBlockDataArray(player, chunkX, chunkZ, yOffset, blockData);
int[] obfuscatedBlockData = obfuscateBlockDataArray(access, chunkX, chunkZ, yOffset, blockData);
long[] reEncodedData = encodeDirectBlockDataArray(obfuscatedBlockData);
@@ -172,13 +172,13 @@ public class ChunkHider {
out.readBytes(data);
List<ClientboundLevelChunkPacketData.BlockEntityInfo> blockEntities = chunkData.blockEntitiesData;
List<ClientboundLevelChunkPacketData.BlockEntityInfo> filteredBlockEntities = filterBlockEntities(player, blockEntities, chunkX, chunkZ);
List<ClientboundLevelChunkPacketData.BlockEntityInfo> filteredBlockEntities = filterBlockEntities(access, blockEntities, chunkX, chunkZ);
return buildNewChunkPacket(packet, data, filteredBlockEntities);
}
private int[] obfuscateBlockDataArray(Player player, int chunkX, int chunkZ, int yOffset, int[] blockDataArray) {
private int[] obfuscateBlockDataArray(ForPlayer access, int chunkX, int chunkZ, int yOffset, int[] blockDataArray) {
int[] obfuscatedData = new int[BLOCKS_PER_SECTION];
for (int sectionY = 0; sectionY < SECTION_SPAN_SIZE; sectionY++) {
for (int sectionZ = 0; sectionZ < SECTION_SPAN_SIZE; sectionZ++) {
@@ -194,7 +194,7 @@ public class ChunkHider {
Block block = blockState.getBlock();
if (accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlock(player, worldX, worldY, worldZ, block) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockState(player, worldX, worldY, worldZ, blockState)) {
if (access.canAccessPosition(worldX, worldY, worldZ) && access.canAccessBlock(worldX, worldY, worldZ, block) && access.canAccessBlockState(worldX, worldY, worldZ, blockState)) {
obfuscatedData[blockDataIndex] = blockId;
} else {
obfuscatedData[blockDataIndex] = blockIdUsedForHiding;
@@ -241,7 +241,7 @@ public class ChunkHider {
return clonedPacket;
}
private List<ClientboundLevelChunkPacketData.BlockEntityInfo> filterBlockEntities(Player player, List<ClientboundLevelChunkPacketData.BlockEntityInfo> blockEntities, int chunkX, int chunkZ) {
private List<ClientboundLevelChunkPacketData.BlockEntityInfo> filterBlockEntities(ForPlayer access, List<ClientboundLevelChunkPacketData.BlockEntityInfo> blockEntities, int chunkX, int chunkZ) {
int fourBitBitmask = 0b0000_1111;
return blockEntities.stream()
@@ -257,7 +257,7 @@ public class ChunkHider {
int worldY = blockEntityInfo.y;
return accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, worldX, worldY, worldZ, type);
return access.canAccessPosition(worldX, worldY, worldZ) && access.canAccessBlockEntity(worldX, worldY, worldZ, type);
}).toList();
}
@@ -19,14 +19,14 @@
package de.steamwar.techhider;
import de.steamwar.techhider.AccessPrivilegeProvider.ForPlayer;
import com.comphenix.tinyprotocol.TinyProtocol;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.shorts.ShortArraySet;
import it.unimi.dsi.fastutil.shorts.ShortSets;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.network.PacketListener;
import net.minecraft.util.Mth;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.common.*;
import net.minecraft.network.protocol.configuration.ClientboundFinishConfigurationPacket;
@@ -54,7 +54,7 @@ import java.util.function.ToIntFunction;
*/
public class TechHider {
private final Set<Class<?>> bypassingPackets;
private final Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> packetProcessors;
private final Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<ForPlayer, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> packetProcessors;
private final BlockState blockStateUsedForObfuscation;
private final ChunkHider chunkHider;
@@ -65,7 +65,7 @@ public class TechHider {
public TechHider(Block blockUsedForObfuscation, AccessPrivilegeProvider privilegeProvider) {
this.blockStateUsedForObfuscation = blockUsedForObfuscation.defaultBlockState();
this.chunkHider = new ChunkHider(blockUsedForObfuscation, privilegeProvider);
this.chunkHider = new ChunkHider(blockUsedForObfuscation);
this.privilegeProvider = privilegeProvider;
this.bypassingPackets = new HashSet<>(List.of(
@@ -181,6 +181,9 @@ public class TechHider {
ClientboundSetChunkCacheCenterPacket.class, // 7.1.93 Set Center Chunk
ClientboundForgetLevelChunkPacket.class, // 7.1.38 Unload Chunk
ClientboundUpdateTagsPacket.class, // 7.1.133 Update Tags (play)
// 7.1.76 Remove Entities: must not be filtered, otherwise Player#hideEntity leaves ghosts on the client.
// Entities that are already gone from the world pass the entity checks anyway, so no additional information is leaked.
ClientboundRemoveEntitiesPacket.class,
ClientboundPlayerInfoRemovePacket.class, // 7.1.68 Player Info Remove
ClientboundPlayerInfoUpdatePacket.class, // 7.1.69 Player Info Update
ClientboundMoveVehiclePacket.class, // 7.1.56 Move Vehicle (vehicle the player is in)
@@ -193,16 +196,16 @@ public class TechHider {
ClientboundContainerSetSlotPacket.class // 7.1.21 Set Container Slot
));
BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>> tossPacket = (p, packet) -> null;
Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> processors = new HashMap<>();
BiFunction<ForPlayer, Packet<? extends PacketListener>, Packet<? extends PacketListener>> tossPacket = (access, packet) -> null;
Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<ForPlayer, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> processors = new HashMap<>();
// --- Entity packets -
// 7.1.2 Spawn Entity: entity type and position can reveal hidden contraptions.
processors.put(ClientboundAddEntityPacket.class, (p, packet) -> this.processAddEntityPacket(p, (ClientboundAddEntityPacket) packet));
processors.put(ClientboundAddEntityPacket.class, (access, packet) -> this.processAddEntityPacket(access, (ClientboundAddEntityPacket) packet));
// 7.1.3 Entity Animation: entity id based signal, keep blocked until entity visibility is modeled.
processors.put(ClientboundAnimatePacket.class, this.buildEntityPacketProcessor(ClientboundAnimatePacket::getId));
// 7.1.35 Entity Event: entity id based signal.
processors.put(ClientboundEntityEventPacket.class, this.buildEntityPacketProcessor(ClientboundEntityEventPacket::getEventId));
processors.put(ClientboundEntityEventPacket.class, this.<ClientboundEntityEventPacket>buildEntityPacketProcessor(packet -> packet.entityId));
// 7.1.36 Teleport Entity: entity id and absolute position.
processors.put(ClientboundTeleportEntityPacket.class, this.buildEntityPacketProcessor(ClientboundTeleportEntityPacket::id));
// 7.1.42 Hurt Animation: entity id based signal.
@@ -234,43 +237,41 @@ public class TechHider {
// 7.1.67 Combat Death: entity/player ids and death message context.
processors.put(ClientboundPlayerCombatKillPacket.class, this.buildEntityPacketProcessor(ClientboundPlayerCombatKillPacket::playerId));
// 7.1.52/53/55 Update Entity Position/Rotation: entity id and movement signal.
processors.put(ClientboundMoveEntityPacket.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket) packet));
processors.put(ClientboundMoveEntityPacket.Pos.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.Pos) packet));
processors.put(ClientboundMoveEntityPacket.Rot.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.Rot) packet));
processors.put(ClientboundMoveEntityPacket.PosRot.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.PosRot) packet));
processors.put(ClientboundMoveEntityPacket.class, (access, packet) -> this.processMoveEntityPacket(access, (ClientboundMoveEntityPacket) packet));
processors.put(ClientboundMoveEntityPacket.Pos.class, (access, packet) -> this.processMoveEntityPacket(access, (ClientboundMoveEntityPacket.Pos) packet));
processors.put(ClientboundMoveEntityPacket.Rot.class, (access, packet) -> this.processMoveEntityPacket(access, (ClientboundMoveEntityPacket.Rot) packet));
processors.put(ClientboundMoveEntityPacket.PosRot.class, (access, packet) -> this.processMoveEntityPacket(access, (ClientboundMoveEntityPacket.PosRot) packet));
// 7.1.82 Set Head Rotation: entity id and rotation.
processors.put(ClientboundRotateHeadPacket.class, (p, packet) -> this.processRotateHeadPacket(p, (ClientboundRotateHeadPacket) packet));
// 7.1.76 Remove Entities: entity id visibility side channel.
processors.put(ClientboundRemoveEntitiesPacket.class, (p, packet) -> this.processRemoveEntitiesPacket(p, (ClientboundRemoveEntitiesPacket) packet));
processors.put(ClientboundRotateHeadPacket.class, (access, packet) -> this.processRotateHeadPacket(access, (ClientboundRotateHeadPacket) packet));
// 7.1.99 Link Entities: entity relationship signal.
processors.put(ClientboundSetEntityLinkPacket.class, (p, packet) -> this.processSetEntityLinkPacket(p, (ClientboundSetEntityLinkPacket) packet));
processors.put(ClientboundSetEntityLinkPacket.class, (access, packet) -> this.processSetEntityLinkPacket(access, (ClientboundSetEntityLinkPacket) packet));
// --- Block entity packets ---
// 7.1.7 Block Entity Data: currently filtered by block position and allowed tile-entity actions.
processors.put(ClientboundBlockEntityDataPacket.class, (p, packet) -> processBlockEntityDataPacket(p, (ClientboundBlockEntityDataPacket) packet));
processors.put(ClientboundBlockEntityDataPacket.class, (access, packet) -> processBlockEntityDataPacket(access, (ClientboundBlockEntityDataPacket) packet));
// 7.1.122 Tag Query Response: block-entity query result.
processors.put(ClientboundTagQueryPacket.class, tossPacket);
// --- Block packets ---
// 7.1.6 Set Block Destroy Stage: block position and mining progress can reveal activity.
processors.put(ClientboundBlockDestructionPacket.class, (p, packet) -> proccessBlockDestructionPacket(p, (ClientboundBlockDestructionPacket) packet));
processors.put(ClientboundBlockDestructionPacket.class, (access, packet) -> proccessBlockDestructionPacket(access, (ClientboundBlockDestructionPacket) packet));
// 7.1.8 Block Action: currently filtered by block position.
processors.put(ClientboundBlockEventPacket.class, (p, packet) -> processBlockEventPacket(p, (ClientboundBlockEventPacket) packet));
processors.put(ClientboundBlockEventPacket.class, (access, packet) -> processBlockEventPacket(access, (ClientboundBlockEventPacket) packet));
// 7.1.9 Block Update: currently filtered/obfuscated by block position and block id.
processors.put(ClientboundBlockUpdatePacket.class, (p, packet) -> processBlockUpdatePacket(p, (ClientboundBlockUpdatePacket) packet));
processors.put(ClientboundBlockUpdatePacket.class, (access, packet) -> processBlockUpdatePacket(access, (ClientboundBlockUpdatePacket) packet));
// --- Chunk packets ---
// 7.1.45 Chunk Data and Update Light: currently filtered/obfuscated by chunk data processor.
processors.put(ClientboundLevelChunkWithLightPacket.class, (p, packet) -> processChunkWithLight(p, (ClientboundLevelChunkWithLightPacket) packet));
processors.put(ClientboundLevelChunkWithLightPacket.class, (access, packet) -> processChunkWithLight(access, (ClientboundLevelChunkWithLightPacket) packet));
// --- Section packets ---
// 7.1.83 Update Section Blocks: currently filtered/obfuscated by section block processor.
processors.put(ClientboundSectionBlocksUpdatePacket.class, (p, packet) -> processSectionUpdate(p, (ClientboundSectionBlocksUpdatePacket) packet));
processors.put(ClientboundSectionBlocksUpdatePacket.class, (access, packet) -> processSectionUpdate(access, (ClientboundSectionBlocksUpdatePacket) packet));
// --- Particle packets ---
// 7.1.47 Particle: particle type and position can reveal hidden machinery.
processors.put(ClientboundLevelParticlesPacket.class, (p, packet) -> processLevelParticlesPacket(p, (ClientboundLevelParticlesPacket) packet));
processors.put(ClientboundLevelParticlesPacket.class, (access, packet) -> processLevelParticlesPacket(access, (ClientboundLevelParticlesPacket) packet));
// --- Sound packets ---
// 7.1.115 Entity Sound Effect: entity id and sound.
@@ -290,26 +291,29 @@ public class TechHider {
// 7.1.59 Open Sign Editor: block position.
processors.put(ClientboundOpenSignEditorPacket.class, buildPositionBasedPacketProcessor(ClientboundOpenSignEditorPacket::getPos));
// 7.1.37 Explosion: position, affected blocks, and knockback can reveal TNT/tech.
processors.put(ClientboundExplodePacket.class, (p, rawPacket) -> {
processors.put(ClientboundExplodePacket.class, (access, rawPacket) -> {
ClientboundExplodePacket packet = (ClientboundExplodePacket) rawPacket;
Vec3 pos = packet.center();
int blockX = (int) pos.x;
int blockY = (int) pos.y;
int blockZ = (int) pos.z;
int blockX = Mth.floor(pos.x);
int blockY = Mth.floor(pos.y);
int blockZ = Mth.floor(pos.z);
return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet);
return proccessPositionBasedPacket(access, blockX, blockY, blockZ, packet);
});
this.packetProcessors = processors;
TinyProtocol.instance.addGlobalClientboundFilter((player, rawPacket) -> {
try {
// Resolved lazily (login packets arrive before the player is known) and once per outer packet
AccessHolder access = new AccessHolder(player);
if (rawPacket instanceof ClientboundBundlePacket bundlePacket) {
List<Packet<? super ClientGamePacketListener>> processedPackets = new ArrayList<>();
for (Packet<? super ClientGamePacketListener> packet : bundlePacket.subPackets()) {
Packet<? super ClientGamePacketListener> processedPacket = (Packet<? super ClientGamePacketListener>) processPacket(player, packet);
Packet<? super ClientGamePacketListener> processedPacket = (Packet<? super ClientGamePacketListener>) processPacket(access, packet);
if (processedPacket != null) {
processedPackets.add(processedPacket);
@@ -318,7 +322,7 @@ public class TechHider {
return new ClientboundBundlePacket(processedPackets);
} else {
return processPacket(player, rawPacket);
return processPacket(access, rawPacket);
}
} catch (Throwable t) {
t.printStackTrace();
@@ -330,89 +334,100 @@ public class TechHider {
TinyProtocol.instance.addFilter(ServerboundInteractPacket.class, (p, packet) -> privilegeProvider.isPlayerPrivilegedToPerformAction(p) ? packet : null);
}
private Packet<?> processPacket(Player player, Packet<?> packet) {
private final class AccessHolder {
private final Player player;
private ForPlayer access;
private AccessHolder(Player player) {
this.player = player;
}
private ForPlayer get() {
if (access == null) {
access = privilegeProvider.forPlayer(player);
}
return access;
}
}
private Packet<?> processPacket(AccessHolder access, Packet<?> packet) {
if (bypassingPackets.contains(packet.getClass())) {
return packet;
} else if (packetProcessors.containsKey(packet.getClass())) {
return packetProcessors.get(packet.getClass()).apply(player, packet);
}
BiFunction<ForPlayer, Packet<? extends PacketListener>, Packet<? extends PacketListener>> processor = packetProcessors.get(packet.getClass());
if (processor != null) {
return processor.apply(access.get(), packet);
} else {
return null;
}
}
private Packet<? extends PacketListener> processAddEntityPacket(Player player, ClientboundAddEntityPacket packet) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.getId()) && privilegeProvider.isPlayerPrivilegedToAccessPosition(player, (int) packet.getX(), (int) packet.getY(), (int) packet.getZ())) {
private Packet<? extends PacketListener> processAddEntityPacket(ForPlayer access, ClientboundAddEntityPacket packet) {
if (access.canAccessEntity(packet.getId()) && access.canAccessPosition(Mth.floor(packet.getX()), Mth.floor(packet.getY()), Mth.floor(packet.getZ()))) {
return packet;
} else {
return null;
}
}
private Packet<? extends PacketListener> processEntityPacket(Player player, int entityId, Packet<? extends PacketListener> packet) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) {
private Packet<? extends PacketListener> processEntityPacket(ForPlayer access, int entityId, Packet<? extends PacketListener> packet) {
if (access.canAccessEntity(entityId)) {
return packet;
} else {
return null;
}
}
private <TTargetPacket extends Packet<?>> BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>> buildEntityPacketProcessor(ToIntFunction<TTargetPacket> entityIdExtractor) {
return (p, rawPacket) -> {
private <TTargetPacket extends Packet<?>> BiFunction<ForPlayer, Packet<? extends PacketListener>, Packet<? extends PacketListener>> buildEntityPacketProcessor(ToIntFunction<TTargetPacket> entityIdExtractor) {
return (access, rawPacket) -> {
TTargetPacket packet = (TTargetPacket) rawPacket;
return processEntityPacket(p, entityIdExtractor.applyAsInt(packet), packet);
return processEntityPacket(access, entityIdExtractor.applyAsInt(packet), packet);
};
}
private Packet<?> processMoveEntityPacket(Player player, ClientboundMoveEntityPacket packet) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.entityId)) {
private Packet<?> processMoveEntityPacket(ForPlayer access, ClientboundMoveEntityPacket packet) {
if (access.canAccessEntity(packet.entityId)) {
return packet;
} else {
return null;
}
}
private Packet<?> processRotateHeadPacket(Player player, ClientboundRotateHeadPacket packet) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.entityId)) {
private Packet<?> processRotateHeadPacket(ForPlayer access, ClientboundRotateHeadPacket packet) {
if (access.canAccessEntity(packet.entityId)) {
return packet;
} else {
return null;
}
}
private Packet<?> processRemoveEntitiesPacket(Player player, ClientboundRemoveEntitiesPacket packet) {
IntList entityIdsToRemove = packet.getEntityIds();
IntList filteredEntitiesToRemove = entityIdsToRemove.intStream().filter((id) -> privilegeProvider.isPlayerPrivilegedToAccessEntity(player, id)).collect(IntArrayList::new, IntList::add, IntList::addAll);
return new ClientboundRemoveEntitiesPacket(filteredEntitiesToRemove);
}
public Packet<?> processSetEntityLinkPacket(Player player, ClientboundSetEntityLinkPacket packet) {
public Packet<?> processSetEntityLinkPacket(ForPlayer access, ClientboundSetEntityLinkPacket packet) {
int fromEntityId = packet.getSourceId();
int toEntityId = packet.getDestId();
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, fromEntityId) && privilegeProvider.isPlayerPrivilegedToAccessEntity(player, toEntityId)) {
if (access.canAccessEntity(fromEntityId) && access.canAccessEntity(toEntityId)) {
return packet;
} else {
return null;
}
}
private Packet<?> processBlockEventPacket(Player player, ClientboundBlockEventPacket packet) {
private Packet<?> processBlockEventPacket(ForPlayer access, ClientboundBlockEventPacket packet) {
BlockPos blockPos = packet.getPos();
Block block = packet.getBlock();
int blockX = blockPos.getX();
int blockY = blockPos.getY();
int blockZ = blockPos.getZ();
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block)) {
if (access.canAccessPosition(blockX, blockY, blockZ) && access.canAccessBlock(blockX, blockY, blockZ, block)) {
return packet;
} else {
return null;
}
}
private Packet<?> processBlockUpdatePacket(Player player, ClientboundBlockUpdatePacket packet) {
private Packet<?> processBlockUpdatePacket(ForPlayer access, ClientboundBlockUpdatePacket packet) {
BlockPos blockPos = packet.getPos();
BlockState blockState = packet.getBlockState();
Block block = blockState.getBlock();
@@ -420,41 +435,41 @@ public class TechHider {
int blockY = blockPos.getY();
int blockZ = blockPos.getZ();
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block) && privilegeProvider.isPlayerPrivilegedToAccessBlockState(player, blockX, blockY, blockZ, blockState)) {
if (access.canAccessPosition(blockX, blockY, blockZ) && access.canAccessBlock(blockX, blockY, blockZ, block) && access.canAccessBlockState(blockX, blockY, blockZ, blockState)) {
return packet;
} else if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
} else if (access.canAccessPosition(blockX, blockY, blockZ)) {
return new ClientboundBlockUpdatePacket(blockPos, blockStateUsedForObfuscation);
} else {
return null;
}
}
private Packet<?> processBlockEntityDataPacket(Player player, ClientboundBlockEntityDataPacket packet) {
private Packet<?> processBlockEntityDataPacket(ForPlayer access, ClientboundBlockEntityDataPacket packet) {
BlockPos blockPos = packet.getPos();
BlockEntityType<?> blockEntityType = packet.getType();
int blockX = blockPos.getX();
int blockY = blockPos.getY();
int blockZ = blockPos.getZ();
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && privilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, blockX, blockY, blockZ, blockEntityType)) {
if (access.canAccessPosition(blockX, blockY, blockZ) && access.canAccessBlockEntity(blockX, blockY, blockZ, blockEntityType)) {
return packet;
} else {
return null;
}
}
private Packet<?> proccessBlockDestructionPacket(Player player, ClientboundBlockDestructionPacket packet) {
private Packet<?> proccessBlockDestructionPacket(ForPlayer access, ClientboundBlockDestructionPacket packet) {
BlockPos blockPos = packet.getPos();
int entityBreakingBlockId = packet.getId();
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityBreakingBlockId) && privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockPos.getX(), blockPos.getY(), blockPos.getZ())) {
if (access.canAccessEntity(entityBreakingBlockId) && access.canAccessPosition(blockPos.getX(), blockPos.getY(), blockPos.getZ())) {
return packet;
} else {
return null;
}
}
private ClientboundSectionBlocksUpdatePacket processSectionUpdate(Player p, ClientboundSectionBlocksUpdatePacket packet) {
private ClientboundSectionBlocksUpdatePacket processSectionUpdate(ForPlayer access, ClientboundSectionBlocksUpdatePacket packet) {
SectionPos sectionPos = packet.sectionPos;
short[] oldPos = packet.positions;
BlockState[] oldStates = packet.states;
@@ -471,10 +486,10 @@ public class TechHider {
int worldY = sectionPos.relativeToBlockY(posShort);
int worldZ = sectionPos.relativeToBlockZ(posShort);
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ) && privilegeProvider.isPlayerPrivilegedToAccessBlock(p, worldX, worldY, worldZ, block) && privilegeProvider.isPlayerPrivilegedToAccessBlockState(p, worldX, worldY, worldZ, state)) {
if (access.canAccessPosition(worldX, worldY, worldZ) && access.canAccessBlock(worldX, worldY, worldZ, block) && access.canAccessBlockState(worldX, worldY, worldZ, state)) {
filteredPos.add(posShort);
filteredStates.add(state);
} else if (privilegeProvider.isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ)) {
} else if (access.canAccessPosition(worldX, worldY, worldZ)) {
filteredPos.add(posShort);
filteredStates.add(blockStateUsedForObfuscation);
}
@@ -494,43 +509,43 @@ public class TechHider {
return new ClientboundSectionBlocksUpdatePacket(sectionPos, ShortSets.unmodifiable(new ShortArraySet(newPos)), newStates);
}
private Packet<?> processLevelParticlesPacket(Player player, ClientboundLevelParticlesPacket packet) {
int blockX = (int) packet.getX();
int blockY = (int) packet.getY();
int blockZ = (int) packet.getZ();
private Packet<?> processLevelParticlesPacket(ForPlayer access, ClientboundLevelParticlesPacket packet) {
int blockX = Mth.floor(packet.getX());
int blockY = Mth.floor(packet.getY());
int blockZ = Mth.floor(packet.getZ());
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
if (access.canAccessPosition(blockX, blockY, blockZ)) {
return packet;
} else {
return null;
}
}
private ClientboundLevelChunkWithLightPacket processChunkWithLight(Player p, ClientboundLevelChunkWithLightPacket packet) {
if (privilegeProvider.isEveryonePrivilegedToAccessAllDataWithinChunk(packet.getX(), packet.getZ())) {
private ClientboundLevelChunkWithLightPacket processChunkWithLight(ForPlayer access, ClientboundLevelChunkWithLightPacket packet) {
if (access.canAccessAllDataWithinChunk(packet.getX(), packet.getZ())) {
return packet;
} else {
return chunkHider.processLevelChunkWithLightPacket(p, packet);
return chunkHider.processLevelChunkWithLightPacket(access, packet);
}
}
private Packet<? extends PacketListener> proccessPositionBasedPacket(Player player, int blockX, int blockY, int blockZ, Packet<? extends PacketListener> packet) {
if (privilegeProvider.isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
private Packet<? extends PacketListener> proccessPositionBasedPacket(ForPlayer access, int blockX, int blockY, int blockZ, Packet<? extends PacketListener> packet) {
if (access.canAccessPosition(blockX, blockY, blockZ)) {
return packet;
} else {
return null;
}
}
private <TTargetPacket extends Packet<?>> BiFunction<Player, Packet<?>, Packet<?>> buildPositionBasedPacketProcessor(Function<TTargetPacket, BlockPos> positionExtractor) {
return (p, rawPacket) -> {
private <TTargetPacket extends Packet<?>> BiFunction<ForPlayer, Packet<?>, Packet<?>> buildPositionBasedPacketProcessor(Function<TTargetPacket, BlockPos> positionExtractor) {
return (access, rawPacket) -> {
TTargetPacket packet = (TTargetPacket) rawPacket;
BlockPos pos = positionExtractor.apply(packet);
int blockX = pos.getX();
int blockY = pos.getY();
int blockZ = pos.getZ();
return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet);
return proccessPositionBasedPacket(access, blockX, blockY, blockZ, packet);
};
}
}