diff --git a/README.md b/README.md index a615f8f..1690d42 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 and 1.18.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 and 1.19.4 ## How it works diff --git a/api/pom.xml b/api/pom.xml index 088d823..967d5c4 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.visualcrafting - 1.0.7 + 1.0.8 4.0.0 diff --git a/bukkit-16R3/pom.xml b/bukkit-16R3/pom.xml index 6d539da..4b3fdd2 100644 --- a/bukkit-16R3/pom.xml +++ b/bukkit-16R3/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.visualcrafting - 1.0.7 + 1.0.8 4.0.0 diff --git a/bukkit-17R1/pom.xml b/bukkit-17R1/pom.xml index cd55ba7..e016e74 100644 --- a/bukkit-17R1/pom.xml +++ b/bukkit-17R1/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.visualcrafting - 1.0.7 + 1.0.8 4.0.0 diff --git a/bukkit-18R1/pom.xml b/bukkit-18R1/pom.xml index 8fef0f4..165a084 100644 --- a/bukkit-18R1/pom.xml +++ b/bukkit-18R1/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.visualcrafting - 1.0.7 + 1.0.8 4.0.0 diff --git a/bukkit-18R2/pom.xml b/bukkit-18R2/pom.xml index 1a9cc96..154c5aa 100644 --- a/bukkit-18R2/pom.xml +++ b/bukkit-18R2/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.visualcrafting - 1.0.7 + 1.0.8 4.0.0 diff --git a/bukkit-19R1/pom.xml b/bukkit-19R1/pom.xml index 4944e43..3b5db74 100644 --- a/bukkit-19R1/pom.xml +++ b/bukkit-19R1/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.visualcrafting - 1.0.7 + 1.0.8 4.0.0 diff --git a/bukkit-19R2/pom.xml b/bukkit-19R2/pom.xml index 3a3a9f8..84a5639 100644 --- a/bukkit-19R2/pom.xml +++ b/bukkit-19R2/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.visualcrafting - 1.0.7 + 1.0.8 4.0.0 diff --git a/bukkit-19R3/pom.xml b/bukkit-19R3/pom.xml new file mode 100644 index 0000000..2d4d4f2 --- /dev/null +++ b/bukkit-19R3/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + dev.cerus.visualcrafting + parent + 1.0.8 + + + bukkit-19R3 + + + 17 + 17 + UTF-8 + + + + + dev.cerus.visualcrafting + api + ${project.parent.version} + provided + + + + org.bukkit + craftbukkit + 1.19.4-R0.1-SNAPSHOT + provided + + + + \ No newline at end of file diff --git a/bukkit-19R3/src/main/java/dev/cerus/visualcrafting/v19r3/VersionAdapter19R3.java b/bukkit-19R3/src/main/java/dev/cerus/visualcrafting/v19r3/VersionAdapter19R3.java new file mode 100644 index 0000000..1f1ae75 --- /dev/null +++ b/bukkit-19R3/src/main/java/dev/cerus/visualcrafting/v19r3/VersionAdapter19R3.java @@ -0,0 +1,186 @@ +package dev.cerus.visualcrafting.v19r3; + +import dev.cerus.visualcrafting.api.config.Config; +import dev.cerus.visualcrafting.api.version.FakeMap; +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.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.world.entity.EntityTypes; +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_19_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class VersionAdapter19R3 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 = PlayerConnection.class.getDeclaredField("h"); + 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().b); + netMan.m.pipeline() + .addBefore("packet_handler", "visual_crafting", new ChannelDuplexHandler() { + @Override + public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { + if (msg instanceof PacketPlayInUseEntity useEntity) { + VersionAdapter19R3.this.handlePacketIn(player, useEntity); + } + super.channelRead(ctx, msg); + } + }); + } + } + + private void handlePacketIn(final Player player, final PacketPlayInUseEntity packet) { + try { + final Field a = packet.getClass().getDeclaredField("a"); + 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.af, + 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 void updateItemFrame(final int frameId, final ItemStack itemStack, final Rotation rotation, final boolean invisible) { + final PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(frameId, Arrays.asList( + new DataWatcher.b<>(8, DataWatcherRegistry.h, CraftItemStack.asNMSCopy(itemStack)), + new DataWatcher.b<>(9, DataWatcherRegistry.b, rotation.ordinal()), + new DataWatcher.b<>(0, DataWatcherRegistry.a, (byte) (invisible ? 0x20 : 0)) + )); + 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() { + return this.createMap(this.getNewMapId()); + } + + @Override + public void sendMap(final FakeMap map) { + final PacketPlayOutMap packet = new PacketPlayOutMap( + map.getId(), + (byte) 0, + true, + Collections.emptyList(), + new WorldMap.b(0, + 0, + 128, + 128, + this.getMapData(map)) + ); + Bukkit.getOnlinePlayers().forEach(player -> this.sendPacket(player, packet)); + } + + private void sendPacket(final Player player, final Packet packet) { + ((CraftPlayer) player).getHandle().b.a(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 ece4639..fc4522c 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -5,7 +5,7 @@ parent dev.cerus.visualcrafting - 1.0.7 + 1.0.8 4.0.0 @@ -65,6 +65,12 @@ ${project.parent.version} compile + + dev.cerus.visualcrafting + bukkit-19R3 + ${project.parent.version} + compile + dev.cerus.maps 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 c21b75c..d794534 100644 --- a/plugin/src/main/java/dev/cerus/visualcrafting/plugin/VisualCraftingPlugin.java +++ b/plugin/src/main/java/dev/cerus/visualcrafting/plugin/VisualCraftingPlugin.java @@ -13,6 +13,7 @@ import dev.cerus.visualcrafting.v18r2.VersionAdapter18R2; import dev.cerus.visualcrafting.v19r1.VersionAdapter19R1; import dev.cerus.visualcrafting.v19r2.VersionAdapter19R2; +import dev.cerus.visualcrafting.v19r3.VersionAdapter19R3; import java.io.File; import org.bstats.bukkit.Metrics; import org.bstats.charts.SimplePie; @@ -26,8 +27,17 @@ public class VisualCraftingPlugin extends JavaPlugin implements Config { public void onEnable() { this.saveDefaultConfig(); + String version = Bukkit.getVersion(); + version = version.substring(version.indexOf("MC: ") + 4, version.lastIndexOf(')')); + if (!version.matches("\\d+\\.\\d+(\\.\\d+)?")) { + this.getLogger().severe("Could not detect server version for " + version); + this.getPluginLoader().disablePlugin(this); + return; + } + final String major = version.split("\\.")[1]; + // Initialize textures - final File textureCacheFile = new File(this.getDataFolder(), "textures"); + final File textureCacheFile = new File(this.getDataFolder(), "textures_" + major); final TextureCache textureCache = new TextureCache(); if (textureCacheFile.exists()) { textureCache.read(textureCacheFile); @@ -41,8 +51,6 @@ public void onEnable() { } // Initialize version adapter - String version = Bukkit.getVersion(); - version = version.substring(version.indexOf("MC: ") + 4, version.lastIndexOf(')')); final VersionAdapter versionAdapter = switch (version) { case "1.16.5" -> new VersionAdapter16R3(); case "1.17", "1.17.1" -> new VersionAdapter17R1(); @@ -50,6 +58,7 @@ public void onEnable() { case "1.18.2" -> new VersionAdapter18R2(); case "1.19", "1.19.1", "1.19.2" -> new VersionAdapter19R1(); case "1.19.3" -> new VersionAdapter19R2(); + case "1.19.4" -> new VersionAdapter19R3(); default -> null; }; if (versionAdapter == null) { diff --git a/plugin/src/main/java/dev/cerus/visualcrafting/plugin/texture/TextureDownloader.java b/plugin/src/main/java/dev/cerus/visualcrafting/plugin/texture/TextureDownloader.java index b6f216d..24cecc4 100644 --- a/plugin/src/main/java/dev/cerus/visualcrafting/plugin/texture/TextureDownloader.java +++ b/plugin/src/main/java/dev/cerus/visualcrafting/plugin/texture/TextureDownloader.java @@ -25,6 +25,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.imageio.ImageIO; +import org.bukkit.Bukkit; /** * Downloads and converts textures from Mojang @@ -62,7 +63,7 @@ public CompletableFuture downloadTextures(final Logger logger, // Get download url final String clientDownloadUrl; try { - clientDownloadUrl = this.getLatestClientUrl(); + clientDownloadUrl = this.getMatchingClientUrl(); } catch (final IOException e) { future.completeExceptionally(e); return; @@ -126,24 +127,24 @@ private void extractAndConvert(final File folder, } /** - * Gets the latest Minecraft client url from the Minecraft version manifest + * Gets a matching Minecraft client url from the Minecraft version manifest * * @return The latest client download url * * @throws IOException In case of IO errors */ - private String getLatestClientUrl() throws IOException { - // Get version manifest and pull out the latest release id + private String getMatchingClientUrl() throws IOException { + // Get version manifest, get server version final JsonObject versionsObj = new JsonParser().parse(this.get(VERSION_MANIFEST_URL)).getAsJsonObject(); - final JsonObject latestObj = versionsObj.get("latest").getAsJsonObject(); - final String latestRelease = latestObj.get("release").getAsString(); + String version = Bukkit.getVersion(); + version = version.substring(version.indexOf("MC: ") + 4, version.lastIndexOf(')')); // Loop through entries until we find the release String url = null; final JsonArray versionsArr = versionsObj.get("versions").getAsJsonArray(); for (final JsonElement element : versionsArr) { final JsonObject obj = element.getAsJsonObject(); - if (obj.get("id").getAsString().equals(latestRelease)) { + if (obj.get("id").getAsString().equals(version)) { url = obj.get("url").getAsString(); } } diff --git a/pom.xml b/pom.xml index 7281e8e..68a6e5d 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ dev.cerus.visualcrafting parent pom - 1.0.7 + 1.0.8 plugin api @@ -17,6 +17,7 @@ bukkit-18R2 bukkit-19R1 bukkit-19R2 + bukkit-19R3