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}