diff --git a/README.md b/README.md index 03ebd96..910e20b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Visualizes the crafting process ## Compatibility -Compatible with Spigot & Paper 1.16.5, 1.17, 1.17.1, 1.18.1, 1.18.2, 1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.20, 1.20.1 and 1.20.2 +Compatible with Spigot & Paper 1.16.5, 1.17, 1.17.1, 1.18.1, 1.18.2, 1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.20, 1.20.1, 1.20.2, 1.20.4 and 1.20.5 ## How it works diff --git a/api/src/main/java/dev/cerus/visualcrafting/api/version/FakeMap.java b/api/src/main/java/dev/cerus/visualcrafting/api/version/FakeMap.java index e34faa7..3eb8d4a 100644 --- a/api/src/main/java/dev/cerus/visualcrafting/api/version/FakeMap.java +++ b/api/src/main/java/dev/cerus/visualcrafting/api/version/FakeMap.java @@ -11,10 +11,12 @@ public class FakeMap { private final int id; private final byte[] data; + private final Object handle; - FakeMap(final int id) { + FakeMap(final int id, Object handle) { this.id = id; this.data = new byte[128 * 128]; + this.handle = handle; } public void setPixel(final int x, final int y, final byte color) { @@ -41,4 +43,7 @@ byte[] getData() { return this.data; } + Object getHandle() { + return handle; + } } diff --git a/api/src/main/java/dev/cerus/visualcrafting/api/version/VersionAdapter.java b/api/src/main/java/dev/cerus/visualcrafting/api/version/VersionAdapter.java index 6cef2da..5326193 100644 --- a/api/src/main/java/dev/cerus/visualcrafting/api/version/VersionAdapter.java +++ b/api/src/main/java/dev/cerus/visualcrafting/api/version/VersionAdapter.java @@ -80,6 +80,18 @@ public void updateItemDisplay(final int displayId, final FakeItemDisplay itemDis */ public abstract void destroyEntity(int entityId); + /** + * Helper method for creating a new fake map + * + * @param id The map's id + * @param handle The map's nms handle + * + * @return A new fake map + */ + protected FakeMap createMap(final int id, Object handle) { + return new FakeMap(id, handle); + } + /** * Helper method for creating a new fake map * @@ -88,7 +100,7 @@ public void updateItemDisplay(final int displayId, final FakeItemDisplay itemDis * @return A new fake map */ protected FakeMap createMap(final int id) { - return new FakeMap(id); + return createMap(id, null); } /** @@ -102,6 +114,17 @@ protected byte[] getMapData(final FakeMap fakeMap) { return fakeMap.getData(); } + /** + * Get a fake map's nms handle + * + * @param fakeMap The fake map + * + * @return The nms handle + */ + protected Object getMapHandle(FakeMap fakeMap) { + return fakeMap.getHandle(); + } + /** * Create a new fake map * diff --git a/bukkit-20R4/pom.xml b/bukkit-20R4/pom.xml new file mode 100644 index 0000000..50cc3af --- /dev/null +++ b/bukkit-20R4/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + dev.cerus.visualcrafting + parent + 1.2.0 + + + bukkit-20R4 + + + 21 + 21 + UTF-8 + + + + + dev.cerus.visualcrafting + api + ${project.parent.version} + provided + + + + org.bukkit + craftbukkit + 1.20.5-R0.1-SNAPSHOT + provided + + + + \ No newline at end of file diff --git a/bukkit-20R4/src/main/java/dev/cerus/visualcrafting/v20r4/VersionAdapter20R4.java b/bukkit-20R4/src/main/java/dev/cerus/visualcrafting/v20r4/VersionAdapter20R4.java new file mode 100644 index 0000000..3b9efbd --- /dev/null +++ b/bukkit-20R4/src/main/java/dev/cerus/visualcrafting/v20r4/VersionAdapter20R4.java @@ -0,0 +1,232 @@ +package dev.cerus.visualcrafting.v20r4; + +import com.mojang.math.Transformation; +import dev.cerus.visualcrafting.api.config.Config; +import dev.cerus.visualcrafting.api.version.FakeItemDisplay; +import dev.cerus.visualcrafting.api.version.FakeMap; +import dev.cerus.visualcrafting.api.version.Feature; +import dev.cerus.visualcrafting.api.version.VersionAdapter; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.UUID; +import java.util.function.BiConsumer; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +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.MapId; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Rotation; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class VersionAdapter20R4 extends VersionAdapter { + + private Config config; + private BiConsumer entityClickCallback; + private int nextEntityId; + private int nextMapId; + private Field netManField; + + private NetworkManager getNetworkManager(final PlayerConnection b) { + try { + if (this.netManField == null) { + this.netManField = ServerCommonPacketListenerImpl.class.getDeclaredField("e"); + this.netManField.setAccessible(true); + } + return (NetworkManager) this.netManField.get(b); + } catch (final NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public void init(final Config config, final BiConsumer entityClickCallback) { + this.config = config; + this.entityClickCallback = entityClickCallback; + this.nextEntityId = config.entityIdRangeMin(); + this.nextMapId = config.mapIdRangeMin(); + } + + @Override + public void inject(final Player player) { + if (this.config.enablePacketListening()) { + final NetworkManager netMan = this.getNetworkManager(((CraftPlayer) player).getHandle().c); + netMan.n.pipeline() + .addBefore("packet_handler", "visual_crafting", new ChannelDuplexHandler() { + @Override + public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { + if (msg instanceof final PacketPlayInUseEntity useEntity) { + VersionAdapter20R4.this.handlePacketIn(player, useEntity); + } + super.channelRead(ctx, msg); + } + }); + } + } + + private void handlePacketIn(final Player player, final PacketPlayInUseEntity packet) { + try { + final Field a = packet.getClass().getDeclaredField("b"); + a.setAccessible(true); + this.entityClickCallback.accept(player, (Integer) a.get(packet)); + } catch (final NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + @Override + public int spawnItemFrame(final Location location, final BlockFace direction) { + final int eid = this.getNewEntityId(); + final PacketPlayOutSpawnEntity packet = new PacketPlayOutSpawnEntity( + eid, + UUID.randomUUID(), + location.getBlockX(), + location.getBlockY(), + location.getBlockZ(), + direction == BlockFace.DOWN ? 90 : direction == BlockFace.UP ? -90 : 0, + switch (direction) { + case NORTH -> -180; + case EAST -> -90; + case WEST -> 90; + default -> 0; + }, + EntityTypes.ai, + switch (direction) { + case UP -> 1; + case NORTH -> 2; + case SOUTH -> 3; + case WEST -> 4; + case EAST -> 5; + default -> 0; + }, + new Vec3D(0, 0, 0), + switch (direction) { + case NORTH -> -180; + case EAST -> -90; + case WEST -> 90; + default -> 0; + } + ); + Bukkit.getOnlinePlayers().forEach(player -> this.sendPacket(player, packet)); + return eid; + } + + @Override + public int spawnItemDisplay(final FakeItemDisplay itemDisplay) { + final int eid = this.getNewEntityId(); + final PacketPlayOutSpawnEntity packet = new PacketPlayOutSpawnEntity( + eid, + UUID.randomUUID(), + itemDisplay.getLocation().getBlockX(), + itemDisplay.getLocation().getBlockY(), + itemDisplay.getLocation().getBlockZ(), + 0, + 0, + EntityTypes.ah, + 0, + new Vec3D(0, 0, 0), + 0 + ); + Bukkit.getOnlinePlayers().forEach(player -> this.sendPacket(player, packet)); + return eid; + } + + @Override + public void updateItemFrame(final int frameId, final ItemStack itemStack, final Rotation rotation, final boolean invisible) { + final PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(frameId, Arrays.asList( + new DataWatcher.c<>(8, DataWatcherRegistry.h, CraftItemStack.asNMSCopy(itemStack)), + new DataWatcher.c<>(9, DataWatcherRegistry.b, rotation.ordinal()), + new DataWatcher.c<>(0, DataWatcherRegistry.a, (byte) (invisible ? 0x20 : 0)) + )); + Bukkit.getOnlinePlayers().forEach(player -> this.sendPacket(player, packet)); + } + + @Override + public void updateItemDisplay(final int displayId, final FakeItemDisplay itemDisplay) { + final Transformation nmsTransf = new Transformation(itemDisplay.getTransformationMatrix()); + final PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(displayId, Arrays.asList( + new DataWatcher.c<>(11, DataWatcherRegistry.D, nmsTransf.d()), + new DataWatcher.c<>(12, DataWatcherRegistry.D, nmsTransf.f()), + new DataWatcher.c<>(13, DataWatcherRegistry.E, nmsTransf.e()), + new DataWatcher.c<>(14, DataWatcherRegistry.E, nmsTransf.g()), + new DataWatcher.c<>(23, DataWatcherRegistry.h, CraftItemStack.asNMSCopy(itemDisplay.getItemStack())), + new DataWatcher.c<>(24, DataWatcherRegistry.a, (byte) itemDisplay.getTransform().ordinal()) + )); + Bukkit.getOnlinePlayers().forEach(player -> this.sendPacket(player, packet)); + } + + @Override + public void destroyEntity(final int entityId) { + final PacketPlayOutEntityDestroy packet = new PacketPlayOutEntityDestroy(entityId); + Bukkit.getOnlinePlayers().forEach(player -> this.sendPacket(player, packet)); + } + + @Override + public FakeMap createMap() { + int mapId = getNewMapId(); + return this.createMap(mapId, new MapId(mapId)); + } + + @Override + public void sendMap(final FakeMap map) { + final PacketPlayOutMap packet = new PacketPlayOutMap( + (MapId) getMapHandle(map), + (byte) 0, + true, + Collections.emptyList(), + new WorldMap.b(0, + 0, + 128, + 128, + this.getMapData(map)) + ); + Bukkit.getOnlinePlayers().forEach(player -> this.sendPacket(player, packet)); + } + + @Override + public EnumSet getImplementedFeatures() { + return FEATURES_DISPLAY; + } + + private void sendPacket(final Player player, final Packet packet) { + ((CraftPlayer) player).getHandle().c.b(packet); + } + + private int getNewEntityId() { + if (this.nextEntityId >= this.config.entityIdRangeMax()) { + this.nextEntityId = this.config.entityIdRangeMin(); + return this.nextEntityId; + } else { + return this.nextEntityId++; + } + } + + private int getNewMapId() { + if (this.nextMapId >= this.config.mapIdRangeMax()) { + this.nextMapId = this.config.mapIdRangeMin(); + return this.nextMapId; + } else { + return this.nextMapId++; + } + } + +} diff --git a/plugin/pom.xml b/plugin/pom.xml index 6fc5a28..9e272a5 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -89,6 +89,12 @@ ${project.parent.version} compile + + dev.cerus.visualcrafting + bukkit-20R4 + ${project.parent.version} + compile + dev.cerus.maps @@ -130,7 +136,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.13.0 ${maven.compiler.source} ${maven.compiler.target} @@ -139,7 +145,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.4.0 + 3.5.1 diff --git a/plugin/src/main/java/dev/cerus/visualcrafting/plugin/VisualCraftingPlugin.java b/plugin/src/main/java/dev/cerus/visualcrafting/plugin/VisualCraftingPlugin.java index 58f0dd1..43a62c6 100644 --- a/plugin/src/main/java/dev/cerus/visualcrafting/plugin/VisualCraftingPlugin.java +++ b/plugin/src/main/java/dev/cerus/visualcrafting/plugin/VisualCraftingPlugin.java @@ -19,6 +19,7 @@ import dev.cerus.visualcrafting.v20r1.VersionAdapter20R1; import dev.cerus.visualcrafting.v20r2.VersionAdapter20R2; import dev.cerus.visualcrafting.v20r3.VersionAdapter20R3; +import dev.cerus.visualcrafting.v20r4.VersionAdapter20R4; import java.io.File; import java.net.URL; import java.security.CodeSource; @@ -56,6 +57,7 @@ public void onEnable() { case "1.20", "1.20.1" -> new VersionAdapter20R1(); case "1.20.2" -> new VersionAdapter20R2(); case "1.20.3", "1.20.4" -> new VersionAdapter20R3(); + case "1.20.5" -> new VersionAdapter20R4(); default -> null; }; if (versionAdapter == null) { diff --git a/pom.xml b/pom.xml index 4011a06..e48d985 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,7 @@ bukkit-20R1 bukkit-20R2 bukkit-20R3 + bukkit-20R4