From 73bd28528d4cc0853f63cca206a82c81ef16b4b4 Mon Sep 17 00:00:00 2001
From: Cerus
Date: Fri, 22 Sep 2023 00:57:45 +0200
Subject: [PATCH] feat: 1.20.2 support
---
README.md | 8 +-
bukkit-16_R3/pom.xml | 2 +-
bukkit-17_R1/pom.xml | 2 +-
bukkit-18_R1/pom.xml | 2 +-
bukkit-18_R2/pom.xml | 2 +-
bukkit-19_R1/pom.xml | 2 +-
bukkit-19_R2/pom.xml | 2 +-
bukkit-19_R3/pom.xml | 2 +-
bukkit-20_R1/pom.xml | 2 +-
bukkit-20_R2/pom.xml | 35 ++++
.../cerus/maps/version/PacketHandler20R2.java | 112 ++++++++++++
.../maps/version/VersionAdapter20R2.java | 167 ++++++++++++++++++
common/pom.xml | 2 +-
plugin/pom.xml | 8 +-
.../maps/version/VersionAdapterFactory.java | 3 +-
pom.xml | 5 +-
16 files changed, 339 insertions(+), 17 deletions(-)
create mode 100644 bukkit-20_R2/pom.xml
create mode 100644 bukkit-20_R2/src/main/java/dev/cerus/maps/version/PacketHandler20R2.java
create mode 100644 bukkit-20_R2/src/main/java/dev/cerus/maps/version/VersionAdapter20R2.java
diff --git a/README.md b/README.md
index 3c6e977..7936f50 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ of packet-maps.
• Advanced engine features
like [alpha compositing](https://en.wikipedia.org/wiki/Alpha_compositing) ([Image](https://cerus.dev/img/maps_alpha_composition.png))\
• Efficient click handling\
-• Supports 1.16.5 - 1.20
+• Supports 1.16.5 - 1.20.2
**What is the point of the plugin module?**\
See [FAQ](#FAQ)
@@ -50,7 +50,7 @@ See [FAQ](#FAQ)
dev.cerus.maps
common
- 3.8.0
+ 3.8.1
provided
@@ -59,7 +59,7 @@ See [FAQ](#FAQ)
dev.cerus.maps
plugin
- 3.8.0
+ 3.8.1
provided
@@ -136,7 +136,7 @@ public class MyPlugin extends JavaPlugin {
### Building
-Requirements: Java 16, Git, Maven, CraftBukkit 1.16.5, 1.17.1, 1.18.1, 1.18.2, 1.19.1, 1.19.3, 1.19.4 and 1.20 installed in local Maven repo
+Requirements: Java 16, Git, Maven, CraftBukkit 1.16.5, 1.17.1, 1.18.1, 1.18.2, 1.19.1, 1.19.3, 1.19.4, 1.20 and 1.20.2 installed in local Maven repo
Simply clone the repository, navigate into the directory and run `mvn clean package`. The plugin will be in `plugin/target` and the api
in `common/target`.
diff --git a/bukkit-16_R3/pom.xml b/bukkit-16_R3/pom.xml
index b844a60..6d80573 100644
--- a/bukkit-16_R3/pom.xml
+++ b/bukkit-16_R3/pom.xml
@@ -5,7 +5,7 @@
parent
dev.cerus.maps
- 3.8.0
+ 3.8.1
4.0.0
diff --git a/bukkit-17_R1/pom.xml b/bukkit-17_R1/pom.xml
index 190e41a..bd24ce8 100644
--- a/bukkit-17_R1/pom.xml
+++ b/bukkit-17_R1/pom.xml
@@ -5,7 +5,7 @@
parent
dev.cerus.maps
- 3.8.0
+ 3.8.1
4.0.0
diff --git a/bukkit-18_R1/pom.xml b/bukkit-18_R1/pom.xml
index 40d374b..386393c 100644
--- a/bukkit-18_R1/pom.xml
+++ b/bukkit-18_R1/pom.xml
@@ -5,7 +5,7 @@
parent
dev.cerus.maps
- 3.8.0
+ 3.8.1
4.0.0
diff --git a/bukkit-18_R2/pom.xml b/bukkit-18_R2/pom.xml
index 2430faa..619e0ae 100644
--- a/bukkit-18_R2/pom.xml
+++ b/bukkit-18_R2/pom.xml
@@ -5,7 +5,7 @@
parent
dev.cerus.maps
- 3.8.0
+ 3.8.1
4.0.0
diff --git a/bukkit-19_R1/pom.xml b/bukkit-19_R1/pom.xml
index 7c0233b..3b65a99 100644
--- a/bukkit-19_R1/pom.xml
+++ b/bukkit-19_R1/pom.xml
@@ -5,7 +5,7 @@
parent
dev.cerus.maps
- 3.8.0
+ 3.8.1
4.0.0
diff --git a/bukkit-19_R2/pom.xml b/bukkit-19_R2/pom.xml
index e8b491d..d3d628e 100644
--- a/bukkit-19_R2/pom.xml
+++ b/bukkit-19_R2/pom.xml
@@ -5,7 +5,7 @@
parent
dev.cerus.maps
- 3.8.0
+ 3.8.1
4.0.0
diff --git a/bukkit-19_R3/pom.xml b/bukkit-19_R3/pom.xml
index 2e0ec44..3d5a7e2 100644
--- a/bukkit-19_R3/pom.xml
+++ b/bukkit-19_R3/pom.xml
@@ -6,7 +6,7 @@
dev.cerus.maps
parent
- 3.8.0
+ 3.8.1
bukkit-19_R3
diff --git a/bukkit-20_R1/pom.xml b/bukkit-20_R1/pom.xml
index fde03fb..78d6b7b 100644
--- a/bukkit-20_R1/pom.xml
+++ b/bukkit-20_R1/pom.xml
@@ -6,7 +6,7 @@
dev.cerus.maps
parent
- 3.8.0
+ 3.8.1
bukkit-20_R1
diff --git a/bukkit-20_R2/pom.xml b/bukkit-20_R2/pom.xml
new file mode 100644
index 0000000..e60dad9
--- /dev/null
+++ b/bukkit-20_R2/pom.xml
@@ -0,0 +1,35 @@
+
+
+ 4.0.0
+
+ dev.cerus.maps
+ parent
+ 3.8.1
+
+
+ bukkit-20_R2
+
+
+ 16
+ 16
+ UTF-8
+
+
+
+
+ dev.cerus.maps
+ common
+ ${parent.version}
+ provided
+
+
+ org.bukkit
+ craftbukkit
+ 1.20.2-R0.1-SNAPSHOT
+ provided
+
+
+
+
\ No newline at end of file
diff --git a/bukkit-20_R2/src/main/java/dev/cerus/maps/version/PacketHandler20R2.java b/bukkit-20_R2/src/main/java/dev/cerus/maps/version/PacketHandler20R2.java
new file mode 100644
index 0000000..bfcd4aa
--- /dev/null
+++ b/bukkit-20_R2/src/main/java/dev/cerus/maps/version/PacketHandler20R2.java
@@ -0,0 +1,112 @@
+package dev.cerus.maps.version;
+
+import dev.cerus.maps.api.version.PacketListener;
+import io.netty.channel.ChannelDuplexHandler;
+import io.netty.channel.ChannelHandlerContext;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import net.minecraft.network.protocol.game.ClientboundBlockChangedAckPacket;
+import net.minecraft.network.protocol.game.PacketPlayInBlockDig;
+import net.minecraft.network.protocol.game.PacketPlayInBlockPlace;
+import net.minecraft.network.protocol.game.PacketPlayInUseEntity;
+import net.minecraft.network.protocol.game.PacketPlayInUseItem;
+import net.minecraft.world.EnumHand;
+import net.minecraft.world.phys.MovingObjectPositionBlock;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlock;
+import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class PacketHandler20R2 extends ChannelDuplexHandler {
+
+ private static final Field actionField;
+ private static final Object attackAction;
+
+ static {
+ try {
+ actionField = PacketPlayInUseEntity.class.getDeclaredField("b");
+ actionField.setAccessible(true);
+ final Field attackActionField = PacketPlayInUseEntity.class.getDeclaredField("d");
+ attackActionField.setAccessible(true);
+ attackAction = attackActionField.get(null);
+ } catch (final NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final Player player;
+ private final PacketListener listener;
+ private final JavaPlugin plugin;
+
+ public PacketHandler20R2(final Player player, final PacketListener listener, final JavaPlugin plugin) {
+ this.player = player;
+ this.listener = listener;
+ this.plugin = plugin;
+ }
+
+ private static Object getAction(final PacketPlayInUseEntity packet) {
+ try {
+ return actionField.get(packet);
+ } catch (final IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static EnumHand getHand(final PacketPlayInUseEntity packet) {
+ try {
+ final Object action = getAction(packet);
+ final Field handField = Arrays.stream(action.getClass().getDeclaredFields())
+ .filter(field -> field.getType() == EnumHand.class)
+ .findAny().orElse(null);
+ if (handField == null) {
+ return null;
+ }
+ handField.setAccessible(true);
+ return (EnumHand) handField.get(action);
+ } catch (final IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
+ if ((((msg instanceof final PacketPlayInUseItem useItem && useItem.a() != EnumHand.b) || msg instanceof PacketPlayInBlockPlace)
+ && this.listener.handlePlayerRightClick(this.player)) || (msg instanceof final PacketPlayInUseEntity useEntity && getHand(useEntity) != EnumHand.b
+ && (getAction(useEntity) == attackAction ? this.listener.handlePlayerLeftClick(this.player)
+ : this.listener.handlePlayerRightClick(this.player))) || (msg instanceof PacketPlayInBlockDig
+ && this.listener.handlePlayerLeftClick(this.player))) {
+ if (msg instanceof final PacketPlayInBlockDig dig) {
+ // To prevent de-syncs we need to tell the client that the block has not changed
+ final Location location = new Location(
+ this.player.getWorld(),
+ dig.a().u(),
+ dig.a().v(),
+ dig.a().w()
+ );
+ // If we don't acknowledge the client's block change it won't accept further block change packets
+ ((CraftPlayer) this.player).getHandle().c.a(new ClientboundBlockChangedAckPacket(dig.f()));
+ Bukkit.getScheduler().runTask(this.plugin, () -> this.player.sendBlockChange(location, location.getBlock().getBlockData()));
+ }
+ if (msg instanceof final PacketPlayInUseItem useItem) {
+ // To prevent de-syncs we need to tell the client that the block has not changed
+ final MovingObjectPositionBlock pos = useItem.d();
+ if (pos != null && pos.a() != null) {
+ final Location location = new Location(
+ this.player.getWorld(),
+ pos.a().u(),
+ pos.a().v(),
+ pos.a().w()
+ ).getBlock().getRelative(CraftBlock.notchToBlockFace(pos.b())).getLocation();
+ // If we don't acknowledge the client's block change it won't accept further block change packets
+ ((CraftPlayer) this.player).getHandle().c.a(new ClientboundBlockChangedAckPacket(useItem.e()));
+ Bukkit.getScheduler().runTask(this.plugin, () -> this.player.sendBlockChange(location, location.getBlock().getBlockData()));
+ }
+ }
+ return;
+ }
+ super.channelRead(ctx, msg);
+ }
+
+}
diff --git a/bukkit-20_R2/src/main/java/dev/cerus/maps/version/VersionAdapter20R2.java b/bukkit-20_R2/src/main/java/dev/cerus/maps/version/VersionAdapter20R2.java
new file mode 100644
index 0000000..584c9ed
--- /dev/null
+++ b/bukkit-20_R2/src/main/java/dev/cerus/maps/version/VersionAdapter20R2.java
@@ -0,0 +1,167 @@
+package dev.cerus.maps.version;
+
+import dev.cerus.maps.api.ClientsideMap;
+import dev.cerus.maps.api.Frame;
+import dev.cerus.maps.api.version.PacketListener;
+import dev.cerus.maps.api.version.VersionAdapter;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+import net.minecraft.network.NetworkManager;
+import net.minecraft.network.chat.IChatBaseComponent;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy;
+import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
+import net.minecraft.network.protocol.game.PacketPlayOutMap;
+import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity;
+import net.minecraft.network.syncher.DataWatcher;
+import net.minecraft.network.syncher.DataWatcherRegistry;
+import net.minecraft.server.network.PlayerConnection;
+import net.minecraft.server.network.ServerCommonPacketListenerImpl;
+import net.minecraft.world.entity.EntityTypes;
+import net.minecraft.world.level.saveddata.maps.MapIcon;
+import net.minecraft.world.level.saveddata.maps.WorldMap;
+import net.minecraft.world.phys.Vec3D;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.Particle;
+import org.bukkit.block.BlockFace;
+import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.meta.MapMeta;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class VersionAdapter20R2 implements VersionAdapter {
+
+ private Field netManField;
+
+ @Override
+ public void spawnBarrierParticle(final Player player, final Location loc) {
+ player.spawnParticle(Particle.BLOCK_MARKER, loc, 1, Material.BARRIER.createBlockData());
+ }
+
+ @Override
+ public Object makeMapPacket(final boolean ignoreBounds, final ClientsideMap map) {
+ final int x = ignoreBounds ? 0 : map.getX();
+ final int y = ignoreBounds ? 0 : map.getY();
+ final int w = ignoreBounds ? 128 : Math.max(1, map.getWidth());
+ final int h = ignoreBounds ? 128 : Math.max(1, map.getHeight());
+
+ final byte[] data;
+ if (ignoreBounds) {
+ data = map.getData();
+ } else {
+ data = new byte[w * h];
+ for (int xx = 0; xx < w; ++xx) {
+ for (int yy = 0; yy < h; ++yy) {
+ data[xx + yy * w] = map.getData()[x + xx + (y + yy) * 128];
+ }
+ }
+ }
+
+ return new PacketPlayOutMap(
+ map.getId(),
+ (byte) 0,
+ true,
+ map.getMarkers().stream()
+ .map(cursor -> new MapIcon(
+ MapIcon.Type.a(cursor.getType()),
+ cursor.getCompressedX(),
+ cursor.getCompressedY(),
+ cursor.getDirection(),
+ !cursor.hasCaption() ? null : IChatBaseComponent.ChatSerializer.a(cursor.getCaptionString())
+ ))
+ .collect(Collectors.toList()),
+ new WorldMap.b(
+ x,
+ y,
+ w,
+ h,
+ data
+ )
+ );
+ }
+
+ @Override
+ public Object makeFramePacket(final int frameId, final boolean visible, final ClientsideMap map) {
+ final org.bukkit.inventory.ItemStack mapItem = new org.bukkit.inventory.ItemStack(Material.FILLED_MAP, 1);
+ final MapMeta mapMeta = (MapMeta) mapItem.getItemMeta();
+ mapMeta.setMapId(map.getId());
+ mapItem.setItemMeta(mapMeta);
+
+ final List> dwItems = Arrays.asList(
+ new DataWatcher.b<>(8, DataWatcherRegistry.h, CraftItemStack.asNMSCopy(mapItem)),
+ new DataWatcher.b<>(0, DataWatcherRegistry.a, (byte) (visible ? 0 : 0x20))
+ );
+ return new PacketPlayOutEntityMetadata(frameId, dwItems);
+ }
+
+ @Override
+ public Object makeFrameSpawnPacket(final Frame frame) {
+ return new PacketPlayOutSpawnEntity(
+ frame.getEntityId(),
+ UUID.randomUUID(),
+ frame.getPosX(),
+ frame.getPosY(),
+ frame.getPosZ(),
+ frame.getFacing() == BlockFace.DOWN ? 90 : frame.getFacing() == BlockFace.UP ? -90 : 0,
+ switch (frame.getFacing()) {
+ case NORTH -> -180;
+ case EAST -> -90;
+ case WEST -> 90;
+ default -> 0;
+ },
+ frame.isGlowing() ? EntityTypes.S : EntityTypes.af,
+ switch (frame.getFacing()) {
+ case UP -> 1;
+ case NORTH -> 2;
+ case SOUTH -> 3;
+ case WEST -> 4;
+ case EAST -> 5;
+ default -> 0;
+ },
+ new Vec3D(0, 0, 0),
+ switch (frame.getFacing()) {
+ case NORTH -> -180;
+ case EAST -> -90;
+ case WEST -> 90;
+ default -> 0;
+ }
+ );
+ }
+
+ @Override
+ public Object makeFrameDespawnPacket(final Frame frame) {
+ return new PacketPlayOutEntityDestroy(frame.getEntityId());
+ }
+
+ @Override
+ public void sendPacket(final Player player, final Object packet) {
+ ((CraftPlayer) player).getHandle().c.b((Packet>) packet);
+ }
+
+ @Override
+ public void inject(final Player player, final PacketListener listener, final JavaPlugin plugin) {
+ final NetworkManager networkManager;
+ try {
+ networkManager = this.getNetworkManager(((CraftPlayer) player).getHandle().c);
+ } catch (final IllegalAccessException | NoSuchFieldException e) {
+ plugin.getLogger().log(Level.WARNING, "Failed to inject packet handler into player %s".formatted(player.getName()), e);
+ return;
+ }
+ networkManager.n.pipeline().addBefore("packet_handler", "maps_listener", new PacketHandler20R2(player, listener, plugin));
+ }
+
+ private NetworkManager getNetworkManager(final PlayerConnection b) throws IllegalAccessException, NoSuchFieldException {
+ if (this.netManField == null) {
+ this.netManField = ServerCommonPacketListenerImpl.class.getDeclaredField("h");
+ this.netManField.setAccessible(true);
+ }
+ return (NetworkManager) this.netManField.get(b);
+ }
+
+}
diff --git a/common/pom.xml b/common/pom.xml
index 683fe33..0ab4ff5 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -5,7 +5,7 @@
parent
dev.cerus.maps
- 3.8.0
+ 3.8.1
4.0.0
diff --git a/plugin/pom.xml b/plugin/pom.xml
index f6e4f3b..7739540 100644
--- a/plugin/pom.xml
+++ b/plugin/pom.xml
@@ -5,7 +5,7 @@
parent
dev.cerus.maps
- 3.8.0
+ 3.8.1
4.0.0
@@ -104,6 +104,12 @@
${parent.version}
compile
+
+ dev.cerus.maps
+ bukkit-20_R2
+ ${parent.version}
+ compile
+
diff --git a/plugin/src/main/java/dev/cerus/maps/version/VersionAdapterFactory.java b/plugin/src/main/java/dev/cerus/maps/version/VersionAdapterFactory.java
index 972325a..c5370bf 100644
--- a/plugin/src/main/java/dev/cerus/maps/version/VersionAdapterFactory.java
+++ b/plugin/src/main/java/dev/cerus/maps/version/VersionAdapterFactory.java
@@ -6,7 +6,7 @@
public class VersionAdapterFactory {
public static final String MIN_VER = "1.16.5";
- public static final String MAX_VER = "1.20";
+ public static final String MAX_VER = "1.20.2";
public VersionAdapter makeAdapter() {
String version = Bukkit.getVersion();
@@ -21,6 +21,7 @@ public VersionAdapter makeAdapter() {
case "1.19.3" -> new VersionAdapter19R2();
case "1.19.4" -> new VersionAdapter19R3();
case "1.20", "1.20.1" -> new VersionAdapter20R1();
+ case "1.20.2" -> new VersionAdapter20R2();
default -> null;
};
}
diff --git a/pom.xml b/pom.xml
index 44379c9..239db70 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
dev.cerus.maps
parent
pom
- 3.8.0
+ 3.8.1
common
bukkit-16_R3
@@ -16,9 +16,10 @@
bukkit-18_R2
bukkit-19_R1
bukkit-19_R2
- plugin
bukkit-19_R3
bukkit-20_R1
+ bukkit-20_R2
+ plugin
${artifactId}