Created
July 29, 2022 23:18
-
-
Save IllusionTheDev/b38fb7f00ef5a565f886931b631072c5 to your computer and use it in GitHub Desktop.
Fancy client-sided blocks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// EditSession but made to cache packets | |
public class CachedEditSession { | |
private final Location origin; | |
private final Location offset; | |
private final FakeBlockTracker tracker; | |
private final Map<Vector, BlockData> cachedData = new ConcurrentHashMap<>(); | |
public CachedEditSession(Location origin, Location offset, FakeBlockTracker tracker) { | |
this.origin = origin; | |
this.offset = offset; | |
this.tracker = tracker; | |
} | |
public void setData(Clipboard clipboard) { | |
Vector min = convert(clipboard.getMinimumPoint()); | |
Vector max = convert(clipboard.getMaximumPoint()); | |
for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { | |
for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { | |
for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { | |
Vector offset = new Vector(x, y, z).subtract(min); | |
BlockData data = BukkitAdapter.adapt(clipboard.getBlock(x, y, z)); | |
setData(offset, data); | |
} | |
} | |
} | |
} | |
private Vector convert(BlockVector3 vector3) { | |
return new Vector(vector3.getBlockX(), vector3.getBlockY(), vector3.getBlockZ()); | |
} | |
public void setType(Location location, Material type) { | |
cachedData.put(location.toVector().subtract(origin.toVector()), type.createBlockData()); | |
} | |
public void setData(Vector offset, BlockData data) { | |
cachedData.put(offset, data); | |
} | |
public void setData(Location location, BlockData data) { | |
cachedData.put(location.toVector().subtract(origin.toVector()), data); | |
} | |
public void revert(Location location) { | |
cachedData.remove(location.toVector().subtract(origin.toVector())); | |
} | |
public BlockData getData(Location location) { | |
return cachedData.getOrDefault(location.toVector().subtract(origin.toVector()), null); | |
} | |
private Location getMinPoint() { | |
return origin.clone(); | |
} | |
private Location getMaxPoint() { | |
return origin.clone().add(offset); | |
} | |
private Set<Location> getLocations() { | |
Set<Location> locations = new HashSet<>(); | |
for (Vector vector : cachedData.keySet()) { | |
locations.add(origin.clone().add(vector)); | |
} | |
return locations; | |
} | |
public void apply(Player... players) { | |
Set<FakeBlock> changedBlocks = new HashSet<>(); | |
for (Map.Entry<Vector, BlockData> entry : cachedData.entrySet()) { | |
Vector vector = entry.getKey(); | |
BlockData data = entry.getValue(); | |
Location location = origin.clone().add(vector); | |
FakeBlock block = tracker.getOrCreate(location); | |
for (Player player : players) { | |
block.setData(player, data); | |
} | |
changedBlocks.add(block); | |
} | |
Map<ChunkPosition, ChunkData> chunkData = new HashMap<>(); | |
for (FakeBlock block : changedBlocks) { | |
ChunkPosition chunkPosition = new ChunkPosition(block.getLocation()); | |
ChunkData data = chunkData.get(chunkPosition); | |
if (data == null) { | |
data = new ChunkData(); | |
chunkData.put(chunkPosition, data); | |
} | |
data.insertBlock(block); | |
} | |
for (Player player : players) { | |
createPacket(player, chunkData); // different players have different fake blocks | |
} | |
} | |
private short toShortLocation(Location location) { | |
int x = location.getBlockX() & 0xF; | |
int y = location.getBlockY() & 0xF; | |
int z = location.getBlockZ() & 0xF; | |
return (short) (x << 8 | z << 4 | y); | |
} | |
public void stop(Player... players) { | |
Set<FakeBlock> changedBlocks = new HashSet<>(); | |
for (Map.Entry<Vector, BlockData> entry : cachedData.entrySet()) { | |
Vector vector = entry.getKey(); | |
Location location = origin.clone().add(vector); | |
FakeBlock block = tracker.getBlockAt(location); | |
if (block == null) | |
continue; | |
for (Player player : players) { | |
block.revert(player); | |
} | |
changedBlocks.add(block); | |
} | |
Map<ChunkPosition, ChunkData> chunkData = new HashMap<>(); | |
for (FakeBlock block : changedBlocks) { | |
ChunkPosition chunkPosition = new ChunkPosition(block.getLocation()); | |
ChunkData data = chunkData.get(chunkPosition); | |
if (data == null) { | |
data = new ChunkData(); | |
chunkData.put(chunkPosition, data); | |
} | |
data.insertBlock(block); | |
} | |
for (Player player : players) { | |
stopPlayer(player, chunkData); // different players have different fake blocks | |
} | |
} | |
private void stopPlayer(Player player, Map<ChunkPosition, ChunkData> chunkData) { | |
List<PacketContainer> packets = new ArrayList<>(); | |
for (Map.Entry<ChunkPosition, ChunkData> entry : chunkData.entrySet()) { | |
ChunkPosition chunkPosition = entry.getKey(); | |
ChunkData data = entry.getValue(); | |
for (ChunkData.ChunkSection section : data.getSections()) { | |
int chunkX = chunkPosition.getX(); | |
int chunkY = section.getY(); | |
int chunkZ = chunkPosition.getZ(); | |
PacketContainer packet = new PacketContainer(PacketType.Play.Server.MULTI_BLOCK_CHANGE); | |
packet.setMeta("no-process", true); | |
packet.getSectionPositions().writeSafely(0, new BlockPosition(chunkX, chunkY, chunkZ)); | |
Collection<FakeBlock> blocks = section.getBlocks(); | |
short[] shorts = new short[blocks.size()]; | |
WrappedBlockData[] blockDatas = new WrappedBlockData[blocks.size()]; | |
int index = 0; | |
for (FakeBlock block : blocks) { | |
block.revert(player, false); | |
shorts[index] = toShortLocation(block.getLocation()); | |
blockDatas[index] = WrappedBlockData.createData(block.getLocation().getBlock().getBlockData()); | |
index++; | |
} | |
packet.getShortArrays().writeSafely(0, shorts); | |
packet.getBlockDataArrays().writeSafely(0, blockDatas); | |
packets.add(packet); | |
} | |
} | |
for (PacketContainer packet : packets) { | |
try { | |
ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); | |
} catch (InvocationTargetException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} | |
private void createPacket(Player player, Map<ChunkPosition, ChunkData> chunkData) { | |
UUID playerId = player.getUniqueId(); | |
List<PacketContainer> packets = new ArrayList<>(); | |
for (Map.Entry<ChunkPosition, ChunkData> entry : chunkData.entrySet()) { | |
ChunkPosition chunkPosition = entry.getKey(); | |
ChunkData data = entry.getValue(); | |
for (ChunkData.ChunkSection section : data.getSections()) { | |
int chunkX = chunkPosition.getX(); | |
int chunkY = section.getY(); | |
int chunkZ = chunkPosition.getZ(); | |
PacketContainer packet = new PacketContainer(PacketType.Play.Server.MULTI_BLOCK_CHANGE); | |
packet.setMeta("no-process", true); | |
packet.getSectionPositions().writeSafely(0, new BlockPosition(chunkX, chunkY, chunkZ)); | |
Collection<FakeBlock> blocks = section.getBlocks(); | |
short[] shorts = new short[blocks.size()]; | |
WrappedBlockData[] blockDatas = new WrappedBlockData[blocks.size()]; | |
int index = 0; | |
for (FakeBlock block : blocks) { | |
shorts[index] = toShortLocation(block.getLocation()); | |
blockDatas[index] = WrappedBlockData.createData(block.getData(playerId).getMaterial()); | |
index++; | |
} | |
packet.getShortArrays().writeSafely(0, shorts); | |
packet.getBlockDataArrays().writeSafely(0, blockDatas); | |
packets.add(packet); | |
} | |
} | |
for (PacketContainer packet : packets) { | |
try { | |
ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); | |
} catch (InvocationTargetException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} | |
public Location getOrigin() { | |
return origin; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ChunkData { | |
private final Map<Integer, ChunkSection> sectionMap = new HashMap<>(); | |
public ChunkSection getSection(int y) { | |
return sectionMap.getOrDefault(y, null); | |
} | |
public Collection<ChunkSection> getSections() { | |
return sectionMap.values(); | |
} | |
public void insertBlock(FakeBlock block) { | |
Location location = block.getLocation(); | |
if (getBlockAt(location) != null) { | |
return; | |
} | |
int y = location.getBlockY() >> 4; | |
ChunkSection section = sectionMap.getOrDefault(y, null); | |
if (section == null) { | |
section = new ChunkSection(y); | |
sectionMap.put(y, section); | |
} | |
section.registerBlock(block); | |
} | |
public FakeBlock getBlockAt(Location location) { | |
int y = location.getBlockY() >> 4; | |
ChunkSection section = sectionMap.getOrDefault(y, null); | |
if (section == null) { | |
return null; | |
} | |
return section.getBlock(location); | |
} | |
public void removeBlock(FakeBlock block) { | |
Location location = block.getLocation(); | |
int y = location.getBlockY() >> 4; | |
ChunkSection section = sectionMap.getOrDefault(y, null); | |
if (section == null) { | |
return; | |
} | |
section.removeBlock(block); | |
} | |
@EqualsAndHashCode | |
public static class ChunkSection { | |
@Getter | |
private final int y; | |
private final Map<Short, FakeBlock> blocks = new HashMap<>(); | |
public ChunkSection(int y) { | |
this.y = y; | |
} | |
public void registerBlock(FakeBlock block) { | |
blocks.put(getLocationHash(block.getLocation()), block); | |
} | |
private short getLocationHash(Location location) { | |
int x = location.getBlockX() & 0xF; | |
int y = location.getBlockY() & 0xF; | |
int z = location.getBlockZ() & 0xF; | |
return (short) (x << 8 | z << 4 | y); | |
} | |
public FakeBlock getBlock(Location location) { | |
return blocks.getOrDefault(getLocationHash(location), null); | |
} | |
public void removeBlock(FakeBlock block) { | |
removeBlock(block.getLocation()); | |
} | |
public void removeBlock(Location location) { | |
blocks.remove(getLocationHash(location)); | |
} | |
public Collection<FakeBlock> getBlocks() { | |
return blocks.values(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ChunkPosition { | |
private int x; | |
private int z; | |
public ChunkPosition(int x, int z) { | |
this.x = x; | |
this.z = z; | |
} | |
public ChunkPosition(Location location) { | |
this.x = location.getBlockX() >> 4; | |
this.z = location.getBlockZ() >> 4; | |
} | |
public ChunkPosition(Chunk chunk) { | |
this.x = chunk.getX(); | |
this.z = chunk.getZ(); | |
} | |
public int getX() { | |
return x; | |
} | |
public void setX(int x) { | |
this.x = x; | |
} | |
public int getZ() { | |
return z; | |
} | |
public void setZ(int z) { | |
this.z = z; | |
} | |
public boolean equals(Object obj) { | |
if (obj instanceof ChunkPosition other) { | |
return other.getX() == x && other.getZ() == z; | |
} | |
return false; | |
} | |
public int hashCode() { | |
return x * 31 + z; | |
} | |
public String toString() { | |
return "ChunkPosition{" + | |
"x=" + x + | |
", z=" + z + | |
'}'; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class EditSession { | |
private final Location origin; | |
private final Location offset; | |
private final FakeBlockTracker tracker; | |
private final Set<FakeBlock> changedBlocks = new HashSet<>(); | |
private final UUID playerId; | |
public EditSession(FakeBlockTracker tracker, Location one, Location two, Player player) { | |
this.origin = one; | |
this.offset = two.clone().subtract(one); | |
this.tracker = tracker; | |
this.playerId = player.getUniqueId(); | |
} | |
private Location getMinPoint() { | |
return origin.clone(); | |
} | |
private Location getMaxPoint() { | |
return origin.clone().add(offset); | |
} | |
public void setType(Material material) { | |
for (Location location : getLocations()) { | |
FakeBlock block = tracker.getBlockAt(location); | |
if (block == null) { | |
block = new FakeBlock(location); | |
tracker.insertBlock(block); | |
} | |
Material type = block.getData(playerId).getMaterial(); | |
if (type == material) { | |
continue; | |
} | |
block.setType(playerId, material); | |
changedBlocks.add(block); | |
} | |
} | |
private Set<Location> getLocations() { | |
Set<Location> locations = new HashSet<>(); | |
Location min = getMinPoint(); | |
Location max = getMaxPoint(); | |
for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { | |
for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { | |
for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { | |
locations.add(new Location(min.getWorld(), x, y, z)); | |
} | |
} | |
} | |
return locations; | |
} | |
public void apply() { | |
Map<ChunkPosition, ChunkData> chunkData = new HashMap<>(); | |
for (FakeBlock block : changedBlocks) { | |
ChunkPosition chunkPosition = new ChunkPosition(block.getLocation()); | |
ChunkData data = chunkData.get(chunkPosition); | |
if (data == null) { | |
data = new ChunkData(); | |
chunkData.put(chunkPosition, data); | |
} | |
data.insertBlock(block); | |
} | |
List<PacketContainer> packets = new ArrayList<>(); | |
for (Map.Entry<ChunkPosition, ChunkData> entry : chunkData.entrySet()) { | |
ChunkPosition chunkPosition = entry.getKey(); | |
ChunkData data = entry.getValue(); | |
for (ChunkData.ChunkSection section : data.getSections()) { | |
int chunkX = chunkPosition.getX(); | |
int chunkY = section.getY(); | |
int chunkZ = chunkPosition.getZ(); | |
PacketContainer packet = new PacketContainer(PacketType.Play.Server.MULTI_BLOCK_CHANGE); | |
packet.getSectionPositions().writeSafely(0, new BlockPosition(chunkX, chunkY, chunkZ)); | |
Collection<FakeBlock> blocks = section.getBlocks(); | |
short[] shorts = new short[blocks.size()]; | |
WrappedBlockData[] blockDatas = new WrappedBlockData[blocks.size()]; | |
int index = 0; | |
for (FakeBlock block : blocks) { | |
shorts[index] = toShortLocation(block.getLocation()); | |
blockDatas[index] = WrappedBlockData.createData(block.getData(playerId).getMaterial()); | |
index++; | |
} | |
packet.getShortArrays().writeSafely(0, shorts); | |
packet.getBlockDataArrays().writeSafely(0, blockDatas); | |
packets.add(packet); | |
} | |
} | |
Player player = Bukkit.getPlayer(playerId); | |
if (player == null) { | |
return; | |
} | |
for (PacketContainer packet : packets) { | |
try { | |
ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); | |
} catch (InvocationTargetException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} | |
private short toShortLocation(Location location) { | |
int x = location.getBlockX(); | |
int y = location.getBlockY(); | |
int z = location.getBlockZ(); | |
return (short) (x << 12 | y << 8 | z); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Data | |
@RequiredArgsConstructor | |
public class FakeBlock { | |
private final Location location; | |
private final Map<UUID, BlockData> displayedData = new HashMap<>(); | |
public int hashCode() { | |
return location.hashCode(); | |
} | |
@Override | |
public boolean equals(Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
FakeBlock fakeBlock = (FakeBlock) o; | |
return location.equals(fakeBlock.location); | |
} | |
public void setType(Player player, Material type) { | |
displayedData.put(player.getUniqueId(), type.createBlockData()); | |
} | |
public void setData(Player player, BlockData data) { | |
displayedData.put(player.getUniqueId(), data); | |
} | |
public void setType(UUID uuid, Material type) { | |
displayedData.put(uuid, type.createBlockData()); | |
} | |
public void setData(UUID uuid, BlockData data) { | |
displayedData.put(uuid, data); | |
} | |
public boolean isViewedBy(Player player) { | |
return displayedData.containsKey(player.getUniqueId()); | |
} | |
public void revert(Player player) { | |
revert(player, true); | |
} | |
public void revert(Player player, boolean sendData) { | |
if (sendData) { | |
setData(player, location.getBlock().getBlockData()); | |
updateIndividually(player); | |
} | |
displayedData.remove(player.getUniqueId()); | |
} | |
public BlockData getData(Player player) { | |
return displayedData.getOrDefault(player.getUniqueId(), null); | |
} | |
public BlockData getData(UUID uuid) { | |
return displayedData.getOrDefault(uuid, null); | |
} | |
public void updateIndividually(Player player) { | |
PacketContainer packet = new PacketContainer(PacketType.Play.Server.BLOCK_CHANGE); | |
packet.getBlockPositionModifier().writeSafely(0, new BlockPosition(location.getBlockX(), location.getBlockY(), location.getBlockZ())); | |
packet.getBlockData().writeSafely(0, WrappedBlockData.createData(getData(player))); | |
packet.setMeta("no-process", true); | |
try { | |
ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); | |
} catch (InvocationTargetException e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class FakeBlockTracker { | |
private final Map<ChunkPosition, ChunkData> chunkDataMap = new ConcurrentHashMap<>(); | |
public FakeBlockTracker(JavaPlugin main) { | |
ProtocolLibrary.getProtocolManager().addPacketListener(new PacketAdapter(main, PacketType.Play.Server.BLOCK_BREAK, PacketType.Play.Server.BLOCK_CHANGE) { | |
@Override | |
public void onPacketSending(PacketEvent event) { | |
Player player = event.getPlayer(); | |
PacketContainer packet = event.getPacket(); | |
if (packet.getMeta("no-process").isPresent()) | |
return; | |
BlockPosition position = packet.getBlockPositionModifier().readSafely(0); | |
FakeBlock block = getBlockAt(position.toLocation(player.getWorld())); | |
if (block == null) | |
return; | |
if (!block.isViewedBy(player)) | |
return; | |
WrappedBlockData data = packet.getBlockData().readSafely(0); | |
if (data == null) | |
return; | |
Material type = data.getType(); | |
Material targetType = block.getData(player).getMaterial(); | |
if (type != targetType) | |
event.setCancelled(true); | |
} | |
}); | |
ProtocolLibrary.getProtocolManager().addPacketListener(new PacketAdapter(main, PacketType.Play.Client.BLOCK_DIG) { | |
@Override | |
public void onPacketReceiving(PacketEvent event) { | |
Player player = event.getPlayer(); | |
PacketContainer packet = event.getPacket(); | |
if (packet.getMeta("no-process").isPresent()) | |
return; | |
EnumWrappers.PlayerDigType type = packet.getPlayerDigTypes().readSafely(0); | |
if (type != EnumWrappers.PlayerDigType.STOP_DESTROY_BLOCK) | |
return; | |
BlockPosition position = packet.getBlockPositionModifier().readSafely(0); | |
FakeBlock block = getBlockAt(position.toLocation(player.getWorld())); | |
if (block == null) | |
return; | |
if (!block.isViewedBy(player)) | |
return; | |
event.setCancelled(true); | |
block.updateIndividually(player); | |
} | |
}); | |
} | |
public void discardPlayer(Player player) { | |
for (ChunkData chunkData : chunkDataMap.values()) { | |
for (ChunkData.ChunkSection section : chunkData.getSections()) { | |
for (FakeBlock block : section.getBlocks()) { | |
block.revert(player, false); | |
} | |
} | |
} | |
} | |
public FakeBlock getBlockAt(Location location) { | |
ChunkPosition chunkPosition = new ChunkPosition(location); | |
ChunkData chunkData = chunkDataMap.get(chunkPosition); | |
if (chunkData == null) { | |
return null; | |
} | |
return chunkData.getBlockAt(location); | |
} | |
public void insertBlock(FakeBlock block) { | |
ChunkPosition chunkPosition = new ChunkPosition(block.getLocation()); | |
ChunkData chunkData = chunkDataMap.get(chunkPosition); | |
if (chunkData == null) { | |
chunkData = new ChunkData(); | |
chunkDataMap.put(chunkPosition, chunkData); | |
} | |
chunkData.insertBlock(block); | |
} | |
public void removeBlock(FakeBlock block) { | |
ChunkPosition chunkPosition = new ChunkPosition(block.getLocation()); | |
ChunkData chunkData = chunkDataMap.get(chunkPosition); | |
if (chunkData == null) { | |
return; | |
} | |
chunkData.removeBlock(block); | |
} | |
public FakeBlock getOrCreate(Location location) { | |
ChunkPosition chunkPosition = new ChunkPosition(location); | |
ChunkData chunkData = chunkDataMap.get(chunkPosition); | |
if (chunkData == null) { | |
chunkData = new ChunkData(); | |
chunkDataMap.put(chunkPosition, chunkData); | |
} | |
FakeBlock block = chunkData.getBlockAt(location); | |
if (block == null) { | |
block = new FakeBlock(location); | |
chunkData.insertBlock(block); | |
} | |
return block; | |
} | |
public void clear() { | |
chunkDataMap.clear(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment