diff --git a/README.md b/README.md index 37ff91d..27ee78f 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ See [FAQ](#FAQ) dev.cerus.maps common - 3.8.8 + 3.8.9 provided @@ -59,7 +59,7 @@ See [FAQ](#FAQ) dev.cerus.maps plugin - 3.8.8 + 3.8.9 provided diff --git a/bukkit-16_R3/pom.xml b/bukkit-16_R3/pom.xml index d4869dd..498b09d 100644 --- a/bukkit-16_R3/pom.xml +++ b/bukkit-16_R3/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.maps - 3.8.8 + 3.8.9 4.0.0 diff --git a/bukkit-17_R1/pom.xml b/bukkit-17_R1/pom.xml index b228c63..a2e76f3 100644 --- a/bukkit-17_R1/pom.xml +++ b/bukkit-17_R1/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.maps - 3.8.8 + 3.8.9 4.0.0 diff --git a/bukkit-18_R1/pom.xml b/bukkit-18_R1/pom.xml index f1c8d8e..5b6cf9a 100644 --- a/bukkit-18_R1/pom.xml +++ b/bukkit-18_R1/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.maps - 3.8.8 + 3.8.9 4.0.0 diff --git a/bukkit-18_R2/pom.xml b/bukkit-18_R2/pom.xml index bb9ab8f..47379e6 100644 --- a/bukkit-18_R2/pom.xml +++ b/bukkit-18_R2/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.maps - 3.8.8 + 3.8.9 4.0.0 diff --git a/bukkit-19_R1/pom.xml b/bukkit-19_R1/pom.xml index e9bc3c9..b6bea07 100644 --- a/bukkit-19_R1/pom.xml +++ b/bukkit-19_R1/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.maps - 3.8.8 + 3.8.9 4.0.0 diff --git a/bukkit-19_R2/pom.xml b/bukkit-19_R2/pom.xml index ba0cb89..0f23b78 100644 --- a/bukkit-19_R2/pom.xml +++ b/bukkit-19_R2/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.maps - 3.8.8 + 3.8.9 4.0.0 diff --git a/bukkit-19_R3/pom.xml b/bukkit-19_R3/pom.xml index 1ab6079..1ee180d 100644 --- a/bukkit-19_R3/pom.xml +++ b/bukkit-19_R3/pom.xml @@ -6,7 +6,7 @@ dev.cerus.maps parent - 3.8.8 + 3.8.9 bukkit-19_R3 diff --git a/bukkit-20_R1/pom.xml b/bukkit-20_R1/pom.xml index a2e8118..fe82a64 100644 --- a/bukkit-20_R1/pom.xml +++ b/bukkit-20_R1/pom.xml @@ -6,7 +6,7 @@ dev.cerus.maps parent - 3.8.8 + 3.8.9 bukkit-20_R1 diff --git a/bukkit-20_R2/pom.xml b/bukkit-20_R2/pom.xml index c2d0041..333bd6c 100644 --- a/bukkit-20_R2/pom.xml +++ b/bukkit-20_R2/pom.xml @@ -6,7 +6,7 @@ dev.cerus.maps parent - 3.8.8 + 3.8.9 bukkit-20_R2 diff --git a/bukkit-20_R3/pom.xml b/bukkit-20_R3/pom.xml index 6fce17a..748a55a 100644 --- a/bukkit-20_R3/pom.xml +++ b/bukkit-20_R3/pom.xml @@ -6,7 +6,7 @@ dev.cerus.maps parent - 3.8.8 + 3.8.9 bukkit-20_R3 diff --git a/bukkit-20_R4/pom.xml b/bukkit-20_R4/pom.xml index 8a7f659..3b851f0 100644 --- a/bukkit-20_R4/pom.xml +++ b/bukkit-20_R4/pom.xml @@ -6,7 +6,7 @@ dev.cerus.maps parent - 3.8.8 + 3.8.9 bukkit-20_R4 diff --git a/bukkit-21_R1/pom.xml b/bukkit-21_R1/pom.xml index 53fa376..0e76deb 100644 --- a/bukkit-21_R1/pom.xml +++ b/bukkit-21_R1/pom.xml @@ -6,7 +6,7 @@ dev.cerus.maps parent - 3.8.8 + 3.8.9 bukkit-21_R1 diff --git a/bukkit-21_R2/pom.xml b/bukkit-21_R2/pom.xml index 97071c0..8b55599 100644 --- a/bukkit-21_R2/pom.xml +++ b/bukkit-21_R2/pom.xml @@ -6,7 +6,7 @@ dev.cerus.maps parent - 3.8.8 + 3.8.9 bukkit-21_R2 diff --git a/bukkit-21_R3/pom.xml b/bukkit-21_R3/pom.xml new file mode 100644 index 0000000..3a4c8a2 --- /dev/null +++ b/bukkit-21_R3/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + dev.cerus.maps + parent + 3.8.9 + + + bukkit-21_R3 + + + 16 + 16 + UTF-8 + + + + + dev.cerus.maps + common + ${parent.version} + provided + + + org.bukkit + craftbukkit + 1.21.4-R0.1-SNAPSHOT + provided + + + + diff --git a/bukkit-21_R3/src/main/java/dev/cerus/maps/version/PacketHandler21R3.java b/bukkit-21_R3/src/main/java/dev/cerus/maps/version/PacketHandler21R3.java new file mode 100644 index 0000000..9617bee --- /dev/null +++ b/bukkit-21_R3/src/main/java/dev/cerus/maps/version/PacketHandler21R3.java @@ -0,0 +1,110 @@ +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_21_R3.block.CraftBlock; +import org.bukkit.craftbukkit.v1_21_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +public class PacketHandler21R3 extends ChannelDuplexHandler { + + private static final Field actionField; + private static final Object attackAction; + + static { + try { + actionField = PacketPlayInUseEntity.class.getDeclaredField("c"); + actionField.setAccessible(true); + final Field attackActionField = PacketPlayInUseEntity.class.getDeclaredField("e"); + 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 PacketHandler21R3(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.b() != 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.b().u(), + dig.b().v(), + dig.b().w() + ); + // If we don't acknowledge the client's block change it won't accept further block change packets + ((CraftPlayer) this.player).getHandle().f.a(new ClientboundBlockChangedAckPacket(dig.g())); + 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.e(); + if (pos != null && pos.a() != null) { + final Location location = new Location( + this.player.getWorld(), + pos.b().u(), + pos.b().v(), + pos.b().w() + ).getBlock().getRelative(CraftBlock.notchToBlockFace(pos.c())).getLocation(); + // If we don't acknowledge the client's block change it won't accept further block change packets + ((CraftPlayer) this.player).getHandle().f.a(new ClientboundBlockChangedAckPacket(useItem.f())); + Bukkit.getScheduler().runTask(this.plugin, () -> this.player.sendBlockChange(location, location.getBlock().getBlockData())); + } + } + return; + } + super.channelRead(ctx, msg); + } + +} diff --git a/bukkit-21_R3/src/main/java/dev/cerus/maps/version/VersionAdapter21R3.java b/bukkit-21_R3/src/main/java/dev/cerus/maps/version/VersionAdapter21R3.java new file mode 100644 index 0000000..a944311 --- /dev/null +++ b/bukkit-21_R3/src/main/java/dev/cerus/maps/version/VersionAdapter21R3.java @@ -0,0 +1,185 @@ +package dev.cerus.maps.version; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +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.Optional; +import java.util.UUID; +import java.util.logging.Level; +import java.util.stream.Collectors; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.chat.ComponentSerialization; +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.MapId; +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_21_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.inventory.meta.MapMeta; +import org.bukkit.plugin.java.JavaPlugin; + +public class VersionAdapter21R3 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( + new MapId(map.getId()), + (byte) 0, + true, + map.getMarkers().stream() + .map(cursor -> new MapIcon( + BuiltInRegistries.ar.c(cursor.getType()).get(), + cursor.getCompressedX(), + cursor.getCompressedY(), + cursor.getDirection(), + !cursor.hasCaption() ? Optional.empty() : parse(cursor.getCaptionString()) + )) + .collect(Collectors.toList()), + new WorldMap.c( + x, + y, + w, + h, + data + ) + ); + } + + private Optional parse(String s) { + if (s == null) { + return Optional.empty(); + } + JsonElement element = JsonParser.parseString(s); + if (element == null) { + return Optional.empty(); + } + return ComponentSerialization.a.parse(JsonOps.INSTANCE, element).result(); + } + + @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.c<>(8, DataWatcherRegistry.h, CraftItemStack.asNMSCopy(mapItem)), + new DataWatcher.c<>(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.af : EntityTypes.as, + 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().f.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().f); + } 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 PacketHandler21R3(player, listener, plugin)); + } + + private NetworkManager getNetworkManager(final PlayerConnection b) throws IllegalAccessException, NoSuchFieldException { + if (this.netManField == null) { + this.netManField = ServerCommonPacketListenerImpl.class.getDeclaredField("e"); + this.netManField.setAccessible(true); + } + return (NetworkManager) this.netManField.get(b); + } + +} diff --git a/common/pom.xml b/common/pom.xml index 0f075c5..96122d7 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.maps - 3.8.8 + 3.8.9 4.0.0 diff --git a/plugin/pom.xml b/plugin/pom.xml index 1ea53dd..0d3a9a3 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.maps - 3.8.8 + 3.8.9 4.0.0 @@ -134,6 +134,12 @@ ${parent.version} compile + + dev.cerus.maps + bukkit-21_R3 + ${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 9412b07..5131b00 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.21"; + public static final String MAX_VER = "1.21.4"; public VersionAdapter makeAdapter() { String version = Bukkit.getVersion(); @@ -26,6 +26,7 @@ public VersionAdapter makeAdapter() { case "1.20.5", "1.20.6" -> new VersionAdapter20R4(); case "1.21", "1.21.1" -> new VersionAdapter21R1(); case "1.21.2", "1.21.3" -> new VersionAdapter21R2(); + case "1.21.4" -> new VersionAdapter21R3(); default -> null; }; } diff --git a/pom.xml b/pom.xml index 203a2f1..aea729e 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ dev.cerus.maps parent pom - 3.8.8 + 3.8.9 common bukkit-16_R3 @@ -23,6 +23,7 @@ bukkit-20_R4 bukkit-21_R1 bukkit-21_R2 + bukkit-21_R3 plugin