Skip to content

Instantly share code, notes, and snippets.

@IllusionTheDev
Created July 29, 2022 23:18
Show Gist options
  • Save IllusionTheDev/b38fb7f00ef5a565f886931b631072c5 to your computer and use it in GitHub Desktop.
Save IllusionTheDev/b38fb7f00ef5a565f886931b631072c5 to your computer and use it in GitHub Desktop.
Fancy client-sided blocks
// 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;
}
}
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();
}
}
}
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 +
'}';
}
}
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);
}
}
@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();
}
}
}
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