From fd225647f5dd00709fa79b4f4d611bbb8de52476 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Tue, 4 Jul 2023 17:24:35 +0200 Subject: [PATCH 01/52] Merge between master and legacy/nms --- .idea/.gitignore | 8 - .idea/copyright/SBDevelopment.xml | 6 - .idea/discord.xml | 7 - .idea/encodings.xml | 8 + .idea/misc.xml | 17 +- .idea/vcs.xml | 2 +- API/pom.xml | 183 +++++++++ .../com/bergerkiller/bukkit/common/LICENSE | 0 .../com/bergerkiller/bukkit/common/README.md | 0 .../bukkit/common/io/BitInputStream.java | 0 .../bukkit/common/io/BitPacket.java | 0 .../bukkit/common/map/MapColorPalette.java | 0 .../common/map/color/MCSDBubbleFormat.java | 0 .../common/map/color/MCSDGenBukkit.java | 0 .../common/map/color/MCSDWebbingCodec.java | 0 .../common/map/color/MapColorSpaceData.java | 0 .../mapreflectionapi/MapReflectionAPI.java | 23 +- .../api/AbstractMapWrapper.java | 2 +- .../mapreflectionapi/api/ArrayImage.java | 0 .../mapreflectionapi/api/IMapController.java | 0 .../mapreflectionapi/api/MapController.java | 0 .../mapreflectionapi/api/MapManager.java | 35 +- .../mapreflectionapi/api/MapWrapper.java | 16 + .../api/MultiMapController.java | 0 .../mapreflectionapi/api/MultiMapWrapper.java | 0 .../events/CreateInventoryMapUpdateEvent.java | 0 .../api/events/MapCancelEvent.java | 0 .../api/events/MapContentUpdateEvent.java | 2 +- .../api/events/MapInteractEvent.java | 0 .../api/events/package-info.java | 0 .../exceptions/MapLimitExceededException.java | 0 .../api/exceptions/package-info.java | 0 .../mapreflectionapi/api/package-info.java | 0 .../mapreflectionapi/cmd/MapManagerCMD.java | 0 .../listeners/MapListener.java | 0 .../listeners/PacketListener.java | 85 ++++ .../managers/Configuration.java | 0 .../mapreflectionapi/utils/MainUtil.java | 0 .../utils/ReflectionUtil.java | 0 .../mapreflectionapi/utils/UpdateManager.java | 0 .../mapreflectionapi/utils/YamlFile.java | 0 .../internal/resources/map/map_1_12.bub | Bin .../internal/resources/map/map_1_16.bub | Bin .../internal/resources/map/map_1_17.bub | Bin .../internal/resources/map/map_1_8_8.bub | Bin {src => API/src}/main/resources/config.yml | 0 {src => API/src}/main/resources/plugin.yml | 0 Dist/pom.xml | 129 ++++++ NMS-v1_12_R1/pom.xml | 54 +++ .../nms/MapSender_v1_12_R1.java | 151 +++++++ .../nms/MapWrapper_v1_12_R1.java | 254 ++++++++++++ .../nms/PacketListener_v1_12_R1.java | 128 ++++++ NMS-v1_13_R2/pom.xml | 54 +++ .../nms/MapSender_v1_13_R2.java | 151 +++++++ .../nms/MapWrapper_v1_13_R2.java | 253 ++++++++++++ .../nms/PacketListener_v1_13_R2.java | 128 ++++++ NMS-v1_14_R1/pom.xml | 54 +++ .../nms/MapSender_v1_14_R1.java | 152 +++++++ .../nms/MapWrapper_v1_14_R1.java | 253 ++++++++++++ .../nms/PacketListener_v1_14_R1.java | 128 ++++++ NMS-v1_15_R1/pom.xml | 54 +++ .../nms/MapSender_v1_15_R1.java | 152 +++++++ .../nms/MapWrapper_v1_15_R1.java | 254 ++++++++++++ .../nms/PacketListener_v1_15_R1.java | 128 ++++++ NMS-v1_16_R3/pom.xml | 54 +++ .../nms/MapSender_v1_16_R3.java | 152 +++++++ .../nms/MapWrapper_v1_16_R3.java | 253 ++++++++++++ .../nms/PacketListener_v1_16_R3.java | 128 ++++++ NMS-v1_17_R1/pom.xml | 77 ++++ .../nms/MapSender_v1_17_R1.java | 123 ++++++ .../nms/MapWrapper_v1_17_R1.java | 256 ++++++++++++ .../nms/PacketListener_v1_17_R1.java | 126 ++++++ NMS-v1_18_R2/pom.xml | 77 ++++ .../nms/MapSender_v1_18_R2.java | 123 ++++++ .../nms/MapWrapper_v1_18_R2.java | 256 ++++++++++++ .../nms/PacketListener_v1_18_R2.java | 126 ++++++ NMS-v1_19_R3/pom.xml | 77 ++++ .../nms/MapSender_v1_19_R3.java | 123 ++++++ .../nms/MapWrapper_v1_19_R3.java | 244 +++++++++++ .../nms/PacketListener_v1_19_R3.java | 131 ++++++ NMS-v1_20_R1/pom.xml | 77 ++++ .../nms/MapSender_v1_20_R1.java | 123 ++++++ .../nms/MapWrapper_v1_20_R1.java | 244 +++++++++++ .../nms/PacketListener_v1_20_R1.java | 131 ++++++ TODO | 3 + pom.xml | 165 ++------ .../mapreflectionapi/api/MapSender.java | 165 -------- .../mapreflectionapi/api/MapWrapper.java | 384 ------------------ .../listeners/PacketListener.java | 160 -------- 89 files changed, 5775 insertions(+), 874 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/copyright/SBDevelopment.xml delete mode 100644 .idea/discord.xml create mode 100644 API/pom.xml rename {src => API/src}/main/java/com/bergerkiller/bukkit/common/LICENSE (100%) rename {src => API/src}/main/java/com/bergerkiller/bukkit/common/README.md (100%) rename {src => API/src}/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java (100%) rename {src => API/src}/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java (100%) rename {src => API/src}/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java (100%) rename {src => API/src}/main/java/com/bergerkiller/bukkit/common/map/color/MCSDBubbleFormat.java (100%) rename {src => API/src}/main/java/com/bergerkiller/bukkit/common/map/color/MCSDGenBukkit.java (100%) rename {src => API/src}/main/java/com/bergerkiller/bukkit/common/map/color/MCSDWebbingCodec.java (100%) rename {src => API/src}/main/java/com/bergerkiller/bukkit/common/map/color/MapColorSpaceData.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java (89%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java (99%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java (84%) create mode 100644 API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java (100%) create mode 100644 API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java (100%) rename {src => API/src}/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java (100%) rename {src => API/src}/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_12.bub (100%) rename {src => API/src}/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_16.bub (100%) rename {src => API/src}/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_17.bub (100%) rename {src => API/src}/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub (100%) rename {src => API/src}/main/resources/config.yml (100%) rename {src => API/src}/main/resources/plugin.yml (100%) create mode 100644 Dist/pom.xml create mode 100644 NMS-v1_12_R1/pom.xml create mode 100644 NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java create mode 100644 NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java create mode 100644 NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java create mode 100644 NMS-v1_13_R2/pom.xml create mode 100644 NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java create mode 100644 NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java create mode 100644 NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java create mode 100644 NMS-v1_14_R1/pom.xml create mode 100644 NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java create mode 100644 NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java create mode 100644 NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java create mode 100644 NMS-v1_15_R1/pom.xml create mode 100644 NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java create mode 100644 NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java create mode 100644 NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java create mode 100644 NMS-v1_16_R3/pom.xml create mode 100644 NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java create mode 100644 NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java create mode 100644 NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java create mode 100644 NMS-v1_17_R1/pom.xml create mode 100644 NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java create mode 100644 NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java create mode 100644 NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java create mode 100644 NMS-v1_18_R2/pom.xml create mode 100644 NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java create mode 100644 NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java create mode 100644 NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java create mode 100644 NMS-v1_19_R3/pom.xml create mode 100644 NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java create mode 100644 NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java create mode 100644 NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java create mode 100644 NMS-v1_20_R1/pom.xml create mode 100644 NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java create mode 100644 NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java create mode 100644 NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java create mode 100644 TODO delete mode 100644 src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java delete mode 100644 src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java delete mode 100644 src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/copyright/SBDevelopment.xml b/.idea/copyright/SBDevelopment.xml deleted file mode 100644 index 7471535..0000000 --- a/.idea/copyright/SBDevelopment.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml deleted file mode 100644 index d8e9561..0000000 --- a/.idea/discord.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 337d139..18cec55 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -1,6 +1,8 @@ + + @@ -19,6 +21,12 @@ + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml index 200af21..394e338 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,8 +1,5 @@ - - - @@ -17,20 +14,10 @@ - - - + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/API/pom.xml b/API/pom.xml new file mode 100644 index 0000000..9cd7072 --- /dev/null +++ b/API/pom.xml @@ -0,0 +1,183 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-API + + + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + org.projectlombok + lombok + 1.18.28 + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + + shade + + + false + + + com.bergerkiller.bukkit.common + tech.sbdevelopment.mapreflectionapi.libs.bkcommonlib + + + org.bstats + tech.sbdevelopment.mapreflectionapi.libs.bstats + + + + + + + + org.projectlombok + lombok-maven-plugin + 1.18.20.0 + + ${project.basedir}/src/main/java + ${maven.lombok.delombok-target} + false + + + + generate-sources + + delombok + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.5.0 + + ${jdk.version} + ${maven.lombok.delombok-target} + + **/com/bergerkiller/bukkit/common/io/*.java + **/com/bergerkiller/bukkit/common/map/*.java + **/com/bergerkiller/bukkit/common/map/color/*.java + **/tech/sbdevelopment/mapreflectionapi/*.java + **/tech/sbdevelopment/mapreflectionapi/cmd/*.java + **/tech/sbdevelopment/mapreflectionapi/managers/*.java + **/tech/sbdevelopment/mapreflectionapi/utils/*.java + **/tech/sbdevelopment/mapreflectionapi/listeners/*.java + + + + + + + src/main/resources + true + + plugin.yml + + + + src/main/resources + + plugin.yml + + + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + MG-Dev Jenkins CI Maven Repository + https://ci.mg-dev.eu/plugin/repository/everything + + + dmulloy2-repo + https://repo.dmulloy2.net/repository/public/ + + + + + + org.projectlombok + lombok + 1.18.28 + provided + + + org.bstats + bstats-bukkit + 3.0.2 + compile + + + + + org.jetbrains + annotations-java5 + 23.0.0 + provided + + + io.netty + netty-transport + 4.1.77.Final + provided + + + diff --git a/src/main/java/com/bergerkiller/bukkit/common/LICENSE b/API/src/main/java/com/bergerkiller/bukkit/common/LICENSE similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/LICENSE rename to API/src/main/java/com/bergerkiller/bukkit/common/LICENSE diff --git a/src/main/java/com/bergerkiller/bukkit/common/README.md b/API/src/main/java/com/bergerkiller/bukkit/common/README.md similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/README.md rename to API/src/main/java/com/bergerkiller/bukkit/common/README.md diff --git a/src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java b/API/src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java rename to API/src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java diff --git a/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java b/API/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java rename to API/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java b/API/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java rename to API/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDBubbleFormat.java b/API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDBubbleFormat.java similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDBubbleFormat.java rename to API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDBubbleFormat.java diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDGenBukkit.java b/API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDGenBukkit.java similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDGenBukkit.java rename to API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDGenBukkit.java diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDWebbingCodec.java b/API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDWebbingCodec.java similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDWebbingCodec.java rename to API/src/main/java/com/bergerkiller/bukkit/common/map/color/MCSDWebbingCodec.java diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/color/MapColorSpaceData.java b/API/src/main/java/com/bergerkiller/bukkit/common/map/color/MapColorSpaceData.java similarity index 100% rename from src/main/java/com/bergerkiller/bukkit/common/map/color/MapColorSpaceData.java rename to API/src/main/java/com/bergerkiller/bukkit/common/map/color/MapColorSpaceData.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java similarity index 89% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java index 70b7368..b2a73a9 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java @@ -38,6 +38,7 @@ import java.util.logging.Level; public class MapReflectionAPI extends JavaPlugin { private static MapReflectionAPI instance; private static MapManager mapManager; + private static PacketListener packetListener; /** * Get the plugin instance @@ -89,8 +90,24 @@ public class MapReflectionAPI extends JavaPlugin { getLogger().info("Loading the commands..."); getCommand("mapmanager").setExecutor(new MapManagerCMD()); + getLogger().info("Loading the packet listener..."); + try { + packetListener = PacketListener.construct(this); + } catch (IllegalStateException e) { + getLogger().log(Level.SEVERE, e.getMessage(), e); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + packetListener.init(this); + getLogger().info("Loading the map manager..."); - mapManager = new MapManager(); + try { + mapManager = new MapManager(this); + } catch (IllegalStateException e) { + getLogger().log(Level.SEVERE, e.getMessage(), e); + Bukkit.getPluginManager().disablePlugin(this); + return; + } if (Configuration.getInstance().isAllowVanilla()) { getLogger().info("Vanilla Maps are allowed. Discovering occupied Map IDs..."); @@ -113,7 +130,6 @@ public class MapReflectionAPI extends JavaPlugin { getLogger().info("Registering the listeners..."); Bukkit.getPluginManager().registerEvents(new MapListener(), this); - Bukkit.getPluginManager().registerEvents(new PacketListener(), this); getLogger().info("Loading metrics..."); Metrics metrics = new Metrics(this, 16033); @@ -167,6 +183,9 @@ public class MapReflectionAPI extends JavaPlugin { @Override public void onDisable() { + getLogger().info("Disabling the packet handler..."); + if (packetListener != null) Bukkit.getOnlinePlayers().forEach(p -> packetListener.removePlayer(p)); + getLogger().info("MapReflectionAPI is disabled!"); instance = null; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java similarity index 99% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java index fc44a5b..7efe749 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java @@ -33,4 +33,4 @@ public abstract class AbstractMapWrapper { getController().cancelSend(); getController().clearViewers(); } -} +} \ No newline at end of file diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java similarity index 84% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java index 7120429..d13659f 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java @@ -18,13 +18,17 @@ package tech.sbdevelopment.mapreflectionapi.api; +import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Nullable; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; import tech.sbdevelopment.mapreflectionapi.managers.Configuration; import java.awt.image.BufferedImage; +import java.lang.reflect.InvocationTargetException; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -36,6 +40,25 @@ import java.util.concurrent.CopyOnWriteArrayList; public class MapManager { protected final Set occupiedIds = new HashSet<>(); protected final List managedMaps = new CopyOnWriteArrayList<>(); + private final Class wrapperClass; + + public MapManager(JavaPlugin plugin) throws IllegalStateException { + String packageName = Bukkit.getServer().getClass().getPackage().getName(); + String version = packageName.substring(packageName.lastIndexOf('.') + 1); + + MapReflectionAPI.getInstance().getLogger().info("Initializing the map manager for Minecraft version " + version + "..."); + + try { + final Class clazz = Class.forName("tech.sbdevelopment.mapreflectionapi.nms.MapWrapper_" + version); + if (MapWrapper.class.isAssignableFrom(clazz)) { + wrapperClass = clazz; + } else { + throw new IllegalStateException("Plugin corrupted! Detected invalid MapWrapper class."); + } + } catch (Exception ex) { + throw new IllegalStateException("This Spigot version (" + version + ") is not supported! Contact the developer to get support."); + } + } /** * Get the amount of maps managed by the plugin @@ -124,9 +147,15 @@ public class MapManager { * @return The wrapper */ private MapWrapper wrapNewImage(ArrayImage image) { - MapWrapper wrapper = new MapWrapper(image); - managedMaps.add(wrapper); - return wrapper; + try { + MapWrapper wrapper = (MapWrapper) wrapperClass.getDeclaredConstructor(ArrayImage.class).newInstance(image); + managedMaps.add(wrapper); + return wrapper; + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | + InvocationTargetException e) { + e.printStackTrace(); + return null; + } } /** diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java new file mode 100644 index 0000000..150cad2 --- /dev/null +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -0,0 +1,16 @@ +package tech.sbdevelopment.mapreflectionapi.api; + +public abstract class MapWrapper extends AbstractMapWrapper { + protected ArrayImage content; + + public MapWrapper(ArrayImage image) { + this.content = image; + } + + public ArrayImage getContent() { + return content; + } + + @Override + public abstract MapController getController(); +} \ No newline at end of file diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java index d106f06..2d90799 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java @@ -24,8 +24,8 @@ import lombok.Setter; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; -import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; /** * This event gets fired when the content of a {@link MapWrapper} is updated diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java new file mode 100644 index 0000000..6884b0d --- /dev/null +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -0,0 +1,85 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.listeners; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.Vector; + +public abstract class PacketListener implements Listener { + protected JavaPlugin plugin; + + public static PacketListener construct(JavaPlugin plugin) throws IllegalStateException { + String packageName = Bukkit.getServer().getClass().getPackage().getName(); + String version = packageName.substring(packageName.lastIndexOf('.') + 1); + + plugin.getLogger().info("Initializing the packet handler for Minecraft version " + version + "..."); + + try { + final Class clazz = Class.forName("tech.sbdevelopment.mapreflectionapi.nms.PacketListener_" + version); + if (PacketListener.class.isAssignableFrom(clazz)) { + return (PacketListener) clazz.getDeclaredConstructor().newInstance(); + } else { + throw new IllegalStateException("Plugin corrupted! Detected invalid PacketListener class."); + } + } catch (Exception ex) { + throw new IllegalStateException("This Minecraft version (" + version + ") is not supported! Contact the developer to get support."); + } + } + + public void init(JavaPlugin plugin) { + this.plugin = plugin; + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + @EventHandler + public void onJoin(PlayerJoinEvent e) { + injectPlayer(e.getPlayer()); + } + + @EventHandler + public void onQuit(PlayerQuitEvent e) { + removePlayer(e.getPlayer()); + } + + protected abstract void injectPlayer(Player p); + + public abstract void removePlayer(Player p); + + protected abstract Vector vec3DToVector(Object vec3d); + + protected boolean hasField(Object packet, String field) { + try { + packet.getClass().getDeclaredField(field); + return true; + } catch (NoSuchFieldException ex) { + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java similarity index 100% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java diff --git a/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_12.bub b/API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_12.bub similarity index 100% rename from src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_12.bub rename to API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_12.bub diff --git a/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_16.bub b/API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_16.bub similarity index 100% rename from src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_16.bub rename to API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_16.bub diff --git a/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_17.bub b/API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_17.bub similarity index 100% rename from src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_17.bub rename to API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_17.bub diff --git a/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub b/API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub similarity index 100% rename from src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub rename to API/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub diff --git a/src/main/resources/config.yml b/API/src/main/resources/config.yml similarity index 100% rename from src/main/resources/config.yml rename to API/src/main/resources/config.yml diff --git a/src/main/resources/plugin.yml b/API/src/main/resources/plugin.yml similarity index 100% rename from src/main/resources/plugin.yml rename to API/src/main/resources/plugin.yml diff --git a/Dist/pom.xml b/Dist/pom.xml new file mode 100644 index 0000000..ba1e074 --- /dev/null +++ b/Dist/pom.xml @@ -0,0 +1,129 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + jar + + MapReflectionAPI-Dist + + + ../target + ${project.parent.name}-${project.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.0.0-M2 + + true + + + + org.apache.maven.plugins + maven-shade-plugin + 3.3.0 + + + package + + shade + + + false + + + + + tech.sbdevelopment:MapReflectionAPI* + + + + + + + + + + + + tech.sbdevelopment + MapReflectionAPI-API + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_20_R1 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_19_R3 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_18_R2 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_16_R3 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_17_R1 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_15_R1 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_14_R1 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_13_R2 + ${project.parent.version} + + + tech.sbdevelopment + MapReflectionAPI-NMS-v1_12_R1 + ${project.parent.version} + + + \ No newline at end of file diff --git a/NMS-v1_12_R1/pom.xml b/NMS-v1_12_R1/pom.xml new file mode 100644 index 0000000..a5b2d0f --- /dev/null +++ b/NMS-v1_12_R1/pom.xml @@ -0,0 +1,54 @@ + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_12_R1 + + + 1.12.2-R0.1-SNAPSHOT + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java new file mode 100644 index 0000000..8a00465 --- /dev/null +++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java @@ -0,0 +1,151 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_12_R1.PacketPlayOutMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class MapSender_v1_12_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_12_R1() { + } + + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //??? + new ArrayList<>(), //Icons + content.array, //Data + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY //Y size (2nd Y pos) + ); + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + static final class QueuedMap { + private final int id; + private final ArrayImage image; + private final Player player; + + QueuedMap(int id, ArrayImage image, Player player) { + this.id = id; + this.image = image; + this.player = player; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + QueuedMap that = (QueuedMap) obj; + return this.id == that.id && + Objects.equals(this.image, that.image) && + Objects.equals(this.player, that.player); + } + + @Override + public int hashCode() { + return Objects.hash(id, image, player); + } + + @Override + public String toString() { + return "QueuedMap[" + + "id=" + id + ", " + + "image=" + image + ", " + + "player=" + player + ']'; + } + } +} diff --git a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java new file mode 100644 index 0000000..95278dd --- /dev/null +++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java @@ -0,0 +1,254 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_12_R1.*; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_12_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_12_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_12_R1 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_12_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_12_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_12_R1.sendMap(id, MapWrapper_v1_12_R1.this.content, player); + } else { + MapSender_v1_12_R1.addToQueue(id, MapWrapper_v1_12_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_12_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().defaultContainer.windowId; + + ItemStack stack = new ItemStack(Material.MAP, 1); + net.minecraft.server.v1_12_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, slot, nmsStack); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_12_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + if (bukkitEntity instanceof ItemFrame) return (ItemFrame) bukkitEntity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.server.v1_12_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + if (nmsStack.getTag() == null) nmsStack.setTag(new NBTTagCompound()); //No orCreate on 1.12.2! + nmsStack.getTag().setInt("map", mapId); //getTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "c"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + }; + + public MapWrapper_v1_12_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java new file mode 100644 index 0000000..bffceb2 --- /dev/null +++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java @@ -0,0 +1,128 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.server.v1_12_R1.*; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_12_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class PacketListener_v1_12_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap) { + PacketPlayOutMap packetPlayOutMap = (PacketPlayOutMap) packet; + + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity) { + PacketPlayInUseEntity packetPlayInUseEntity = (PacketPlayInUseEntity) packet; + + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + PacketPlayInUseEntity.EnumEntityUseAction action = packetPlayInUseEntity.a(); //action + EnumHand hand = packetPlayInUseEntity.b(); //hand + Vec3D pos = packetPlayInUseEntity.c(); //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, action.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot) { + PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot = (PacketPlayInSetCreativeSlot) packet; + + int slot = packetPlayInSetCreativeSlot.a(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel.pipeline(); + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel; + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D)) return new Vector(0, 0, 0); + + Vec3D vec3dObj = (Vec3D) vec3d; + return new Vector(vec3dObj.x, vec3dObj.y, vec3dObj.z); + } +} diff --git a/NMS-v1_13_R2/pom.xml b/NMS-v1_13_R2/pom.xml new file mode 100644 index 0000000..abee20e --- /dev/null +++ b/NMS-v1_13_R2/pom.xml @@ -0,0 +1,54 @@ + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_13_R2 + + + 1.13.2-R0.1-SNAPSHOT + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java new file mode 100644 index 0000000..8a52114 --- /dev/null +++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java @@ -0,0 +1,151 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_13_R2.PacketPlayOutMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class MapSender_v1_13_R2 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_13_R2() { + } + + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //??? + new ArrayList<>(), //Icons + content.array, //Data + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY //Y size (2nd Y pos) + ); + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + static final class QueuedMap { + private final int id; + private final ArrayImage image; + private final Player player; + + QueuedMap(int id, ArrayImage image, Player player) { + this.id = id; + this.image = image; + this.player = player; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (QueuedMap) obj; + return this.id == that.id && + Objects.equals(this.image, that.image) && + Objects.equals(this.player, that.player); + } + + @Override + public int hashCode() { + return Objects.hash(id, image, player); + } + + @Override + public String toString() { + return "QueuedMap[" + + "id=" + id + ", " + + "image=" + image + ", " + + "player=" + player + ']'; + } + } +} diff --git a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java new file mode 100644 index 0000000..8192d28 --- /dev/null +++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java @@ -0,0 +1,253 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_13_R2.*; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_13_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_13_R2.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_13_R2 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_13_R2.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_13_R2.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_13_R2.sendMap(id, MapWrapper_v1_13_R2.this.content, player); + } else { + MapSender_v1_13_R2.addToQueue(id, MapWrapper_v1_13_R2.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_13_R2.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().defaultContainer.windowId; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.server.v1_13_R2.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, slot, nmsStack); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_13_R2.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + if (bukkitEntity instanceof ItemFrame) return (ItemFrame) bukkitEntity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.server.v1_13_R2.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.getOrCreateTag().setInt("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "e"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + }; + + public MapWrapper_v1_13_R2(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java new file mode 100644 index 0000000..fd4a1cc --- /dev/null +++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java @@ -0,0 +1,128 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.server.v1_13_R2.*; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_13_R2.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class PacketListener_v1_13_R2 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap) { + PacketPlayOutMap packetPlayOutMap = (PacketPlayOutMap) packet; + + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity) { + PacketPlayInUseEntity packetPlayInUseEntity = (PacketPlayInUseEntity) packet; + + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + PacketPlayInUseEntity.EnumEntityUseAction action = packetPlayInUseEntity.b(); //action + EnumHand hand = packetPlayInUseEntity.c(); //hand + Vec3D pos = packetPlayInUseEntity.d(); //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, action.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot) { + PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot = (PacketPlayInSetCreativeSlot) packet; + + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel.pipeline(); + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel; + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D)) return new Vector(0, 0, 0); + + Vec3D vec3dObj = (Vec3D) vec3d; + return new Vector(vec3dObj.x, vec3dObj.y, vec3dObj.z); + } +} diff --git a/NMS-v1_14_R1/pom.xml b/NMS-v1_14_R1/pom.xml new file mode 100644 index 0000000..13b3f3c --- /dev/null +++ b/NMS-v1_14_R1/pom.xml @@ -0,0 +1,54 @@ + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_14_R1 + + + 1.14.4-R0.1-SNAPSHOT + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java new file mode 100644 index 0000000..f336211 --- /dev/null +++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java @@ -0,0 +1,152 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_14_R1.PacketPlayOutMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class MapSender_v1_14_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_14_R1() { + } + + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Tracking position + false, //Locked + new ArrayList<>(), //Icons + content.array, //Data + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY //Y size (2nd Y pos) + ); + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + static final class QueuedMap { + private final int id; + private final ArrayImage image; + private final Player player; + + QueuedMap(int id, ArrayImage image, Player player) { + this.id = id; + this.image = image; + this.player = player; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (QueuedMap) obj; + return this.id == that.id && + Objects.equals(this.image, that.image) && + Objects.equals(this.player, that.player); + } + + @Override + public int hashCode() { + return Objects.hash(id, image, player); + } + + @Override + public String toString() { + return "QueuedMap[" + + "id=" + id + ", " + + "image=" + image + ", " + + "player=" + player + ']'; + } + } +} diff --git a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java new file mode 100644 index 0000000..7248827 --- /dev/null +++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java @@ -0,0 +1,253 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_14_R1.*; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_14_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_14_R1 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_14_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_14_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_14_R1.sendMap(id, MapWrapper_v1_14_R1.this.content, player); + } else { + MapSender_v1_14_R1.addToQueue(id, MapWrapper_v1_14_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_14_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().defaultContainer.windowId; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.server.v1_14_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, slot, nmsStack); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_14_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + if (bukkitEntity instanceof ItemFrame) return (ItemFrame) bukkitEntity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.server.v1_14_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.getOrCreateTag().setInt("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "ITEM"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + }; + + public MapWrapper_v1_14_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java new file mode 100644 index 0000000..9af8bf3 --- /dev/null +++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java @@ -0,0 +1,128 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.server.v1_14_R1.*; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class PacketListener_v1_14_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap) { + PacketPlayOutMap packetPlayOutMap = (PacketPlayOutMap) packet; + + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity) { + PacketPlayInUseEntity packetPlayInUseEntity = (PacketPlayInUseEntity) packet; + + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + PacketPlayInUseEntity.EnumEntityUseAction action = packetPlayInUseEntity.b(); //action + EnumHand hand = packetPlayInUseEntity.c(); //hand + Vec3D pos = packetPlayInUseEntity.d(); //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, action.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot) { + PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot = (PacketPlayInSetCreativeSlot) packet; + + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel.pipeline(); + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel; + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D)) return new Vector(0, 0, 0); + + Vec3D vec3dObj = (Vec3D) vec3d; + return new Vector(vec3dObj.x, vec3dObj.y, vec3dObj.z); + } +} diff --git a/NMS-v1_15_R1/pom.xml b/NMS-v1_15_R1/pom.xml new file mode 100644 index 0000000..ad1c41b --- /dev/null +++ b/NMS-v1_15_R1/pom.xml @@ -0,0 +1,54 @@ + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_15_R1 + + + 1.15.2-R0.1-SNAPSHOT + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java new file mode 100644 index 0000000..77106f4 --- /dev/null +++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java @@ -0,0 +1,152 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_15_R1.PacketPlayOutMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class MapSender_v1_15_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_15_R1() { + } + + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Tracking position + false, //Locked + new ArrayList<>(), //Icons + content.array, //Data + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY //Y size (2nd Y pos) + ); + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + static final class QueuedMap { + private final int id; + private final ArrayImage image; + private final Player player; + + QueuedMap(int id, ArrayImage image, Player player) { + this.id = id; + this.image = image; + this.player = player; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (QueuedMap) obj; + return this.id == that.id && + Objects.equals(this.image, that.image) && + Objects.equals(this.player, that.player); + } + + @Override + public int hashCode() { + return Objects.hash(id, image, player); + } + + @Override + public String toString() { + return "QueuedMap[" + + "id=" + id + ", " + + "image=" + image + ", " + + "player=" + player + ']'; + } + } +} diff --git a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java new file mode 100644 index 0000000..d1148ed --- /dev/null +++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java @@ -0,0 +1,254 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_15_R1.*; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_15_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_15_R1 extends MapWrapper { + + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_15_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_15_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_15_R1.sendMap(id, MapWrapper_v1_15_R1.this.content, player); + } else { + MapSender_v1_15_R1.addToQueue(id, MapWrapper_v1_15_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_15_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().defaultContainer.windowId; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.server.v1_15_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, slot, nmsStack); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_15_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + if (bukkitEntity instanceof ItemFrame) return (ItemFrame) bukkitEntity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.server.v1_15_R1.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.getOrCreateTag().setInt("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "ITEM"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + }; + + public MapWrapper_v1_15_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java new file mode 100644 index 0000000..deb785d --- /dev/null +++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java @@ -0,0 +1,128 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.server.v1_15_R1.*; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class PacketListener_v1_15_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap) { + PacketPlayOutMap packetPlayOutMap = (PacketPlayOutMap) packet; + + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity) { + PacketPlayInUseEntity packetPlayInUseEntity = (PacketPlayInUseEntity) packet; + + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + PacketPlayInUseEntity.EnumEntityUseAction action = packetPlayInUseEntity.b(); //action + EnumHand hand = packetPlayInUseEntity.c(); //hand + Vec3D pos = packetPlayInUseEntity.d(); //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, action.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot) { + PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot = (PacketPlayInSetCreativeSlot) packet; + + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel.pipeline(); + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel; + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D)) return new Vector(0, 0, 0); + + Vec3D vec3dObj = (Vec3D) vec3d; + return new Vector(vec3dObj.x, vec3dObj.y, vec3dObj.z); + } +} diff --git a/NMS-v1_16_R3/pom.xml b/NMS-v1_16_R3/pom.xml new file mode 100644 index 0000000..ef40e8c --- /dev/null +++ b/NMS-v1_16_R3/pom.xml @@ -0,0 +1,54 @@ + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_16_R3 + + + 1.16.4-R0.1-SNAPSHOT + 11 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java new file mode 100644 index 0000000..a76ceae --- /dev/null +++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java @@ -0,0 +1,152 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_16_R3.PacketPlayOutMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class MapSender_v1_16_R3 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_16_R3() { + } + + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Tracking position + false, //Locked + new ArrayList<>(), //Icons + content.array, //Data + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY //Y size (2nd Y pos) + ); + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + static final class QueuedMap { + private final int id; + private final ArrayImage image; + private final Player player; + + QueuedMap(int id, ArrayImage image, Player player) { + this.id = id; + this.image = image; + this.player = player; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (QueuedMap) obj; + return this.id == that.id && + Objects.equals(this.image, that.image) && + Objects.equals(this.player, that.player); + } + + @Override + public int hashCode() { + return Objects.hash(id, image, player); + } + + @Override + public String toString() { + return "QueuedMap[" + + "id=" + id + ", " + + "image=" + image + ", " + + "player=" + player + ']'; + } + } +} diff --git a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java new file mode 100644 index 0000000..1381753 --- /dev/null +++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java @@ -0,0 +1,253 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.server.v1_16_R3.*; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_16_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_16_R3 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_16_R3.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_16_R3.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_16_R3.sendMap(id, MapWrapper_v1_16_R3.this.content, player); + } else { + MapSender_v1_16_R3.addToQueue(id, MapWrapper_v1_16_R3.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_16_R3.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().defaultContainer.windowId; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.server.v1_16_R3.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, slot, nmsStack); + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_16_R3.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity(); + if (bukkitEntity instanceof ItemFrame) return (ItemFrame) bukkitEntity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.server.v1_16_R3.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.getOrCreateTag().setInt("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "ITEM"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + }; + + public MapWrapper_v1_16_R3(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java new file mode 100644 index 0000000..3549001 --- /dev/null +++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java @@ -0,0 +1,128 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.server.v1_16_R3.*; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class PacketListener_v1_16_R3 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap) { + PacketPlayOutMap packetPlayOutMap = (PacketPlayOutMap) packet; + + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity) { + PacketPlayInUseEntity packetPlayInUseEntity = (PacketPlayInUseEntity) packet; + + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + PacketPlayInUseEntity.EnumEntityUseAction action = packetPlayInUseEntity.b(); //action + EnumHand hand = packetPlayInUseEntity.c(); //hand + Vec3D pos = packetPlayInUseEntity.d(); //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, action.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot) { + PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot = (PacketPlayInSetCreativeSlot) packet; + + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel.pipeline(); + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().playerConnection.networkManager.channel; + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D)) return new Vector(0, 0, 0); + + Vec3D vec3dObj = (Vec3D) vec3d; + return new Vector(vec3dObj.x, vec3dObj.y, vec3dObj.z); + } +} diff --git a/NMS-v1_17_R1/pom.xml b/NMS-v1_17_R1/pom.xml new file mode 100644 index 0000000..7fbb484 --- /dev/null +++ b/NMS-v1_17_R1/pom.xml @@ -0,0 +1,77 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_17_R1 + + + 1.17.1-R0.1-SNAPSHOT + 17 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java new file mode 100644 index 0000000..2022165 --- /dev/null +++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java @@ -0,0 +1,123 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +public class MapSender_v1_17_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_17_R1() { + } + + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().b.sendPacket(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} \ No newline at end of file diff --git a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java new file mode 100644 index 0000000..1bea945 --- /dev/null +++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java @@ -0,0 +1,256 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.network.syncher.DataWatcherObject; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_17_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_17_R1 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_17_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_17_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_17_R1.sendMap(id, MapWrapper_v1_17_R1.this.content, player); + } else { + MapSender_v1_17_R1.addToQueue(id, MapWrapper_v1_17_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_17_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().bU.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().bU.getStateId(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().b.sendPacket(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_17_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().getEntity(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.getOrCreateTag().setInt("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "ao"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().b.sendPacket(packet); + } + }; + + public MapWrapper_v1_17_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} \ No newline at end of file diff --git a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java new file mode 100644 index 0000000..ff07b17 --- /dev/null +++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_17_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.getItemStack(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().b.a.k.pipeline(); //connection connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().b.a.k; //connection connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.b, vec3dObj.c, vec3dObj.d); //x, y, z + } +} \ No newline at end of file diff --git a/NMS-v1_18_R2/pom.xml b/NMS-v1_18_R2/pom.xml new file mode 100644 index 0000000..9ac89c4 --- /dev/null +++ b/NMS-v1_18_R2/pom.xml @@ -0,0 +1,77 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_18_R2 + + + 1.18.2-R0.1-SNAPSHOT + 17 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java new file mode 100644 index 0000000..631bbd7 --- /dev/null +++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java @@ -0,0 +1,123 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +public class MapSender_v1_18_R2 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_18_R2() { + } + + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().b.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} \ No newline at end of file diff --git a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java new file mode 100644 index 0000000..0908b8c --- /dev/null +++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java @@ -0,0 +1,256 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.network.syncher.DataWatcherObject; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_18_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.getDeclaredField; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.setDeclaredField; + +public class MapWrapper_v1_18_R2 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_18_R2.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_18_R2.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_18_R2.sendMap(id, MapWrapper_v1_18_R2.this.content, player); + } else { + MapSender_v1_18_R2.addToQueue(id, MapWrapper_v1_18_R2.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_18_R2.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().bU.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().bU.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().b.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_18_R2.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.u().a("map", mapId); //getOrCreateTag putInt + + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, new DataWatcher(null), true); + + try { + List> list = new ArrayList<>(); + DataWatcherObject dataWatcherObject = (DataWatcherObject) getDeclaredField(EntityItemFrame.class, "ao"); + DataWatcher.Item dataWatcherItem = new DataWatcher.Item<>(dataWatcherObject, nmsStack); + list.add(dataWatcherItem); + setDeclaredField(packet, "b", list); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + ((CraftPlayer) player).getHandle().b.a(packet); + } + }; + + public MapWrapper_v1_18_R2(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java new file mode 100644 index 0000000..4123dd2 --- /dev/null +++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_18_R2 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.c(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + ChannelPipeline pipeline = ((CraftPlayer) p).getHandle().b.a.m.pipeline(); //connection connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + Channel channel = ((CraftPlayer) p).getHandle().b.a.m; //connection connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.b, vec3dObj.c, vec3dObj.d); //x, y, z + } +} diff --git a/NMS-v1_19_R3/pom.xml b/NMS-v1_19_R3/pom.xml new file mode 100644 index 0000000..b14f9a9 --- /dev/null +++ b/NMS-v1_19_R3/pom.xml @@ -0,0 +1,77 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_19_R3 + + + 1.19.4-R0.1-SNAPSHOT + 17 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java new file mode 100644 index 0000000..1da9363 --- /dev/null +++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java @@ -0,0 +1,123 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +public class MapSender_v1_19_R3 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_19_R3() { + } + + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().b.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java new file mode 100644 index 0000000..afd651b --- /dev/null +++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java @@ -0,0 +1,244 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_19_R3 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_19_R3.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_19_R3.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_19_R3.sendMap(id, MapWrapper_v1_19_R3.this.content, player); + } else { + MapSender_v1_19_R3.addToQueue(id, MapWrapper_v1_19_R3.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_19_R3.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().bO.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().bO.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().b.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_19_R3.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.v().a("map", mapId); //getOrCreateTag putInt + + List> list = new ArrayList<>(); + DataWatcher.b dataWatcherItem = DataWatcher.b.a(EntityItemFrame.g, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().b.a(packet); + } + }; + + public MapWrapper_v1_19_R3(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java new file mode 100644 index 0000000..4e8f0db --- /dev/null +++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java @@ -0,0 +1,131 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +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.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_19_R3 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.a(); + ItemStack item = packetPlayInSetCreativeSlot.c(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().b, "h"); + ChannelPipeline pipeline = networkManager.m.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().b, "h"); + Channel channel = networkManager.m; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.c, vec3dObj.d, vec3dObj.e); //x, y, z + } +} diff --git a/NMS-v1_20_R1/pom.xml b/NMS-v1_20_R1/pom.xml new file mode 100644 index 0000000..50dd21d --- /dev/null +++ b/NMS-v1_20_R1/pom.xml @@ -0,0 +1,77 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_20_R1 + + + 1.20.1-R0.1-SNAPSHOT + 17 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java new file mode 100644 index 0000000..aae89aa --- /dev/null +++ b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java @@ -0,0 +1,123 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +public class MapSender_v1_20_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_20_R1() { + } + + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().c.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java new file mode 100644 index 0000000..b95f9dc --- /dev/null +++ b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java @@ -0,0 +1,244 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_20_R1 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_20_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_20_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_20_R1.sendMap(id, MapWrapper_v1_20_R1.this.content, player); + } else { + MapSender_v1_20_R1.addToQueue(id, MapWrapper_v1_20_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_20_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().bQ.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().bQ.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().c.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_20_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.v().a("map", mapId); //getOrCreateTag putInt + + List> list = new ArrayList<>(); + DataWatcher.b dataWatcherItem = DataWatcher.b.a(EntityItemFrame.g, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().c.a(packet); + } + }; + + public MapWrapper_v1_20_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java new file mode 100644 index 0000000..2511259 --- /dev/null +++ b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java @@ -0,0 +1,131 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_20_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.a(); + ItemStack item = packetPlayInSetCreativeSlot.c(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + ChannelPipeline pipeline = networkManager.m.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + Channel channel = networkManager.m; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.c, vec3dObj.d, vec3dObj.e); //x, y, z + } +} diff --git a/TODO b/TODO new file mode 100644 index 0000000..ea15269 --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +- replace _V1xxxxx by package +- bump version +- test \ No newline at end of file diff --git a/pom.xml b/pom.xml index 361046c..19485ab 100644 --- a/pom.xml +++ b/pom.xml @@ -24,18 +24,33 @@ tech.sbdevelopment MapReflectionAPI - 1.4.4 - jar - + pom + ${revision} + MapReflectionAPI This API helps developer with viewing images on maps. https://sbdplugins.nl + 1.4.4 UTF-8 - ${project.build.directory}/javadoc-delombok + 11 + + API + Dist + NMS-v1_20_R1 + NMS-v1_19_R3 + NMS-v1_18_R2 + NMS-v1_17_R1 + NMS-v1_16_R3 + NMS-v1_15_R1 + NMS-v1_14_R1 + NMS-v1_13_R2 + NMS-v1_12_R1 + + sbdevelopment-repo @@ -44,102 +59,46 @@ + clean package org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - 11 - - - org.projectlombok - lombok - 1.18.24 - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.3.0 + maven-toolchains-plugin + 3.1.0 - package - shade - - - false - - - com.bergerkiller.bukkit.common - tech.sbdevelopment.mapreflectionapi.libs.bkcommonlib - - - org.bstats - tech.sbdevelopment.mapreflectionapi.libs.bstats - - - - - - - - org.projectlombok - lombok-maven-plugin - 1.18.20.0 - - ${project.basedir}/src/main/java - ${maven.lombok.delombok-target} - false - - - - generate-sources - - delombok + toolchain + + + + ${jdk.version} + + + org.apache.maven.plugins - maven-javadoc-plugin - 3.4.0 + maven-compiler-plugin + 3.11.0 - 11 - ${maven.lombok.delombok-target} - - **/com/bergerkiller/bukkit/common/io/*.java - **/com/bergerkiller/bukkit/common/map/*.java - **/com/bergerkiller/bukkit/common/map/color/*.java - **/tech/sbdevelopment/mapreflectionapi/*.java - **/tech/sbdevelopment/mapreflectionapi/cmd/*.java - **/tech/sbdevelopment/mapreflectionapi/managers/*.java - **/tech/sbdevelopment/mapreflectionapi/utils/*.java - **/tech/sbdevelopment/mapreflectionapi/listeners/*.java - + ${jdk.version} + + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true - - - src/main/resources - true - - plugin.yml - - - - src/main/resources - - plugin.yml - - - @@ -147,49 +106,13 @@ spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - MG-Dev Jenkins CI Maven Repository - https://ci.mg-dev.eu/plugin/repository/everything - - - dmulloy2-repo - https://repo.dmulloy2.net/repository/public/ - org.spigotmc spigot-api - 1.19.4-R0.1-SNAPSHOT - provided - - - org.projectlombok - lombok - 1.18.24 - provided - - - - org.bstats - bstats-bukkit - 3.0.0 - compile - - - - - org.jetbrains - annotations-java5 - 23.0.0 - provided - - - io.netty - netty-transport - 4.1.77.Final - provided + 1.20.1-R0.1-SNAPSHOT - \ No newline at end of file + diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java deleted file mode 100644 index 56fcaed..0000000 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package tech.sbdevelopment.mapreflectionapi.api; - -import lombok.Data; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; -import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; - -import java.util.ArrayList; -import java.util.List; - -/** - * The {@link MapSender} sends the Map packets to players. - */ -public class MapSender { - private static final List sendQueue = new ArrayList<>(); - private static int senderID = -1; - - private MapSender() { - } - - /** - * Add a map to the send queue - * - * @param id The ID of the map - * @param content The {@link ArrayImage} to view on the map - * @param player The {@link Player} to view for - */ - public static void addToQueue(final int id, final ArrayImage content, final Player player) { - QueuedMap toSend = new QueuedMap(id, content, player); - if (sendQueue.contains(toSend)) return; - sendQueue.add(toSend); - - runSender(); - } - - /** - * Cancels a senderID in the sender queue - * - * @param s The senderID to cancel - */ - public static void cancelID(int s) { - sendQueue.removeIf(queuedMap -> queuedMap.id == s); - } - - /** - * Run the sender task - */ - private static void runSender() { - if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) - return; - - senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { - if (sendQueue.isEmpty()) return; - - for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { - QueuedMap current = sendQueue.get(0); - if (current == null) return; - - sendMap(current.id, current.image, current.player); - - if (!sendQueue.isEmpty()) sendQueue.remove(0); - } - }, 0, 2); - } - - private static final Class packetPlayOutMapClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutMap"); - private static final Class worldMapData = ReflectionUtil.supports(17) ? ReflectionUtil.getNMSClass("world.level.saveddata.maps", "WorldMap$b") : null; - - /** - * Send a map to a player - * - * @param id0 The ID of the map - * @param content The {@link ArrayImage} to view on the map - * @param player The {@link Player} to view for - */ - public static void sendMap(final int id0, final ArrayImage content, final Player player) { - if (player == null || !player.isOnline()) { - List toRemove = new ArrayList<>(); - for (QueuedMap qMap : sendQueue) { - if (qMap == null) continue; - - if (qMap.player == null || !qMap.player.isOnline()) { - toRemove.add(qMap); - } - } - Bukkit.getScheduler().cancelTask(senderID); - sendQueue.removeAll(toRemove); - - return; - } - - final int id = -id0; - Object packet; - if (ReflectionUtil.supports(17)) { //1.17+ - Object updateData = ReflectionUtil.callConstructor(worldMapData, - content.minX, //X pos - content.minY, //Y pos - content.maxX, //X size (2nd X pos) - content.maxY, //Y size (2nd Y pos) - content.array //Data - ); - - packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, - id, //ID - (byte) 0, //Scale, 0 = 1 block per pixel - false, //Show icons - new ReflectionUtil.CollectionParam<>(), //Icons - updateData - ); - } else if (ReflectionUtil.supports(14)) { //1.16-1.14 - packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, - id, //ID - (byte) 0, //Scale, 0 = 1 block per pixel - false, //Tracking position - false, //Locked - new ReflectionUtil.CollectionParam<>(), //Icons - content.array, //Data - content.minX, //X pos - content.minY, //Y pos - content.maxX, //X size (2nd X pos) - content.maxY //Y size (2nd Y pos) - ); - } else { //1.13- - packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, - id, //ID - (byte) 0, //Scale, 0 = 1 block per pixel - false, //??? - new ReflectionUtil.CollectionParam<>(), //Icons - content.array, //Data - content.minX, //X pos - content.minY, //Y pos - content.maxX, //X size (2nd X pos) - content.maxY //Y size (2nd Y pos) - ); - } - - ReflectionUtil.sendPacket(player, packet); - } - - @Data - static final class QueuedMap { - private final int id; - private final ArrayImage image; - private final Player player; - } -} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java deleted file mode 100644 index 76d828a..0000000 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * This file is part of MapReflectionAPI. - * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package tech.sbdevelopment.mapreflectionapi.api; - -import org.bukkit.*; -import org.bukkit.entity.ItemFrame; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.metadata.FixedMetadataValue; -import org.jetbrains.annotations.NotNull; -import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; -import tech.sbdevelopment.mapreflectionapi.api.events.MapContentUpdateEvent; -import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; -import tech.sbdevelopment.mapreflectionapi.managers.Configuration; -import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * A {@link MapWrapper} wraps one image. - */ -public class MapWrapper extends AbstractMapWrapper { - private static final String REFERENCE_METADATA = "MAP_WRAPPER_REF"; - protected ArrayImage content; - - private static final Material MAP_MATERIAL; - - static { - MAP_MATERIAL = ReflectionUtil.supports(13) ? Material.FILLED_MAP : Material.MAP; - } - - /** - * Construct a new {@link MapWrapper} - * - * @param image The {@link ArrayImage} to wrap - */ - public MapWrapper(ArrayImage image) { - this.content = image; - } - - private static final Class craftStackClass = ReflectionUtil.getCraftClass("inventory.CraftItemStack"); - private static final Class setSlotPacketClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutSetSlot"); - private static final Class entityClass = ReflectionUtil.getNMSClass("world.entity", "Entity"); - private static final Class dataWatcherClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher"); - private static final Class entityMetadataPacketClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata"); - private static final Class entityItemFrameClass = ReflectionUtil.getNMSClass("world.entity.decoration", "EntityItemFrame"); - private static final Class dataWatcherItemClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher$Item"); - - protected MapController controller = new MapController() { - private final Map viewers = new HashMap<>(); - - @Override - public void addViewer(Player player) throws MapLimitExceededException { - if (!isViewing(player)) { - viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); - } - } - - @Override - public void removeViewer(OfflinePlayer player) { - viewers.remove(player.getUniqueId()); - } - - @Override - public void clearViewers() { - for (UUID uuid : viewers.keySet()) { - viewers.remove(uuid); - } - } - - @Override - public boolean isViewing(OfflinePlayer player) { - if (player == null) return false; - return viewers.containsKey(player.getUniqueId()); - } - - @Override - public int getMapId(OfflinePlayer player) { - if (isViewing(player)) { - return viewers.get(player.getUniqueId()); - } - return -1; - } - - @Override - public void update(@NotNull ArrayImage content) { - MapContentUpdateEvent event = new MapContentUpdateEvent(MapWrapper.this, content); - Bukkit.getPluginManager().callEvent(event); - - if (Configuration.getInstance().isImageCache()) { - MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); - if (duplicate != null) { - MapWrapper.this.content = duplicate.getContent(); - return; - } - } - - MapWrapper.this.content = content; - - if (event.isSendContent()) { - for (UUID id : viewers.keySet()) { - sendContent(Bukkit.getPlayer(id)); - } - } - } - - @Override - public void sendContent(Player player) { - sendContent(player, false); - } - - @Override - public void sendContent(Player player, boolean withoutQueue) { - if (!isViewing(player)) return; - - int id = getMapId(player); - if (withoutQueue) { - MapSender.sendMap(id, MapWrapper.this.content, player); - } else { - MapSender.addToQueue(id, MapWrapper.this.content, player); - } - } - - @Override - public void cancelSend() { - for (int s : viewers.values()) { - MapSender.cancelID(s); - } - } - - @Override - public void showInInventory(Player player, int slot, boolean force) { - if (!isViewing(player)) return; - - if (player.getGameMode() == GameMode.CREATIVE && !force) return; - - if (slot < 9) { - slot += 36; - } else if (slot > 35 && slot != 45) { - slot = 8 - (slot - 36); - } - - String inventoryMenuName; - if (ReflectionUtil.supports(20)) { //1.20 - inventoryMenuName = "bQ"; - } else if (ReflectionUtil.supports(19)) { //1.19 - inventoryMenuName = ReflectionUtil.VER_MINOR == 3 ? "bO" : "bT"; //1.19.4 = bO, >= 1.19.3 = bT - } else if (ReflectionUtil.supports(18)) { //1.18 - inventoryMenuName = ReflectionUtil.VER_MINOR == 1 ? "bV" : "bU"; //1.18.1 = ap, 1.18(.2) = ao - } else if (ReflectionUtil.supports(17)) { //1.17, same as 1.18(.2) - inventoryMenuName = "bU"; - } else { //1.12-1.16 - inventoryMenuName = "defaultContainer"; - } - Object inventoryMenu = ReflectionUtil.getField(ReflectionUtil.getHandle(player), inventoryMenuName); - - ItemStack stack; - if (ReflectionUtil.supports(13)) { - stack = new ItemStack(MAP_MATERIAL, 1); - } else { - stack = new ItemStack(MAP_MATERIAL, 1, (short) getMapId(player)); - } - - Object nmsStack = createCraftItemStack(stack, (short) getMapId(player)); - - Object packet; - if (ReflectionUtil.supports(17)) { //1.17+ - int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, ReflectionUtil.supports(18) ? "j" : "getStateId"); - - packet = ReflectionUtil.callConstructor(setSlotPacketClass, - 0, //0 = Player inventory - stateId, - slot, - nmsStack - ); - } else { //1.16- - packet = ReflectionUtil.callConstructor(setSlotPacketClass, - 0, //0 = Player inventory - slot, - nmsStack - ); - } - - ReflectionUtil.sendPacketSync(player, packet); - } - - @Override - public void showInInventory(Player player, int slot) { - showInInventory(player, slot, false); - } - - @Override - public void showInHand(Player player, boolean force) { - if (player.getInventory().getItemInMainHand().getType() != MAP_MATERIAL && !force) - return; - - showInInventory(player, player.getInventory().getHeldItemSlot(), force); - } - - @Override - public void showInHand(Player player) { - showInHand(player, false); - } - - @Override - public void showInFrame(Player player, ItemFrame frame) { - showInFrame(player, frame, false); - } - - @Override - public void showInFrame(Player player, ItemFrame frame, boolean force) { - if (frame.getItem().getType() != MAP_MATERIAL && !force) - return; - - showInFrame(player, frame.getEntityId()); - } - - @Override - public void showInFrame(Player player, int entityId) { - showInFrame(player, entityId, null); - } - - @Override - public void showInFrame(Player player, int entityId, String debugInfo) { - if (!isViewing(player)) return; - - ItemStack stack = new ItemStack(MAP_MATERIAL, 1); - if (debugInfo != null) { - ItemMeta itemMeta = stack.getItemMeta(); - itemMeta.setDisplayName(debugInfo); - stack.setItemMeta(itemMeta); - } - - Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { - ItemFrame frame = getItemFrameById(player.getWorld(), entityId); - if (frame != null) { - frame.removeMetadata(REFERENCE_METADATA, MapReflectionAPI.getInstance()); - frame.setMetadata(REFERENCE_METADATA, new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper.this)); - } - - sendItemFramePacket(player, entityId, stack, getMapId(player)); - }); - } - - @Override - public void clearFrame(Player player, int entityId) { - sendItemFramePacket(player, entityId, null, -1); - Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { - ItemFrame frame = getItemFrameById(player.getWorld(), entityId); - if (frame != null) frame.removeMetadata(REFERENCE_METADATA, MapReflectionAPI.getInstance()); - }); - } - - @Override - public void clearFrame(Player player, ItemFrame frame) { - clearFrame(player, frame.getEntityId()); - } - - @Override - public ItemFrame getItemFrameById(World world, int entityId) { - Object worldHandle = ReflectionUtil.getHandle(world); - Object nmsEntity = ReflectionUtil.callMethod(worldHandle, ReflectionUtil.supports(18) ? "a" : "getEntity", entityId); - if (nmsEntity == null) return null; - - Object craftEntity = ReflectionUtil.callMethod(nmsEntity, "getBukkitEntity"); - if (craftEntity == null) return null; - - Class itemFrameClass = ReflectionUtil.getNMSClass("world.entity.decoration", "EntityItemFrame"); - if (itemFrameClass == null) return null; - - if (craftEntity.getClass().isAssignableFrom(itemFrameClass)) - return (ItemFrame) itemFrameClass.cast(craftEntity); - - return null; - } - - private Object createCraftItemStack(@NotNull ItemStack stack, int mapId) { - if (mapId < 0) return null; - - Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack); - - if (ReflectionUtil.supports(13)) { - String nbtObjectName; - if (ReflectionUtil.supports(19)) { //1.19 - nbtObjectName = "v"; - } else if (ReflectionUtil.supports(18)) { //1.18 - nbtObjectName = ReflectionUtil.VER_MINOR == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u - } else { //1.13 - 1.17 - nbtObjectName = "getOrCreateTag"; - } - Object nbtObject = ReflectionUtil.callMethod(nmsStack, nbtObjectName); - ReflectionUtil.callMethod(nbtObject, ReflectionUtil.supports(18) ? "a" : "setInt", "map", mapId); - } - return nmsStack; - } - - private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { - Object nmsStack = createCraftItemStack(stack, mapId); - - String dataWatcherObjectName; - if (ReflectionUtil.supports(19)) { //1.19 - dataWatcherObjectName = ReflectionUtil.VER_MINOR == 3 ? "g" : "ao"; //1.19.4 = g, >= 1.19.3 = ao - } else if (ReflectionUtil.supports(18)) { //1.18 - dataWatcherObjectName = ReflectionUtil.VER_MINOR == 1 ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao - } else if (ReflectionUtil.supports(17)) { //1.17 - dataWatcherObjectName = "ao"; - } else if (ReflectionUtil.supports(14)) { //1.14 - 1.16 - dataWatcherObjectName = "ITEM"; - } else if (ReflectionUtil.supports(13)) { //1.13 - dataWatcherObjectName = "e"; - } else { //1.12 - dataWatcherObjectName = "c"; - } - Object dataWatcherObject = ReflectionUtil.getDeclaredField(entityItemFrameClass, dataWatcherObjectName); - - ReflectionUtil.ListParam list = new ReflectionUtil.ListParam<>(); - - Object packet; - if (ReflectionUtil.supports(19, 2)) { //1.19.3 - Class dataWatcherRecordClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher$b"); - // Sadly not possible to use ReflectionUtil (in its current state), because of the Object parameter - Object dataWatcherItem; - try { - Method m = dataWatcherRecordClass.getMethod("a", dataWatcherObject.getClass(), Object.class); - m.setAccessible(true); - dataWatcherItem = m.invoke(null, dataWatcherObject, nmsStack); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { - ex.printStackTrace(); - return; - } - list.add(dataWatcherItem); - - packet = ReflectionUtil.callConstructor(entityMetadataPacketClass, - entityId, - list - ); - } else { //1.19.2 or lower - Object dataWatcher = ReflectionUtil.callConstructorNull(dataWatcherClass, entityClass); - - packet = ReflectionUtil.callConstructor(entityMetadataPacketClass, - entityId, - dataWatcher, //dummy watcher! - true - ); - - Object dataWatcherItem = ReflectionUtil.callFirstConstructor(dataWatcherItemClass, dataWatcherObject, nmsStack); - list.add(dataWatcherItem); - ReflectionUtil.setDeclaredField(packet, "b", list); - } - - ReflectionUtil.sendPacketSync(player, packet); - } - }; - - public ArrayImage getContent() { - return content; - } - - @Override - public MapController getController() { - return controller; - } -} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java deleted file mode 100644 index 3785599..0000000 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * This file is part of MapReflectionAPI. - * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package tech.sbdevelopment.mapreflectionapi.listeners; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelDuplexHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; -import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; -import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; -import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; -import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; -import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; - -import java.util.concurrent.TimeUnit; - -import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; - -public class PacketListener implements Listener { - private static final Class packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap"); - private static final Class packetPlayInUseEntityClass = getNMSClass("network.protocol.game", "PacketPlayInUseEntity"); - private static final Class packetPlayInSetCreativeSlotClass = getNMSClass("network.protocol.game", "PacketPlayInSetCreativeSlot"); - private static final Class vec3DClass = getNMSClass("world.phys", "Vec3D"); - private static final Class craftStackClass = getCraftClass("inventory.CraftItemStack"); - - @EventHandler - public void onJoin(PlayerJoinEvent e) { - injectPlayer(e.getPlayer()); - } - - @EventHandler - public void onQuit(PlayerQuitEvent e) { - removePlayer(e.getPlayer()); - } - - private void injectPlayer(Player player) { - ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { - @Override - public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { - if (packet.getClass().isAssignableFrom(packetPlayOutMapClass)) { - Object packetPlayOutMap = packetPlayOutMapClass.cast(packet); - - int id = (int) getDeclaredField(packetPlayOutMap, "a"); - if (id < 0) { - int newId = -id; - setDeclaredField(packetPlayOutMap, "a", newId); - } else { - boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); - MapCancelEvent event = new MapCancelEvent(player, id, async); - if (MapReflectionAPI.getMapManager().isIdUsedBy(player, id)) event.setCancelled(true); - if (event.getHandlers().getRegisteredListeners().length > 0) - Bukkit.getPluginManager().callEvent(event); - - if (event.isCancelled()) return; - } - } - - super.write(ctx, packet, promise); - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { - if (packet.getClass().isAssignableFrom(packetPlayInUseEntityClass)) { - Object packetPlayInEntity = packetPlayInUseEntityClass.cast(packet); - - int entityId = (int) getDeclaredField(packetPlayInEntity, "a"); - - Enum actionEnum; - Enum hand; - Object pos; - if (ReflectionUtil.supports(17)) { - Object action = getDeclaredField(packetPlayInEntity, "b"); - actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type - hand = hasField(action, "a") ? (Enum) getDeclaredField(action, "a") : null; - pos = hasField(action, "b") ? getDeclaredField(action, "b") : null; - } else { - actionEnum = (Enum) callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "b" : "a"); //1.13 = b, 1.12 = a - hand = (Enum) callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "c" : "b"); //1.13 = c, 1.12 = b - pos = callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "d" : "c"); //1.13 = d, 1.12 = c - } - - if (Bukkit.getScheduler().callSyncMethod(MapReflectionAPI.getInstance(), () -> { - boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); - MapInteractEvent event = new MapInteractEvent(player, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); - if (event.getFrame() != null && event.getMapWrapper() != null) { - Bukkit.getPluginManager().callEvent(event); - return event.isCancelled(); - } - return false; - }).get(1, TimeUnit.SECONDS)) return; - } else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) { - Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet); - - int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, ReflectionUtil.supports(19, 3) ? "a" : ReflectionUtil.supports(13) ? "b" : "a"); //1.19.4 = a, 1.19.3 - 1.13 = b, 1.12 = a - Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, ReflectionUtil.supports(18) ? "c" : "getItemStack"); //1.18 = c, 1.17 = getItemStack - ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack); - - boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); - CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(player, slot, craftStack, async); - if (event.getMapWrapper() != null) { - Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) return; - } - } - - super.channelRead(ctx, packet); - } - }; - - Channel channel = getChannel(player); - channel.pipeline().addBefore("packet_handler", player.getName(), channelDuplexHandler); - } - - private void removePlayer(Player player) { - Channel channel = getChannel(player); - channel.eventLoop().submit(() -> channel.pipeline().remove(player.getName())); - } - - private Channel getChannel(Player player) { - Object playerHandle = getHandle(player); - Object playerConnection = getDeclaredField(playerHandle, ReflectionUtil.supports(20) ? "c" : ReflectionUtil.supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection - Object networkManager = getDeclaredField(playerConnection, ReflectionUtil.supports(19, 3) ? "h" : ReflectionUtil.supports(19) ? "b" : ReflectionUtil.supports(17) ? "a" : "networkManager"); //1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager - return (Channel) getDeclaredField(networkManager, ReflectionUtil.supports(18) ? "m" : ReflectionUtil.supports(17) ? "k" : "channel"); //1.19 & 1.18 = m, 1.17 = k, 1.16 = channel - } - - private Vector vec3DToVector(Object vec3d) { - if (!(vec3d.getClass().isAssignableFrom(vec3DClass))) return new Vector(0, 0, 0); - - Object vec3dNMS = vec3DClass.cast(vec3d); - double x = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "c" : ReflectionUtil.supports(17) ? "b" : "x"); //1.19 = c, 1.18 = b, 1.16 = x - double y = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "d" : ReflectionUtil.supports(17) ? "c" : "y"); //1.19 = d, 1.18 = c, 1.16 = y - double z = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "e" : ReflectionUtil.supports(17) ? "d" : "z"); //1.19 = e, 1.18 = d, 1.16 = z - - return new Vector(x, y, z); - } -} From 6840d732ad7925f990e62f2b7f8f2f3e8c8b8bf1 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Tue, 4 Jul 2023 17:26:57 +0200 Subject: [PATCH 02/52] Restored some .idea files --- .idea/.gitignore | 8 ++++++++ .idea/discord.xml | 7 +++++++ .idea/encodings.xml | 6 ------ .idea/misc.xml | 6 ------ TODO | 3 --- 5 files changed, 15 insertions(+), 15 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/discord.xml delete mode 100644 TODO diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ecca007 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..a672138 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 18cec55..67b333b 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -19,15 +19,9 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 394e338..fadb731 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -12,12 +12,6 @@ - \ No newline at end of file diff --git a/TODO b/TODO deleted file mode 100644 index ea15269..0000000 --- a/TODO +++ /dev/null @@ -1,3 +0,0 @@ -- replace _V1xxxxx by package -- bump version -- test \ No newline at end of file From 6db8a11f5c0b66e5e59a7368bd3407e9dffc2fcb Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Tue, 4 Jul 2023 17:31:22 +0200 Subject: [PATCH 03/52] Updated copyright --- .github/workflows/maven.yml | 2 +- API/pom.xml | 27 ++++++++----------- .../bukkit/common/io/BitInputStream.java | 2 +- .../bukkit/common/io/BitPacket.java | 2 +- .../bukkit/common/map/MapColorPalette.java | 2 +- .../common/map/color/MCSDGenBukkit.java | 2 +- .../common/map/color/MCSDWebbingCodec.java | 2 +- .../common/map/color/MapColorSpaceData.java | 2 +- .../api/AbstractMapWrapper.java | 2 +- .../mapreflectionapi/api/ArrayImage.java | 2 +- .../mapreflectionapi/api/IMapController.java | 2 +- .../mapreflectionapi/api/MapManager.java | 2 +- .../mapreflectionapi/api/MapWrapper.java | 18 +++++++++++++ .../events/CreateInventoryMapUpdateEvent.java | 2 +- .../api/events/MapCancelEvent.java | 2 +- .../api/events/MapContentUpdateEvent.java | 4 +-- .../api/events/MapInteractEvent.java | 2 +- .../api/events/package-info.java | 2 +- .../exceptions/MapLimitExceededException.java | 2 +- .../api/exceptions/package-info.java | 2 +- .../mapreflectionapi/api/package-info.java | 2 +- .../mapreflectionapi/cmd/MapManagerCMD.java | 2 +- .../listeners/MapListener.java | 2 +- .../listeners/PacketListener.java | 27 ++++++++----------- .../managers/Configuration.java | 2 +- .../mapreflectionapi/utils/UpdateManager.java | 2 +- .../mapreflectionapi/utils/YamlFile.java | 2 +- Dist/pom.xml | 25 +++++++---------- NMS-v1_12_R1/pom.xml | 18 +++++++++++++ .../nms/MapSender_v1_12_R1.java | 27 ++++++++----------- .../nms/MapWrapper_v1_12_R1.java | 27 ++++++++----------- .../nms/PacketListener_v1_12_R1.java | 27 ++++++++----------- NMS-v1_13_R2/pom.xml | 18 +++++++++++++ .../nms/MapSender_v1_13_R2.java | 27 ++++++++----------- .../nms/MapWrapper_v1_13_R2.java | 27 ++++++++----------- .../nms/PacketListener_v1_13_R2.java | 27 ++++++++----------- NMS-v1_14_R1/pom.xml | 18 +++++++++++++ .../nms/MapSender_v1_14_R1.java | 27 ++++++++----------- .../nms/MapWrapper_v1_14_R1.java | 27 ++++++++----------- .../nms/PacketListener_v1_14_R1.java | 27 ++++++++----------- NMS-v1_15_R1/pom.xml | 18 +++++++++++++ .../nms/MapSender_v1_15_R1.java | 27 ++++++++----------- .../nms/MapWrapper_v1_15_R1.java | 27 ++++++++----------- .../nms/PacketListener_v1_15_R1.java | 27 ++++++++----------- NMS-v1_16_R3/pom.xml | 18 +++++++++++++ .../nms/MapSender_v1_16_R3.java | 27 ++++++++----------- .../nms/MapWrapper_v1_16_R3.java | 27 ++++++++----------- .../nms/PacketListener_v1_16_R3.java | 27 ++++++++----------- NMS-v1_17_R1/pom.xml | 27 ++++++++----------- .../nms/MapSender_v1_17_R1.java | 27 ++++++++----------- .../nms/MapWrapper_v1_17_R1.java | 27 ++++++++----------- .../nms/PacketListener_v1_17_R1.java | 27 ++++++++----------- NMS-v1_18_R2/pom.xml | 27 ++++++++----------- .../nms/MapSender_v1_18_R2.java | 27 ++++++++----------- .../nms/MapWrapper_v1_18_R2.java | 27 ++++++++----------- .../nms/PacketListener_v1_18_R2.java | 27 ++++++++----------- NMS-v1_19_R3/pom.xml | 25 +++++++---------- .../nms/MapSender_v1_19_R3.java | 25 +++++++---------- .../nms/MapWrapper_v1_19_R3.java | 25 +++++++---------- .../nms/PacketListener_v1_19_R3.java | 25 +++++++---------- NMS-v1_20_R1/pom.xml | 25 +++++++---------- .../nms/MapSender_v1_20_R1.java | 25 +++++++---------- .../nms/MapWrapper_v1_20_R1.java | 25 +++++++---------- .../nms/PacketListener_v1_20_R1.java | 25 +++++++---------- pom.xml | 4 +-- 65 files changed, 500 insertions(+), 562 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index c9caadf..93bce61 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -1,6 +1,6 @@ name: Java CI -on: [push] +on: [ push ] jobs: build: diff --git a/API/pom.xml b/API/pom.xml index 9cd7072..a276161 100644 --- a/API/pom.xml +++ b/API/pom.xml @@ -1,25 +1,20 @@ . + */ + package tech.sbdevelopment.mapreflectionapi.api; public abstract class MapWrapper extends AbstractMapWrapper { diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java index 6629266..a003ddc 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java index 4cc282f..b7aa6c9 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java index 2d90799..65b9a6e 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,8 +24,8 @@ import lombok.Setter; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; -import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; /** * This event gets fired when the content of a {@link MapWrapper} is updated diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java index 6e2dbb4..6bbcb4a 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java index 96733cc..56c07cd 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/package-info.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java index 55a2cc7..9aa6672 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java index bff336f..7fb11d0 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/package-info.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java index b3d3b45..6c697b6 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java index 4284c2a..5fe9c42 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java index 33ee0b3..4c8729e 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index 6884b0d..a809d0a 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.listeners; diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java index f5acc72..ab55bef 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java index 7acc5f3..631c663 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java index 50154a5..bb2db31 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/Dist/pom.xml b/Dist/pom.xml index ba1e074..0af26ba 100644 --- a/Dist/pom.xml +++ b/Dist/pom.xml @@ -3,23 +3,18 @@ ~ This file is part of MapReflectionAPI. ~ Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved ~ - ~ Permission is hereby granted, free of charge, to any person obtaining a copy - ~ of this software and associated documentation files (the "Software"), to deal - ~ in the Software without restriction, including without limitation the rights - ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - ~ copies of the Software, and to permit persons to whom the Software is - ~ furnished to do so, subject to the following conditions: + ~ This program is free software: you can redistribute it and/or modify + ~ it under the terms of the GNU General Public License as published by + ~ the Free Software Foundation, either version 3 of the License, or + ~ (at your option) any later version. ~ - ~ The above copyright notice and this permission notice shall be included in all - ~ copies or substantial portions of the Software. + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. ~ - ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - ~ SOFTWARE. + ~ You should have received a copy of the GNU General Public License + ~ along with this program. If not, see . --> + + diff --git a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java index 8a00465..0fe0431 100644 --- a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java +++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java index 95278dd..4d710ab 100644 --- a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java +++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java index bffceb2..f2a6924 100644 --- a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java +++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_13_R2/pom.xml b/NMS-v1_13_R2/pom.xml index abee20e..17b0098 100644 --- a/NMS-v1_13_R2/pom.xml +++ b/NMS-v1_13_R2/pom.xml @@ -1,4 +1,22 @@ + + diff --git a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java index 8a52114..4eb19fa 100644 --- a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java +++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java index 8192d28..145dc66 100644 --- a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java +++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java index fd4a1cc..2180ab0 100644 --- a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java +++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_14_R1/pom.xml b/NMS-v1_14_R1/pom.xml index 13b3f3c..9e67e14 100644 --- a/NMS-v1_14_R1/pom.xml +++ b/NMS-v1_14_R1/pom.xml @@ -1,4 +1,22 @@ + + diff --git a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java index f336211..3e7026a 100644 --- a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java +++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java index 7248827..4a73b9f 100644 --- a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java +++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java index 9af8bf3..551a54b 100644 --- a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java +++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_15_R1/pom.xml b/NMS-v1_15_R1/pom.xml index ad1c41b..4f6f9df 100644 --- a/NMS-v1_15_R1/pom.xml +++ b/NMS-v1_15_R1/pom.xml @@ -1,4 +1,22 @@ + + diff --git a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java index 77106f4..ea6ee7b 100644 --- a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java +++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java index d1148ed..7308ace 100644 --- a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java +++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java index deb785d..a73dd04 100644 --- a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java +++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_16_R3/pom.xml b/NMS-v1_16_R3/pom.xml index ef40e8c..46edaa9 100644 --- a/NMS-v1_16_R3/pom.xml +++ b/NMS-v1_16_R3/pom.xml @@ -1,4 +1,22 @@ + + diff --git a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java index a76ceae..f188e64 100644 --- a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java +++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java index 1381753..492fd29 100644 --- a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java +++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java index 3549001..5f6fa98 100644 --- a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java +++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_17_R1/pom.xml b/NMS-v1_17_R1/pom.xml index 7fbb484..2608809 100644 --- a/NMS-v1_17_R1/pom.xml +++ b/NMS-v1_17_R1/pom.xml @@ -1,25 +1,20 @@ . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java index 1bea945..8ba9201 100644 --- a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java +++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java index ff07b17..f06f3f5 100644 --- a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java +++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_18_R2/pom.xml b/NMS-v1_18_R2/pom.xml index 9ac89c4..5dfc581 100644 --- a/NMS-v1_18_R2/pom.xml +++ b/NMS-v1_18_R2/pom.xml @@ -1,25 +1,20 @@ . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java index 0908b8c..1ab2a22 100644 --- a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java +++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java index 4123dd2..7524a29 100644 --- a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java +++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java @@ -1,24 +1,19 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_19_R3/pom.xml b/NMS-v1_19_R3/pom.xml index b14f9a9..08f0828 100644 --- a/NMS-v1_19_R3/pom.xml +++ b/NMS-v1_19_R3/pom.xml @@ -3,23 +3,18 @@ ~ This file is part of MapReflectionAPI. ~ Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved ~ - ~ Permission is hereby granted, free of charge, to any person obtaining a copy - ~ of this software and associated documentation files (the "Software"), to deal - ~ in the Software without restriction, including without limitation the rights - ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - ~ copies of the Software, and to permit persons to whom the Software is - ~ furnished to do so, subject to the following conditions: + ~ This program is free software: you can redistribute it and/or modify + ~ it under the terms of the GNU General Public License as published by + ~ the Free Software Foundation, either version 3 of the License, or + ~ (at your option) any later version. ~ - ~ The above copyright notice and this permission notice shall be included in all - ~ copies or substantial portions of the Software. + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. ~ - ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - ~ SOFTWARE. + ~ You should have received a copy of the GNU General Public License + ~ along with this program. If not, see . --> . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java index afd651b..e0e0615 100644 --- a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java +++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java @@ -2,23 +2,18 @@ * This file is part of MapReflectionAPI. * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java index 4e8f0db..c437542 100644 --- a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java +++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java @@ -2,23 +2,18 @@ * This file is part of MapReflectionAPI. * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_20_R1/pom.xml b/NMS-v1_20_R1/pom.xml index 50dd21d..6f5cdde 100644 --- a/NMS-v1_20_R1/pom.xml +++ b/NMS-v1_20_R1/pom.xml @@ -3,23 +3,18 @@ ~ This file is part of MapReflectionAPI. ~ Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved ~ - ~ Permission is hereby granted, free of charge, to any person obtaining a copy - ~ of this software and associated documentation files (the "Software"), to deal - ~ in the Software without restriction, including without limitation the rights - ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - ~ copies of the Software, and to permit persons to whom the Software is - ~ furnished to do so, subject to the following conditions: + ~ This program is free software: you can redistribute it and/or modify + ~ it under the terms of the GNU General Public License as published by + ~ the Free Software Foundation, either version 3 of the License, or + ~ (at your option) any later version. ~ - ~ The above copyright notice and this permission notice shall be included in all - ~ copies or substantial portions of the Software. + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. ~ - ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - ~ SOFTWARE. + ~ You should have received a copy of the GNU General Public License + ~ along with this program. If not, see . --> . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java index b95f9dc..ca958f6 100644 --- a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java +++ b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java @@ -2,23 +2,18 @@ * This file is part of MapReflectionAPI. * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java index 2511259..a07cde1 100644 --- a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java +++ b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java @@ -2,23 +2,18 @@ * This file is part of MapReflectionAPI. * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ package tech.sbdevelopment.mapreflectionapi.nms; diff --git a/pom.xml b/pom.xml index 19485ab..fba01f0 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ MapReflectionAPI pom ${revision} - + MapReflectionAPI This API helps developer with viewing images on maps. https://sbdplugins.nl @@ -50,7 +50,7 @@ NMS-v1_13_R2 NMS-v1_12_R1 - + sbdevelopment-repo From b163e575638a34d1c467e0297bad6bdea1c1522a Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Tue, 4 Jul 2023 17:32:39 +0200 Subject: [PATCH 04/52] Disabled GitHub actions --- .github/workflows/maven.yml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .github/workflows/maven.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index 93bce61..0000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Java CI - -on: [ push ] - -jobs: - build: - name: Build - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - - name: Build with Maven - run: mvn -B package --file pom.xml - - - run: mkdir -p target - - - uses: actions/upload-artifact@master - with: - name: MapReflectionAPI - path: target From 24583880a211d93ca4317fda97ded93f00dbac89 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Tue, 4 Jul 2023 17:33:05 +0200 Subject: [PATCH 05/52] Bumped to v1.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fba01f0..aa639dd 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ https://sbdplugins.nl - 1.4.4 + 1.5 UTF-8 11 From 336d9626e130825e053ba23203f5161d027e4e9a Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Tue, 4 Jul 2023 17:35:14 +0200 Subject: [PATCH 06/52] Readded copyright files --- .idea/copyright/SBDevelopment.xml | 6 ++++++ .idea/copyright/profiles_settings.xml | 7 +++++++ 2 files changed, 13 insertions(+) create mode 100644 .idea/copyright/SBDevelopment.xml create mode 100644 .idea/copyright/profiles_settings.xml diff --git a/.idea/copyright/SBDevelopment.xml b/.idea/copyright/SBDevelopment.xml new file mode 100644 index 0000000..9a257e5 --- /dev/null +++ b/.idea/copyright/SBDevelopment.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..26f52f6 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From 3ac104289426e4f07af2c76a36405d2bccac886f Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Tue, 4 Jul 2023 17:47:30 +0200 Subject: [PATCH 07/52] Added javadoc --- .../mapreflectionapi/api/MapWrapper.java | 8 ++++++++ .../nms/MapSender_v1_12_R1.java | 20 +++++++++++++++++++ .../nms/MapSender_v1_13_R2.java | 20 +++++++++++++++++++ .../nms/MapSender_v1_14_R1.java | 20 +++++++++++++++++++ .../nms/MapSender_v1_15_R1.java | 20 +++++++++++++++++++ .../nms/MapSender_v1_16_R3.java | 20 +++++++++++++++++++ .../nms/MapSender_v1_17_R1.java | 20 +++++++++++++++++++ .../nms/MapSender_v1_18_R2.java | 20 +++++++++++++++++++ .../nms/MapSender_v1_19_R3.java | 20 +++++++++++++++++++ .../nms/MapSender_v1_20_R1.java | 20 +++++++++++++++++++ 10 files changed, 188 insertions(+) diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index 59ff6c7..2d7f9cb 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -18,9 +18,17 @@ package tech.sbdevelopment.mapreflectionapi.api; +/** + * A {@link MapWrapper} wraps one image. + */ public abstract class MapWrapper extends AbstractMapWrapper { protected ArrayImage content; + /** + * Construct a new {@link MapWrapper} + * + * @param image The {@link ArrayImage} to wrap + */ public MapWrapper(ArrayImage image) { this.content = image; } diff --git a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java index 0fe0431..f24528d 100644 --- a/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java +++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java @@ -29,6 +29,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +/** + * The {@link MapSender_v1_12_R1} sends the Map packets to players. + */ public class MapSender_v1_12_R1 { private static final List sendQueue = new ArrayList<>(); private static int senderID = -1; @@ -36,6 +39,13 @@ public class MapSender_v1_12_R1 { private MapSender_v1_12_R1() { } + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void addToQueue(final int id, final ArrayImage content, final Player player) { QueuedMap toSend = new QueuedMap(id, content, player); if (sendQueue.contains(toSend)) return; @@ -53,6 +63,9 @@ public class MapSender_v1_12_R1 { sendQueue.removeIf(queuedMap -> queuedMap.id == s); } + /** + * Run the sender task + */ private static void runSender() { if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) return; @@ -71,6 +84,13 @@ public class MapSender_v1_12_R1 { }, 0, 2); } + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void sendMap(final int id0, final ArrayImage content, final Player player) { if (player == null || !player.isOnline()) { List toRemove = new ArrayList<>(); diff --git a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java index 4eb19fa..d82d5fc 100644 --- a/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java +++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java @@ -29,6 +29,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +/** + * The {@link MapSender_v1_13_R2} sends the Map packets to players. + */ public class MapSender_v1_13_R2 { private static final List sendQueue = new ArrayList<>(); private static int senderID = -1; @@ -36,6 +39,13 @@ public class MapSender_v1_13_R2 { private MapSender_v1_13_R2() { } + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void addToQueue(final int id, final ArrayImage content, final Player player) { QueuedMap toSend = new QueuedMap(id, content, player); if (sendQueue.contains(toSend)) return; @@ -53,6 +63,9 @@ public class MapSender_v1_13_R2 { sendQueue.removeIf(queuedMap -> queuedMap.id == s); } + /** + * Run the sender task + */ private static void runSender() { if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) return; @@ -71,6 +84,13 @@ public class MapSender_v1_13_R2 { }, 0, 2); } + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void sendMap(final int id0, final ArrayImage content, final Player player) { if (player == null || !player.isOnline()) { List toRemove = new ArrayList<>(); diff --git a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java index 3e7026a..49ebfae 100644 --- a/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java +++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java @@ -29,6 +29,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +/** + * The {@link MapSender_v1_14_R1} sends the Map packets to players. + */ public class MapSender_v1_14_R1 { private static final List sendQueue = new ArrayList<>(); private static int senderID = -1; @@ -36,6 +39,13 @@ public class MapSender_v1_14_R1 { private MapSender_v1_14_R1() { } + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void addToQueue(final int id, final ArrayImage content, final Player player) { QueuedMap toSend = new QueuedMap(id, content, player); if (sendQueue.contains(toSend)) return; @@ -53,6 +63,9 @@ public class MapSender_v1_14_R1 { sendQueue.removeIf(queuedMap -> queuedMap.id == s); } + /** + * Run the sender task + */ private static void runSender() { if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) return; @@ -71,6 +84,13 @@ public class MapSender_v1_14_R1 { }, 0, 2); } + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void sendMap(final int id0, final ArrayImage content, final Player player) { if (player == null || !player.isOnline()) { List toRemove = new ArrayList<>(); diff --git a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java index ea6ee7b..a7b83d9 100644 --- a/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java +++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java @@ -29,6 +29,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +/** + * The {@link MapSender_v1_15_R1} sends the Map packets to players. + */ public class MapSender_v1_15_R1 { private static final List sendQueue = new ArrayList<>(); private static int senderID = -1; @@ -36,6 +39,13 @@ public class MapSender_v1_15_R1 { private MapSender_v1_15_R1() { } + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void addToQueue(final int id, final ArrayImage content, final Player player) { QueuedMap toSend = new QueuedMap(id, content, player); if (sendQueue.contains(toSend)) return; @@ -53,6 +63,9 @@ public class MapSender_v1_15_R1 { sendQueue.removeIf(queuedMap -> queuedMap.id == s); } + /** + * Run the sender task + */ private static void runSender() { if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) return; @@ -71,6 +84,13 @@ public class MapSender_v1_15_R1 { }, 0, 2); } + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void sendMap(final int id0, final ArrayImage content, final Player player) { if (player == null || !player.isOnline()) { List toRemove = new ArrayList<>(); diff --git a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java index f188e64..e4ed5cf 100644 --- a/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java +++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java @@ -29,6 +29,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +/** + * The {@link MapSender_v1_16_R3} sends the Map packets to players. + */ public class MapSender_v1_16_R3 { private static final List sendQueue = new ArrayList<>(); private static int senderID = -1; @@ -36,6 +39,13 @@ public class MapSender_v1_16_R3 { private MapSender_v1_16_R3() { } + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void addToQueue(final int id, final ArrayImage content, final Player player) { QueuedMap toSend = new QueuedMap(id, content, player); if (sendQueue.contains(toSend)) return; @@ -53,6 +63,9 @@ public class MapSender_v1_16_R3 { sendQueue.removeIf(queuedMap -> queuedMap.id == s); } + /** + * Run the sender task + */ private static void runSender() { if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) return; @@ -71,6 +84,13 @@ public class MapSender_v1_16_R3 { }, 0, 2); } + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void sendMap(final int id0, final ArrayImage content, final Player player) { if (player == null || !player.isOnline()) { List toRemove = new ArrayList<>(); diff --git a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java index 357e75d..050df5b 100644 --- a/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java +++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java @@ -29,6 +29,9 @@ import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; import java.util.ArrayList; import java.util.List; +/** + * The {@link MapSender_v1_17_R1} sends the Map packets to players. + */ public class MapSender_v1_17_R1 { private static final List sendQueue = new ArrayList<>(); private static int senderID = -1; @@ -36,6 +39,13 @@ public class MapSender_v1_17_R1 { private MapSender_v1_17_R1() { } + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void addToQueue(final int id, final ArrayImage content, final Player player) { QueuedMap toSend = new QueuedMap(id, content, player); if (sendQueue.contains(toSend)) return; @@ -53,6 +63,9 @@ public class MapSender_v1_17_R1 { sendQueue.removeIf(queuedMap -> queuedMap.id == s); } + /** + * Run the sender task + */ private static void runSender() { if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) return; @@ -71,6 +84,13 @@ public class MapSender_v1_17_R1 { }, 0, 2); } + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void sendMap(final int id0, final ArrayImage content, final Player player) { if (player == null || !player.isOnline()) { List toRemove = new ArrayList<>(); diff --git a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java index b71caa4..ebe34fe 100644 --- a/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java +++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java @@ -29,6 +29,9 @@ import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; import java.util.ArrayList; import java.util.List; +/** + * The {@link MapSender_v1_18_R2} sends the Map packets to players. + */ public class MapSender_v1_18_R2 { private static final List sendQueue = new ArrayList<>(); private static int senderID = -1; @@ -36,6 +39,13 @@ public class MapSender_v1_18_R2 { private MapSender_v1_18_R2() { } + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void addToQueue(final int id, final ArrayImage content, final Player player) { QueuedMap toSend = new QueuedMap(id, content, player); if (sendQueue.contains(toSend)) return; @@ -53,6 +63,9 @@ public class MapSender_v1_18_R2 { sendQueue.removeIf(queuedMap -> queuedMap.id == s); } + /** + * Run the sender task + */ private static void runSender() { if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) return; @@ -71,6 +84,13 @@ public class MapSender_v1_18_R2 { }, 0, 2); } + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void sendMap(final int id0, final ArrayImage content, final Player player) { if (player == null || !player.isOnline()) { List toRemove = new ArrayList<>(); diff --git a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java index 6714141..50d5bbd 100644 --- a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java +++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java @@ -29,6 +29,9 @@ import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; import java.util.ArrayList; import java.util.List; +/** + * The {@link MapSender_v1_19_R3} sends the Map packets to players. + */ public class MapSender_v1_19_R3 { private static final List sendQueue = new ArrayList<>(); private static int senderID = -1; @@ -36,6 +39,13 @@ public class MapSender_v1_19_R3 { private MapSender_v1_19_R3() { } + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void addToQueue(final int id, final ArrayImage content, final Player player) { QueuedMap toSend = new QueuedMap(id, content, player); if (sendQueue.contains(toSend)) return; @@ -53,6 +63,9 @@ public class MapSender_v1_19_R3 { sendQueue.removeIf(queuedMap -> queuedMap.id == s); } + /** + * Run the sender task + */ private static void runSender() { if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) return; @@ -71,6 +84,13 @@ public class MapSender_v1_19_R3 { }, 0, 2); } + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void sendMap(final int id0, final ArrayImage content, final Player player) { if (player == null || !player.isOnline()) { List toRemove = new ArrayList<>(); diff --git a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java index 4bd4140..3f523f7 100644 --- a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java +++ b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java @@ -29,6 +29,9 @@ import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; import java.util.ArrayList; import java.util.List; +/** + * The {@link MapSender_v1_20_R1} sends the Map packets to players. + */ public class MapSender_v1_20_R1 { private static final List sendQueue = new ArrayList<>(); private static int senderID = -1; @@ -36,6 +39,13 @@ public class MapSender_v1_20_R1 { private MapSender_v1_20_R1() { } + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void addToQueue(final int id, final ArrayImage content, final Player player) { QueuedMap toSend = new QueuedMap(id, content, player); if (sendQueue.contains(toSend)) return; @@ -53,6 +63,9 @@ public class MapSender_v1_20_R1 { sendQueue.removeIf(queuedMap -> queuedMap.id == s); } + /** + * Run the sender task + */ private static void runSender() { if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) return; @@ -71,6 +84,13 @@ public class MapSender_v1_20_R1 { }, 0, 2); } + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ public static void sendMap(final int id0, final ArrayImage content, final Player player) { if (player == null || !player.isOnline()) { List toRemove = new ArrayList<>(); From 8afc1fb2026dd49e564db4f60cb2fa06417aafff Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Wed, 5 Jul 2023 11:33:48 +0200 Subject: [PATCH 08/52] Fixed v1.20(.1), small startup print improvements --- .../sbdevelopment/mapreflectionapi/MapReflectionAPI.java | 7 +++---- .../sbdevelopment/mapreflectionapi/api/MapManager.java | 2 +- .../mapreflectionapi/listeners/PacketListener.java | 2 +- .../mapreflectionapi/nms/MapWrapper_v1_20_R1.java | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java index b2a73a9..ac18791 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java @@ -90,7 +90,6 @@ public class MapReflectionAPI extends JavaPlugin { getLogger().info("Loading the commands..."); getCommand("mapmanager").setExecutor(new MapManagerCMD()); - getLogger().info("Loading the packet listener..."); try { packetListener = PacketListener.construct(this); } catch (IllegalStateException e) { @@ -100,7 +99,6 @@ public class MapReflectionAPI extends JavaPlugin { } packetListener.init(this); - getLogger().info("Loading the map manager..."); try { mapManager = new MapManager(this); } catch (IllegalStateException e) { @@ -110,7 +108,8 @@ public class MapReflectionAPI extends JavaPlugin { } if (Configuration.getInstance().isAllowVanilla()) { - getLogger().info("Vanilla Maps are allowed. Discovering occupied Map IDs..."); + getLogger().info("Vanilla Maps are allowed!"); + getLogger().info("Discovering occupied Map IDs..."); int occupiedIDs = 0; for (int s = 0; s < Short.MAX_VALUE; s++) { try { @@ -131,7 +130,7 @@ public class MapReflectionAPI extends JavaPlugin { getLogger().info("Registering the listeners..."); Bukkit.getPluginManager().registerEvents(new MapListener(), this); - getLogger().info("Loading metrics..."); + getLogger().info("Enabling metrics..."); Metrics metrics = new Metrics(this, 16033); metrics.addCustomChart(new SingleLineChart("managed_maps", () -> mapManager.getManagedMapsCount())); diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java index 07c27b9..00b906d 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java @@ -46,7 +46,7 @@ public class MapManager { String packageName = Bukkit.getServer().getClass().getPackage().getName(); String version = packageName.substring(packageName.lastIndexOf('.') + 1); - MapReflectionAPI.getInstance().getLogger().info("Initializing the map manager for Minecraft version " + version + "..."); + plugin.getLogger().info("Enabling MapManager for " + version + "..."); try { final Class clazz = Class.forName("tech.sbdevelopment.mapreflectionapi.nms.MapWrapper_" + version); diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index a809d0a..d56a89e 100644 --- a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -34,7 +34,7 @@ public abstract class PacketListener implements Listener { String packageName = Bukkit.getServer().getClass().getPackage().getName(); String version = packageName.substring(packageName.lastIndexOf('.') + 1); - plugin.getLogger().info("Initializing the packet handler for Minecraft version " + version + "..."); + plugin.getLogger().info("Enabling PacketListener for " + version + "..."); try { final Class clazz = Class.forName("tech.sbdevelopment.mapreflectionapi.nms.PacketListener_" + version); diff --git a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java index ca958f6..fcdf607 100644 --- a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java +++ b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java @@ -217,7 +217,7 @@ public class MapWrapper_v1_20_R1 extends MapWrapper { private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); - nmsStack.v().a("map", mapId); //getOrCreateTag putInt + nmsStack.w().a("map", mapId); //getOrCreateTag putInt List> list = new ArrayList<>(); DataWatcher.b dataWatcherItem = DataWatcher.b.a(EntityItemFrame.g, nmsStack); From e9b19fa2f15b45ce2a026baf3f88696a7f1ef410 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Wed, 5 Jul 2023 11:49:59 +0200 Subject: [PATCH 09/52] Fixed deploy --- pom.xml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/pom.xml b/pom.xml index aa639dd..15576c5 100644 --- a/pom.xml +++ b/pom.xml @@ -80,24 +80,6 @@ - - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - ${jdk.version} - - - - - org.apache.maven.plugins - maven-deploy-plugin - 3.1.1 - - true - - From a3ba68434f3504db684458ed2fbb121152146da8 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Wed, 5 Jul 2023 11:54:49 +0200 Subject: [PATCH 10/52] Bumped README version --- .idea/encodings.xml | 2 ++ README.md | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 67b333b..f031880 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -23,5 +23,7 @@ + + \ No newline at end of file diff --git a/README.md b/README.md index 548bf80..2575bac 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ First, include the API using Maven: tech.sbdevelopment - MapReflectionAPI - 1.4.3 + MapReflectionAPI-API + 1.5 provided ``` From 230d7fc5b2bb81ae221be66c3b061c9bef3b096e Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Wed, 5 Jul 2023 12:04:21 +0200 Subject: [PATCH 11/52] Fixed deploy again (revision problem) --- .gitignore | 1 + .idea/encodings.xml | 2 ++ pom.xml | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/.gitignore b/.gitignore index e3f5493..4841f93 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ buildNumber.properties .mvn/timing.properties # https://github.com/takari/maven-wrapper#usage-without-binary-jar .mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml # Eclipse m2e generated files # Eclipse Core diff --git a/.idea/encodings.xml b/.idea/encodings.xml index f031880..4879e46 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -1,6 +1,8 @@ + + diff --git a/pom.xml b/pom.xml index 15576c5..c3457f2 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,38 @@ clean package + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.codehaus.mojo + flatten-maven-plugin + 1.5.0 + + true + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + org.apache.maven.plugins maven-toolchains-plugin From fa136c49556f91107d60654b91fa7af1e29d7325 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Wed, 5 Jul 2023 19:29:23 +0200 Subject: [PATCH 12/52] Fixed build issue --- .../mapreflectionapi/nms/PacketListener_v1_19_R3.java | 3 +-- pom.xml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java index c437542..7af3493 100644 --- a/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java +++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java @@ -49,8 +49,7 @@ public class PacketListener_v1_19_R3 extends PacketListener { //On send packet public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { if (packet instanceof PacketPlayOutMap packetPlayOutMap) { - int id = (int) getDeclaredField(packetPlayOutMap, "a"); - + int id = packetPlayOutMap.a(); //mapId if (id < 0) { //It's one of our maps, invert ID and let through! int newId = -id; diff --git a/pom.xml b/pom.xml index c3457f2..6c05711 100644 --- a/pom.xml +++ b/pom.xml @@ -127,6 +127,7 @@ org.spigotmc spigot-api 1.20.1-R0.1-SNAPSHOT + provided From 15640e588640da0827080b29645c483a1ce0459e Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Wed, 5 Jul 2023 19:36:41 +0200 Subject: [PATCH 13/52] Hotfix for the show in hand feature, closes #20 --- README.md | 2 +- pom.xml | 4 ++-- .../tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 548bf80..b2a8f1f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ First, include the API using Maven: tech.sbdevelopment MapReflectionAPI - 1.4.3 + 1.5.1 provided ``` diff --git a/pom.xml b/pom.xml index 361046c..42a77e5 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ tech.sbdevelopment MapReflectionAPI - 1.4.4 + 1.5.1 jar MapReflectionAPI @@ -161,7 +161,7 @@ org.spigotmc spigot-api - 1.19.4-R0.1-SNAPSHOT + 1.20.1-R0.1-SNAPSHOT provided diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index 76d828a..9cece07 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -302,7 +302,9 @@ public class MapWrapper extends AbstractMapWrapper { if (ReflectionUtil.supports(13)) { String nbtObjectName; - if (ReflectionUtil.supports(19)) { //1.19 + if (ReflectionUtil.supports(20)) { //1.20 + nbtObjectName = "w"; + } else if (ReflectionUtil.supports(19)) { //1.19 nbtObjectName = "v"; } else if (ReflectionUtil.supports(18)) { //1.18 nbtObjectName = ReflectionUtil.VER_MINOR == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u From c390ebbd5e1e87baa5717603fb9380cf3ee850b7 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Wed, 9 Aug 2023 10:48:37 +0200 Subject: [PATCH 14/52] Hotfix: Fixed supports() function detecting invalid major/minor version combination for newer versions; closes #22 --- .idea/misc.xml | 1 - pom.xml | 2 +- .../mapreflectionapi/listeners/PacketListener.java | 2 +- .../sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 200af21..4a803e8 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/pom.xml b/pom.xml index 42a77e5..f1bd2ed 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ tech.sbdevelopment MapReflectionAPI - 1.5.1 + 1.5.2 jar MapReflectionAPI diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index 3785599..b4f5ed0 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -143,7 +143,7 @@ public class PacketListener implements Listener { private Channel getChannel(Player player) { Object playerHandle = getHandle(player); Object playerConnection = getDeclaredField(playerHandle, ReflectionUtil.supports(20) ? "c" : ReflectionUtil.supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection - Object networkManager = getDeclaredField(playerConnection, ReflectionUtil.supports(19, 3) ? "h" : ReflectionUtil.supports(19) ? "b" : ReflectionUtil.supports(17) ? "a" : "networkManager"); //1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager + Object networkManager = getDeclaredField(playerConnection, ReflectionUtil.supports(19, 3) ? "h" : ReflectionUtil.supports(19) ? "b" : ReflectionUtil.supports(17) ? "a" : "networkManager"); //1.20 & 1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager return (Channel) getDeclaredField(networkManager, ReflectionUtil.supports(18) ? "m" : ReflectionUtil.supports(17) ? "k" : "channel"); //1.19 & 1.18 = m, 1.17 = k, 1.16 = channel } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java index bcf93d8..d0288c6 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java @@ -197,7 +197,7 @@ public class ReflectionUtil { * @since 4.0.0 */ public static boolean supports(int major, int minor) { - return VER >= major && VER_MINOR >= minor; + return (VER == major && VER_MINOR >= minor) || VER > major; } /** From a5acef06661a12b4942a82013fae7df4443257e3 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Thu, 10 Aug 2023 19:32:12 +0200 Subject: [PATCH 15/52] Fixed typo in README --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b2a8f1f..9da7ea1 100644 --- a/README.md +++ b/README.md @@ -50,13 +50,13 @@ controller.showInHand(p, true); It's also possible to split one image onto multiple itemframes. For example using the following code. ```java -BufferedImage leftTopFrame = ...; -BufferedImage leftBottomFrame = ...; -BufferedImage rightTopFrame = ...; -BufferedImage rightBottomFrame = ...; +BufferedImage leftTopImg = ...; +BufferedImage leftBottomImg = ...; +BufferedImage rightTopImg = ...; +BufferedImage rightBottomImg = ...; BufferedImage[][] images = { - {leftBottomFrame, leftTopFrame}, - {rightBottomFrame, rightTopFrame} + {leftBottomImg, leftTopImg}, + {rightBottomImg, rightTopImg} }; //--- Wrap image --- From 63ac9bb38eb243a687b9a10d43c1d8a104c298b0 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Sat, 12 Aug 2023 21:35:18 +0200 Subject: [PATCH 16/52] [BREAKING]: Improved [row][col] naming; now always accepts [row][col], breaks old multi-map plugins --- pom.xml | 2 +- .../mapreflectionapi/api/MapWrapper.java | 7 +- .../api/MultiMapController.java | 18 +-- .../mapreflectionapi/api/MultiMapWrapper.java | 127 ++++++++++-------- .../mapreflectionapi/utils/MainUtil.java | 6 - 5 files changed, 89 insertions(+), 71 deletions(-) diff --git a/pom.xml b/pom.xml index f1bd2ed..f60f473 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ tech.sbdevelopment MapReflectionAPI - 1.5.2 + 1.6 jar MapReflectionAPI diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index 9cece07..7e8e82a 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -33,6 +33,7 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.sql.Ref; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -321,8 +322,10 @@ public class MapWrapper extends AbstractMapWrapper { Object nmsStack = createCraftItemStack(stack, mapId); String dataWatcherObjectName; - if (ReflectionUtil.supports(19)) { //1.19 - dataWatcherObjectName = ReflectionUtil.VER_MINOR == 3 ? "g" : "ao"; //1.19.4 = g, >= 1.19.3 = ao + if (ReflectionUtil.supports(19, 3)) { //1.19.3 and 1.20(.1) + dataWatcherObjectName = "g"; + } else if (ReflectionUtil.supports(19)) { //1.19-1.19.2 + dataWatcherObjectName = "ao"; } else if (ReflectionUtil.supports(18)) { //1.18 dataWatcherObjectName = ReflectionUtil.VER_MINOR == 1 ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao } else if (ReflectionUtil.supports(17)) { //1.17 diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java index df07c10..537fd54 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java @@ -86,7 +86,7 @@ public interface MultiMapController extends IMapController { * Show this {@link MultiMapController} in {@link ItemFrame}s * * @param player {@link Player} that will be able to see the maps - * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[width][height]) + * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[rows][columns]) * @see MapController#showInFrame(Player, int) */ void showInFrames(Player player, Integer[][] entityIdMatrix); @@ -95,7 +95,7 @@ public interface MultiMapController extends IMapController { * Show this {@link MultiMapController} in {@link ItemFrame}s * * @param player {@link Player} that will be able to see the maps - * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[width][height]) + * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[rows][columns]) * @param callable {@link DebugCallable} which will be called to display debug information, or null * @see MapController#showInFrame(Player, int, String) */ @@ -105,7 +105,7 @@ public interface MultiMapController extends IMapController { * Show this {@link MultiMapController} in {@link ItemFrame}s * * @param player {@link Player} that will be able to see the maps - * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[width][height]) + * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[rows][columns]) * @param force if false, the map will not be shown if there is not Map-Item in the ItemFrames * @see MapController#showInFrame(Player, ItemFrame, boolean) */ @@ -115,7 +115,7 @@ public interface MultiMapController extends IMapController { * Show this {@link MultiMapController} in {@link ItemFrame}s * * @param player {@link Player} that will be able to see the maps - * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[width][height]) + * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[rows][columns]) * @see MapController#showInFrame(Player, ItemFrame) */ void showInFrames(Player player, ItemFrame[][] itemFrameMatrix); @@ -124,7 +124,7 @@ public interface MultiMapController extends IMapController { * Clear the frames * * @param player {@link Player} that will be able to see the cleared frames - * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[width][height]) + * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[rows][columns]) */ void clearFrames(Player player, Integer[][] entityIdMatrix); @@ -132,7 +132,7 @@ public interface MultiMapController extends IMapController { * Clear the frames * * @param player {@link Player} that will be able to see the cleared frames - * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[width][height]) + * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[rows][columns]) */ void clearFrames(Player player, ItemFrame[][] itemFrameMatrix); @@ -144,11 +144,11 @@ public interface MultiMapController extends IMapController { * Called to get debug information for a frame * * @param controller the {@link MapController} - * @param x X-Position of the current frame - * @param y Y-Position of the current frame + * @param row Row of the current frame + * @param column Column of the current frame * @return {@link String} to show when a player looks at the map, or null * @see MapController#showInFrame(Player, int, String) */ - String call(MapController controller, int x, int y); + String call(MapController controller, int row, int column); } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java index 7d22541..795a2be 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java @@ -25,52 +25,79 @@ import org.jetbrains.annotations.NotNull; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; -import java.awt.*; import java.awt.image.BufferedImage; import java.util.HashSet; import java.util.Set; import java.util.UUID; -import static tech.sbdevelopment.mapreflectionapi.utils.MainUtil.validateArrayDimensions; - /** * A {@link MultiMapWrapper} wraps one image split in pieces. */ public class MultiMapWrapper extends AbstractMapWrapper { private final MapWrapper[][] wrapperMatrix; + /** + * Creates a new {@link MultiMapWrapper} from the given image. + * The image will be split into the given amount of rows and columns. + * + * @param image The image to wrap + * @param rows The amount of rows + * @param columns The amount of columns + */ public MultiMapWrapper(BufferedImage image, int rows, int columns) { - this(splitImage(image, columns, rows)); + this(splitImage(image, rows, columns)); } + /** + * Creates a new {@link MultiMapWrapper} from the given image. + * The image will be split into the given amount of rows and columns. + * + * @param image The image to wrap + * @param rows The amount of rows + * @param columns The amount of columns + */ public MultiMapWrapper(ArrayImage image, int rows, int columns) { - this(splitImage(image.toBuffered(), columns, rows)); + this(splitImage(image.toBuffered(), rows, columns)); } + /** + * Creates a new {@link MultiMapWrapper} from the given image. + * + * @param imageMatrix The image matrix to wrap + * @deprecated Use {@link #MultiMapWrapper(ArrayImage, int, int)} instead, this method is meant for internal use only. + */ + @Deprecated(since = "1.6", forRemoval = true) public MultiMapWrapper(ArrayImage[][] imageMatrix) { wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length]; - for (int x = 0; x < imageMatrix.length; x++) { - if (imageMatrix[x].length != imageMatrix[0].length) { + for (int row = 0; row < imageMatrix.length; row++) { + if (imageMatrix[row].length != imageMatrix[0].length) { throw new IllegalArgumentException("An image in a MultiMapWrapper is not rectangular!"); } - for (int y = 0; y < imageMatrix[x].length; y++) { - wrapperMatrix[x][y] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[x][y]); + for (int column = 0; column < imageMatrix[row].length; column++) { + wrapperMatrix[row][column] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[row][column]); } } } + /** + * Creates a new {@link MultiMapWrapper} from the given image. + * + * @param imageMatrix The image matrix to wrap + * @deprecated Use {@link #MultiMapWrapper(BufferedImage, int, int)} instead, this method is meant for internal use only. + */ + @Deprecated(since = "1.6", forRemoval = true) public MultiMapWrapper(BufferedImage[][] imageMatrix) { wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length]; - for (int x = 0; x < imageMatrix.length; x++) { - if (imageMatrix[x].length != imageMatrix[0].length) { + for (int row = 0; row < imageMatrix.length; row++) { + if (imageMatrix[row].length != imageMatrix[0].length) { throw new IllegalArgumentException("An image in a MultiMapWrapper is not rectangular!"); } - for (int y = 0; y < imageMatrix[x].length; y++) { - wrapperMatrix[x][y] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[x][y]); + for (int column = 0; column < imageMatrix[row].length; column++) { + wrapperMatrix[row][column] = MapReflectionAPI.getMapManager().wrapImage(imageMatrix[row][column]); } } } @@ -117,10 +144,10 @@ public class MultiMapWrapper extends AbstractMapWrapper { @Override public void update(@NotNull ArrayImage content) { - ArrayImage[][] split = splitImage(content.toBuffered(), wrapperMatrix[0].length, wrapperMatrix.length); - for (int x = 0; x < wrapperMatrix.length; x++) { - for (int y = 0; y < wrapperMatrix[x].length; y++) { - wrapperMatrix[x][y].getController().update(split[x][y]); + ArrayImage[][] split = splitImage(content.toBuffered(), wrapperMatrix.length, wrapperMatrix[0].length); + for (int row = 0; row < wrapperMatrix.length; row++) { + for (int column = 0; column < wrapperMatrix[row].length; column++) { + wrapperMatrix[row][column].getController().update(split[row][column]); } } } @@ -150,33 +177,27 @@ public class MultiMapWrapper extends AbstractMapWrapper { @Override public void showInFrames(Player player, Integer[][] entityIdMatrix) { - validateArrayDimensions(wrapperMatrix, entityIdMatrix); - - for (int x = 0; x < entityIdMatrix.length; x++) { - for (int y = 0; y < entityIdMatrix[x].length; y++) { - wrapperMatrix[y][x].getController().showInFrame(player, entityIdMatrix[x][wrapperMatrix.length - 1 - y]); + for (int row = 0; row < entityIdMatrix.length; row++) { + for (int column = 0; column < entityIdMatrix[row].length; column++) { + wrapperMatrix[row][column].getController().showInFrame(player, entityIdMatrix[row][column]); } } } @Override public void showInFrames(Player player, Integer[][] entityIdMatrix, DebugCallable callable) { - validateArrayDimensions(wrapperMatrix, entityIdMatrix); - - for (int x = 0; x < entityIdMatrix.length; x++) { - for (int y = 0; y < entityIdMatrix[x].length; y++) { - wrapperMatrix[y][x].getController().showInFrame(player, entityIdMatrix[x][wrapperMatrix.length - 1 - y], callable.call(wrapperMatrix[y][x].getController(), x, y)); + for (int row = 0; row < entityIdMatrix.length; row++) { + for (int column = 0; column < entityIdMatrix[row].length; column++) { + wrapperMatrix[row][column].getController().showInFrame(player, entityIdMatrix[row][column], callable.call(wrapperMatrix[row][column].getController(), row, column)); } } } @Override public void showInFrames(Player player, ItemFrame[][] itemFrameMatrix, boolean force) { - validateArrayDimensions(wrapperMatrix, itemFrameMatrix); - - for (int x = 0; x < itemFrameMatrix.length; x++) { - for (int y = 0; y < itemFrameMatrix[x].length; y++) { - wrapperMatrix[y][x].getController().showInFrame(player, itemFrameMatrix[x][wrapperMatrix.length - 1 - y], force); + for (int row = 0; row < itemFrameMatrix.length; row++) { + for (int column = 0; column < itemFrameMatrix[row].length; column++) { + wrapperMatrix[row][column].getController().showInFrame(player, itemFrameMatrix[row][column], force); } } } @@ -188,47 +209,47 @@ public class MultiMapWrapper extends AbstractMapWrapper { @Override public void clearFrames(Player player, Integer[][] entityIdMatrix) { - validateArrayDimensions(wrapperMatrix, entityIdMatrix); - - for (int x = 0; x < entityIdMatrix.length; x++) { - for (int y = 0; y < entityIdMatrix[x].length; y++) { - wrapperMatrix[y][x].getController().clearFrame(player, entityIdMatrix[x][y]); + for (int row = 0; row < entityIdMatrix.length; row++) { + for (int column = 0; column < entityIdMatrix[row].length; column++) { + wrapperMatrix[row][column].getController().clearFrame(player, entityIdMatrix[row][column]); } } } @Override public void clearFrames(Player player, ItemFrame[][] itemFrameMatrix) { - validateArrayDimensions(wrapperMatrix, itemFrameMatrix); - - for (int x = 0; x < itemFrameMatrix.length; x++) { - for (int y = 0; y < itemFrameMatrix[x].length; y++) { - wrapperMatrix[y][x].getController().clearFrame(player, itemFrameMatrix[x][y]); + for (int row = 0; row < itemFrameMatrix.length; row++) { + for (int column = 0; column < itemFrameMatrix[row].length; column++) { + wrapperMatrix[row][column].getController().clearFrame(player, itemFrameMatrix[row][column]); } } } }; - /* - * Modified Method from http://kalanir.blogspot.de/2010/02/how-to-split-image-into-chunks-java.html + /** + * Splits a BufferedImage into a matrix of ArrayImages. + * + * @param image The image to split + * @param rows The number of rows + * @param columns The number of columns + * @return The matrix of ArrayImages */ - private static ArrayImage[][] splitImage(final BufferedImage image, final int columns, final int rows) { + private static ArrayImage[][] splitImage(final BufferedImage image, final int rows, final int columns) { int chunkWidth = image.getWidth() / columns; int chunkHeight = image.getHeight() / rows; ArrayImage[][] images = new ArrayImage[rows][columns]; - for (int x = 0; x < rows; x++) { - for (int y = 0; y < columns; y++) { - BufferedImage raw = new BufferedImage(chunkWidth, chunkHeight, image.getType()); - Graphics2D gr = raw.createGraphics(); - gr.drawImage(image, 0, 0, chunkWidth, chunkHeight, chunkWidth * y, chunkHeight * x, chunkWidth * y + chunkWidth, chunkHeight * x + chunkHeight, null); - gr.dispose(); + for (int i = 0; i < rows; i++) { + for (int j = 0; j < columns; j++) { + int x = j * chunkWidth; + int y = i * chunkHeight; - images[x][y] = new ArrayImage(raw); - raw.flush(); + BufferedImage raw = image.getSubimage(x, y, chunkWidth, chunkHeight); + images[i][j] = new ArrayImage(raw); } } + return images; } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java index 5556e0e..e55bc83 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/MainUtil.java @@ -35,10 +35,4 @@ public class MainUtil { return true; } } - - public static void validateArrayDimensions(A[][] arrayOne, B[][] arrayTwo) { - if (arrayOne.length != arrayTwo.length || arrayOne[0].length != arrayTwo[0].length) { - throw new IllegalArgumentException("The dimensions of two provided arrays (" + arrayOne.getClass().getName() + ", " + arrayTwo.getClass().getName() + ") do not match!"); - } - } } From 31ff370dc0d2a737c16061ed52230bbb694f3583 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Sat, 12 Aug 2023 21:35:52 +0200 Subject: [PATCH 17/52] Bumped to v1.6 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9da7ea1..79f9f2e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ First, include the API using Maven: tech.sbdevelopment MapReflectionAPI - 1.5.1 + 1.6 provided ``` From 8d11483afec151b88873ce53fe50575475e98b0d Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Sat, 12 Aug 2023 21:41:11 +0200 Subject: [PATCH 18/52] Fixed multi example for v1.6 --- README.md | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 79f9f2e..8bef8df 100644 --- a/README.md +++ b/README.md @@ -50,17 +50,8 @@ controller.showInHand(p, true); It's also possible to split one image onto multiple itemframes. For example using the following code. ```java -BufferedImage leftTopImg = ...; -BufferedImage leftBottomImg = ...; -BufferedImage rightTopImg = ...; -BufferedImage rightBottomImg = ...; -BufferedImage[][] images = { - {leftBottomImg, leftTopImg}, - {rightBottomImg, rightTopImg} -}; - -//--- Wrap image --- -MultiMapWrapper wrapper = MapReflectionAPI.getMapManager().wrapMultiImage(images); +//--- Wrap image (into 2 rows and 2 columns) --- +MultiMapWrapper wrapper = MapReflectionAPI.getMapManager().wrapMultiImage(ImageIO.read(new File("image.png")), 2, 2); MultiMapController controller = wrapper.getController(); final Player p = Bukkit.getPlayer("SBDeveloper"); @@ -81,8 +72,8 @@ ItemFrame leftBottomFrame = ...; ItemFrame rightTopFrame = ...; ItemFrame rightBottomFrame = ...; ItemFrame[][] frames = { - {leftBottomFrame, leftTopFrame}, - {rightBottomFrame, rightTopFrame} + {leftTopFrame, rightTopFrame}, + {leftBottomFrame, rightBottomFrame} }; controller.showInFrames(p, frames, true); ``` From 616e2157975f5d5ef3c10df557bd0e512a71e00f Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Sun, 13 Aug 2023 12:44:39 +0200 Subject: [PATCH 19/52] Implemented Reflection caching, closes #8 --- .../bukkit/common/map/MapColorPalette.java | 10 +- .../mapreflectionapi/MapReflectionAPI.java | 4 +- .../mapreflectionapi/api/MapSender.java | 12 +- .../mapreflectionapi/api/MapWrapper.java | 78 +-- .../listeners/PacketListener.java | 25 +- .../utils/ReflectionUtil.java | 488 +++++------------ .../utils/ReflectionUtils.java | 515 ++++++++++++++++++ .../internal/resources/map/map_1_8_8.bub | Bin 175777 -> 0 bytes 8 files changed, 718 insertions(+), 414 deletions(-) create mode 100644 src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java delete mode 100644 src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java b/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java index f0777e9..3963b7b 100644 --- a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java +++ b/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java @@ -27,6 +27,8 @@ import java.awt.*; import java.io.InputStream; import java.util.Arrays; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports; + /** * Additional functionality on top of Bukkit's MapPalette */ @@ -50,14 +52,12 @@ public class MapColorPalette { MCSDBubbleFormat bubbleData = new MCSDBubbleFormat(); try { String bub_path_postfix; - if (ReflectionUtil.supports(17)) { + if (supports(17)) { bub_path_postfix = "map_1_17.bub"; - } else if (ReflectionUtil.supports(16)) { + } else if (supports(16)) { bub_path_postfix = "map_1_16.bub"; - } else if (ReflectionUtil.supports(12)) { - bub_path_postfix = "map_1_12.bub"; } else { - bub_path_postfix = "map_1_8_8.bub"; + bub_path_postfix = "map_1_12.bub"; } String bub_path = "/tech/sbdevelopment/mapreflectionapi/libs/bkcommonlib/internal/resources/map/" + bub_path_postfix; InputStream input = MapColorPalette.class.getResourceAsStream(bub_path); diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java index 70b7368..7461be4 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java @@ -35,6 +35,8 @@ import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager; import java.util.logging.Level; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports; + public class MapReflectionAPI extends JavaPlugin { private static MapReflectionAPI instance; private static MapManager mapManager; @@ -67,7 +69,7 @@ public class MapReflectionAPI extends JavaPlugin { getLogger().info("MapReflectionAPI v" + getDescription().getVersion()); getLogger().info("Made by © Copyright SBDevelopment 2023"); - if (!ReflectionUtil.supports(12)) { + if (!supports(12)) { getLogger().severe("MapReflectionAPI only supports Minecraft 1.12 - 1.19.4!"); Bukkit.getPluginManager().disablePlugin(this); return; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java index 56fcaed..2cb8844 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java @@ -27,6 +27,8 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import java.util.ArrayList; import java.util.List; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; + /** * The {@link MapSender} sends the Map packets to players. */ @@ -82,8 +84,8 @@ public class MapSender { }, 0, 2); } - private static final Class packetPlayOutMapClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutMap"); - private static final Class worldMapData = ReflectionUtil.supports(17) ? ReflectionUtil.getNMSClass("world.level.saveddata.maps", "WorldMap$b") : null; + private static final Class packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap"); + private static final Class worldMapData = supports(17) ? getNMSClass("world.level.saveddata.maps", "WorldMap$b") : null; /** * Send a map to a player @@ -110,7 +112,7 @@ public class MapSender { final int id = -id0; Object packet; - if (ReflectionUtil.supports(17)) { //1.17+ + if (supports(17)) { //1.17+ Object updateData = ReflectionUtil.callConstructor(worldMapData, content.minX, //X pos content.minY, //Y pos @@ -126,7 +128,7 @@ public class MapSender { new ReflectionUtil.CollectionParam<>(), //Icons updateData ); - } else if (ReflectionUtil.supports(14)) { //1.16-1.14 + } else if (supports(14)) { //1.16-1.14 packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, id, //ID (byte) 0, //Scale, 0 = 1 block per pixel @@ -153,7 +155,7 @@ public class MapSender { ); } - ReflectionUtil.sendPacket(player, packet); + sendPacket(player, packet); } @Data diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index 7e8e82a..4dca45d 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -38,6 +38,8 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; + /** * A {@link MapWrapper} wraps one image. */ @@ -48,7 +50,7 @@ public class MapWrapper extends AbstractMapWrapper { private static final Material MAP_MATERIAL; static { - MAP_MATERIAL = ReflectionUtil.supports(13) ? Material.FILLED_MAP : Material.MAP; + MAP_MATERIAL = supports(13) ? Material.FILLED_MAP : Material.MAP; } /** @@ -60,13 +62,13 @@ public class MapWrapper extends AbstractMapWrapper { this.content = image; } - private static final Class craftStackClass = ReflectionUtil.getCraftClass("inventory.CraftItemStack"); - private static final Class setSlotPacketClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutSetSlot"); - private static final Class entityClass = ReflectionUtil.getNMSClass("world.entity", "Entity"); - private static final Class dataWatcherClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher"); - private static final Class entityMetadataPacketClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata"); - private static final Class entityItemFrameClass = ReflectionUtil.getNMSClass("world.entity.decoration", "EntityItemFrame"); - private static final Class dataWatcherItemClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher$Item"); + private static final Class craftStackClass = getCraftClass("inventory.CraftItemStack"); + private static final Class setSlotPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutSetSlot"); + private static final Class entityClass = getNMSClass("world.entity", "Entity"); + private static final Class dataWatcherClass = getNMSClass("network.syncher", "DataWatcher"); + private static final Class entityMetadataPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata"); + private static final Class entityItemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame"); + private static final Class dataWatcherItemClass = getNMSClass("network.syncher", "DataWatcher$Item"); protected MapController controller = new MapController() { private final Map viewers = new HashMap<>(); @@ -163,21 +165,21 @@ public class MapWrapper extends AbstractMapWrapper { } String inventoryMenuName; - if (ReflectionUtil.supports(20)) { //1.20 + if (supports(20)) { //1.20 inventoryMenuName = "bQ"; - } else if (ReflectionUtil.supports(19)) { //1.19 - inventoryMenuName = ReflectionUtil.VER_MINOR == 3 ? "bO" : "bT"; //1.19.4 = bO, >= 1.19.3 = bT - } else if (ReflectionUtil.supports(18)) { //1.18 - inventoryMenuName = ReflectionUtil.VER_MINOR == 1 ? "bV" : "bU"; //1.18.1 = ap, 1.18(.2) = ao - } else if (ReflectionUtil.supports(17)) { //1.17, same as 1.18(.2) + } else if (supports(19)) { //1.19 + inventoryMenuName = PATCH_NUMBER == 3 ? "bO" : "bT"; //1.19.4 = bO, >= 1.19.3 = bT + } else if (supports(18)) { //1.18 + inventoryMenuName = PATCH_NUMBER == 1 ? "bV" : "bU"; //1.18.1 = ap, 1.18(.2) = ao + } else if (supports(17)) { //1.17, same as 1.18(.2) inventoryMenuName = "bU"; } else { //1.12-1.16 inventoryMenuName = "defaultContainer"; } - Object inventoryMenu = ReflectionUtil.getField(ReflectionUtil.getHandle(player), inventoryMenuName); + Object inventoryMenu = ReflectionUtil.getField(getHandle(player), inventoryMenuName); ItemStack stack; - if (ReflectionUtil.supports(13)) { + if (supports(13)) { stack = new ItemStack(MAP_MATERIAL, 1); } else { stack = new ItemStack(MAP_MATERIAL, 1, (short) getMapId(player)); @@ -186,8 +188,8 @@ public class MapWrapper extends AbstractMapWrapper { Object nmsStack = createCraftItemStack(stack, (short) getMapId(player)); Object packet; - if (ReflectionUtil.supports(17)) { //1.17+ - int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, ReflectionUtil.supports(18) ? "j" : "getStateId"); + if (supports(17)) { //1.17+ + int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, supports(18) ? "j" : "getStateId"); packet = ReflectionUtil.callConstructor(setSlotPacketClass, 0, //0 = Player inventory @@ -203,7 +205,7 @@ public class MapWrapper extends AbstractMapWrapper { ); } - ReflectionUtil.sendPacketSync(player, packet); + sendPacketSync(player, packet); } @Override @@ -280,14 +282,14 @@ public class MapWrapper extends AbstractMapWrapper { @Override public ItemFrame getItemFrameById(World world, int entityId) { - Object worldHandle = ReflectionUtil.getHandle(world); - Object nmsEntity = ReflectionUtil.callMethod(worldHandle, ReflectionUtil.supports(18) ? "a" : "getEntity", entityId); + Object worldHandle = getHandle(world); + Object nmsEntity = ReflectionUtil.callMethod(worldHandle, supports(18) ? "a" : "getEntity", entityId); if (nmsEntity == null) return null; Object craftEntity = ReflectionUtil.callMethod(nmsEntity, "getBukkitEntity"); if (craftEntity == null) return null; - Class itemFrameClass = ReflectionUtil.getNMSClass("world.entity.decoration", "EntityItemFrame"); + Class itemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame"); if (itemFrameClass == null) return null; if (craftEntity.getClass().isAssignableFrom(itemFrameClass)) @@ -301,19 +303,19 @@ public class MapWrapper extends AbstractMapWrapper { Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack); - if (ReflectionUtil.supports(13)) { + if (supports(13)) { String nbtObjectName; - if (ReflectionUtil.supports(20)) { //1.20 + if (supports(20)) { //1.20 nbtObjectName = "w"; - } else if (ReflectionUtil.supports(19)) { //1.19 + } else if (supports(19)) { //1.19 nbtObjectName = "v"; - } else if (ReflectionUtil.supports(18)) { //1.18 - nbtObjectName = ReflectionUtil.VER_MINOR == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u + } else if (supports(18)) { //1.18 + nbtObjectName = PATCH_NUMBER == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u } else { //1.13 - 1.17 nbtObjectName = "getOrCreateTag"; } Object nbtObject = ReflectionUtil.callMethod(nmsStack, nbtObjectName); - ReflectionUtil.callMethod(nbtObject, ReflectionUtil.supports(18) ? "a" : "setInt", "map", mapId); + ReflectionUtil.callMethod(nbtObject, supports(18) ? "a" : "setInt", "map", mapId); } return nmsStack; } @@ -322,17 +324,17 @@ public class MapWrapper extends AbstractMapWrapper { Object nmsStack = createCraftItemStack(stack, mapId); String dataWatcherObjectName; - if (ReflectionUtil.supports(19, 3)) { //1.19.3 and 1.20(.1) + if (supports(19, 3)) { //1.19.3 and 1.20(.1) dataWatcherObjectName = "g"; - } else if (ReflectionUtil.supports(19)) { //1.19-1.19.2 + } else if (supports(19)) { //1.19-1.19.2 dataWatcherObjectName = "ao"; - } else if (ReflectionUtil.supports(18)) { //1.18 - dataWatcherObjectName = ReflectionUtil.VER_MINOR == 1 ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao - } else if (ReflectionUtil.supports(17)) { //1.17 + } else if (supports(18)) { //1.18 + dataWatcherObjectName = PATCH_NUMBER == 1 ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao + } else if (supports(17)) { //1.17 dataWatcherObjectName = "ao"; - } else if (ReflectionUtil.supports(14)) { //1.14 - 1.16 + } else if (supports(14)) { //1.14 - 1.16 dataWatcherObjectName = "ITEM"; - } else if (ReflectionUtil.supports(13)) { //1.13 + } else if (supports(13)) { //1.13 dataWatcherObjectName = "e"; } else { //1.12 dataWatcherObjectName = "c"; @@ -342,8 +344,8 @@ public class MapWrapper extends AbstractMapWrapper { ReflectionUtil.ListParam list = new ReflectionUtil.ListParam<>(); Object packet; - if (ReflectionUtil.supports(19, 2)) { //1.19.3 - Class dataWatcherRecordClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher$b"); + if (supports(19, 2)) { //1.19.3 + Class dataWatcherRecordClass = getNMSClass("network.syncher", "DataWatcher$b"); // Sadly not possible to use ReflectionUtil (in its current state), because of the Object parameter Object dataWatcherItem; try { @@ -374,7 +376,7 @@ public class MapWrapper extends AbstractMapWrapper { ReflectionUtil.setDeclaredField(packet, "b", list); } - ReflectionUtil.sendPacketSync(player, packet); + sendPacketSync(player, packet); } }; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index b4f5ed0..c7d15d4 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -39,6 +39,7 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import java.util.concurrent.TimeUnit; import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; public class PacketListener implements Listener { private static final Class packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap"); @@ -92,15 +93,15 @@ public class PacketListener implements Listener { Enum actionEnum; Enum hand; Object pos; - if (ReflectionUtil.supports(17)) { + if (supports(17)) { Object action = getDeclaredField(packetPlayInEntity, "b"); actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type hand = hasField(action, "a") ? (Enum) getDeclaredField(action, "a") : null; pos = hasField(action, "b") ? getDeclaredField(action, "b") : null; } else { - actionEnum = (Enum) callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "b" : "a"); //1.13 = b, 1.12 = a - hand = (Enum) callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "c" : "b"); //1.13 = c, 1.12 = b - pos = callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "d" : "c"); //1.13 = d, 1.12 = c + actionEnum = (Enum) callDeclaredMethod(packetPlayInEntity, supports(13) ? "b" : "a"); //1.13 = b, 1.12 = a + hand = (Enum) callDeclaredMethod(packetPlayInEntity, supports(13) ? "c" : "b"); //1.13 = c, 1.12 = b + pos = callDeclaredMethod(packetPlayInEntity, supports(13) ? "d" : "c"); //1.13 = d, 1.12 = c } if (Bukkit.getScheduler().callSyncMethod(MapReflectionAPI.getInstance(), () -> { @@ -115,8 +116,8 @@ public class PacketListener implements Listener { } else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) { Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet); - int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, ReflectionUtil.supports(19, 3) ? "a" : ReflectionUtil.supports(13) ? "b" : "a"); //1.19.4 = a, 1.19.3 - 1.13 = b, 1.12 = a - Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, ReflectionUtil.supports(18) ? "c" : "getItemStack"); //1.18 = c, 1.17 = getItemStack + int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(19, 3) ? "a" : supports(13) ? "b" : "a"); //1.19.4 = a, 1.19.3 - 1.13 = b, 1.12 = a + Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(18) ? "c" : "getItemStack"); //1.18 = c, 1.17 = getItemStack ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack); boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); @@ -142,18 +143,18 @@ public class PacketListener implements Listener { private Channel getChannel(Player player) { Object playerHandle = getHandle(player); - Object playerConnection = getDeclaredField(playerHandle, ReflectionUtil.supports(20) ? "c" : ReflectionUtil.supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection - Object networkManager = getDeclaredField(playerConnection, ReflectionUtil.supports(19, 3) ? "h" : ReflectionUtil.supports(19) ? "b" : ReflectionUtil.supports(17) ? "a" : "networkManager"); //1.20 & 1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager - return (Channel) getDeclaredField(networkManager, ReflectionUtil.supports(18) ? "m" : ReflectionUtil.supports(17) ? "k" : "channel"); //1.19 & 1.18 = m, 1.17 = k, 1.16 = channel + Object playerConnection = getDeclaredField(playerHandle, supports(20) ? "c" : supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection + Object networkManager = getDeclaredField(playerConnection, supports(19, 3) ? "h" : supports(19) ? "b" : supports(17) ? "a" : "networkManager"); //1.20 & 1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager + return (Channel) getDeclaredField(networkManager, supports(18) ? "m" : supports(17) ? "k" : "channel"); //1.19 & 1.18 = m, 1.17 = k, 1.16 = channel } private Vector vec3DToVector(Object vec3d) { if (!(vec3d.getClass().isAssignableFrom(vec3DClass))) return new Vector(0, 0, 0); Object vec3dNMS = vec3DClass.cast(vec3d); - double x = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "c" : ReflectionUtil.supports(17) ? "b" : "x"); //1.19 = c, 1.18 = b, 1.16 = x - double y = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "d" : ReflectionUtil.supports(17) ? "c" : "y"); //1.19 = d, 1.18 = c, 1.16 = y - double z = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "e" : ReflectionUtil.supports(17) ? "d" : "z"); //1.19 = e, 1.18 = d, 1.16 = z + double x = (double) getDeclaredField(vec3dNMS, supports(19) ? "c" : supports(17) ? "b" : "x"); //1.19 = c, 1.18 = b, 1.16 = x + double y = (double) getDeclaredField(vec3dNMS, supports(19) ? "d" : supports(17) ? "c" : "y"); //1.19 = d, 1.18 = c, 1.16 = y + double z = (double) getDeclaredField(vec3dNMS, supports(19) ? "e" : supports(17) ? "d" : "z"); //1.19 = e, 1.18 = d, 1.16 = z return new Vector(x, y, z); } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java index d0288c6..b998d74 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java @@ -18,205 +18,33 @@ package tech.sbdevelopment.mapreflectionapi.utils; -import org.bukkit.World; -import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.annotation.Nonnull; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; -import java.util.concurrent.CompletableFuture; -/** - * ReflectionUtil - Reflection handler for NMS and CraftBukkit.
- * Caches the packet related methods and is asynchronous. - *

- * This class does not handle null checks as most of the requests are from the - * other utility classes that already handle null checks. - *

- * Clientbound Packets are considered fake - * updates to the client without changing the actual data. Since all the data is handled - * by the server. - * - * @author Crypto Morin, Stijn Bannink - * @version 2.1 - */ public class ReflectionUtil { - /** - * We use reflection mainly to avoid writing a new class for version barrier. - * The version barrier is for NMS that uses the Minecraft version as the main package name. - *

- * E.g. EntityPlayer in 1.15 is in the class {@code net.minecraft.server.v1_15_R1} - * but in 1.14 it's in {@code net.minecraft.server.v1_14_R1} - * In order to maintain cross-version compatibility we cannot import these classes. - *

- * Performance is not a concern for these specific statically initialized values. - */ - public static final String VERSION; - - static { // This needs to be right below VERSION because of initialization order. - // This package loop is used to avoid implementation-dependant strings like Bukkit.getVersion() or Bukkit.getBukkitVersion() - // which allows easier testing as well. - String found = null; - for (Package pack : Package.getPackages()) { - String name = pack.getName(); - - // .v because there are other packages. - if (name.startsWith("org.bukkit.craftbukkit.v")) { - found = pack.getName().split("\\.")[3]; - - // Just a final guard to make sure it finds this important class. - // As a protection for forge+bukkit implementation that tend to mix versions. - // The real CraftPlayer should exist in the package. - // Note: Doesn't seem to function properly. Will need to separate the version - // handler for NMS and CraftBukkit for softwares like catmc. - try { - Class.forName("org.bukkit.craftbukkit." + found + ".entity.CraftPlayer"); - break; - } catch (ClassNotFoundException e) { - found = null; - } - } - } - if (found == null) - throw new IllegalArgumentException("Failed to parse server version. Could not find any package starting with name: 'org.bukkit.craftbukkit.v'"); - VERSION = found; - } - - /** - * The raw minor version number. - * E.g. {@code v1_17_R1} to {@code 17} - * - * @since 4.0.0 - */ - public static final int VER = Integer.parseInt(VERSION.substring(1).split("_")[1]); - /** - * The raw minor version number. - * E.g. {@code v1_18_R2} to {@code 2} - * - * @since 4.0.0 - */ - public static final int VER_MINOR = toInt(VERSION.substring(1).split("_")[2].substring(1), 0); - /** - * Mojang remapped their NMS in 1.17 https://www.spigotmc.org/threads/spigot-bungeecord-1-17.510208/#post-4184317 - */ - public static final String - CRAFTBUKKIT = "org.bukkit.craftbukkit." + VERSION + '.', - NMS = v(17, "net.minecraft.").orElse("net.minecraft.server." + VERSION + '.'); - /** - * A nullable public accessible field only available in {@code EntityPlayer}. - * This can be null if the player is offline. - */ - private static final MethodHandle PLAYER_CONNECTION; - /** - * Responsible for getting the NMS handler {@code EntityPlayer} object for the player. - * {@code CraftPlayer} is simply a wrapper for {@code EntityPlayer}. - * Used mainly for handling packet related operations. - *

- * This is also where the famous player {@code ping} field comes from! - */ - private static final MethodHandle GET_HANDLE; - private static final MethodHandle GET_HANDLE_WORLD; - /** - * Sends a packet to the player's client through a {@code NetworkManager} which - * is where {@code ProtocolLib} controls packets by injecting channels! - */ - private static final MethodHandle SEND_PACKET; - - static { - Class entityPlayer = getNMSClass("server.level", "EntityPlayer"); - Class worldServer = getNMSClass("server.level", "WorldServer"); - Class craftPlayer = getCraftClass("entity.CraftPlayer"); - Class craftWorld = getCraftClass("CraftWorld"); - Class playerConnection = getNMSClass("server.network", "PlayerConnection"); - - MethodHandles.Lookup lookup = MethodHandles.lookup(); - MethodHandle sendPacket = null; - MethodHandle getHandle = null; - MethodHandle getHandleWorld = null; - MethodHandle connection = null; - - try { - connection = lookup.findGetter(entityPlayer, - supports(20) ? "c" : supports(17) ? "b" : "playerConnection", playerConnection); - getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer)); - getHandleWorld = lookup.findVirtual(craftWorld, "getHandle", MethodType.methodType(worldServer)); - sendPacket = lookup.findVirtual(playerConnection, - v(18, "a").orElse("sendPacket"), - MethodType.methodType(void.class, getNMSClass("network.protocol", "Packet"))); - } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); - } - - PLAYER_CONNECTION = connection; - SEND_PACKET = sendPacket; - GET_HANDLE = getHandle; - GET_HANDLE_WORLD = getHandleWorld; - } - - private ReflectionUtil() { - } - - /** - * This method is purely for readability. - * No performance is gained. - * - * @since 5.0.0 - */ - public static VersionHandler v(int version, T handle) { - return new VersionHandler<>(version, handle); - } - - /** - * Checks whether the server version is equal or greater than the given version. - * - * @param version the version to compare the server version with. - * @return true if the version is equal or newer, otherwise false. - * @since 4.0.0 - */ - public static boolean supports(int version) { - return VER >= version; - } - - /** - * Checks whether the server version is equal or greater than the given version. - *

- * PAY ATTENTION! The minor version is based on the NMS version. - * This means that v1_19_R3 has major version 19 and minor version 3. - * - * @param major the major version to compare the server version with. - * @param minor the minor version to compare the server version with. - * @return true if the version is equal or newer, otherwise false. - * @since 4.0.0 - */ - public static boolean supports(int major, int minor) { - return (VER == major && VER_MINOR >= minor) || VER > major; - } + private static final Map> constructorCache = new HashMap<>(); + private static final Map methodCache = new HashMap<>(); + private static final Map fieldCache = new HashMap<>(); /** * Helper class converted to {@link List} * * @param The storage type */ - public static class ListParam extends ArrayList { - - } + public static class ListParam extends ArrayList {} /** * Helper class converted to {@link Collection} * * @param The storage type */ - public static class CollectionParam extends ArrayList { - - } + public static class CollectionParam extends ArrayList {} private static Class wrapperToPrimitive(Class clazz) { if (clazz == Boolean.class) return boolean.class; @@ -254,9 +82,17 @@ public class ReflectionUtil { @Nullable public static Object callConstructorNull(Class clazz, Class paramClass) { try { - Constructor con = clazz.getConstructor(paramClass); - con.setAccessible(true); - return con.newInstance(clazz.cast(null)); + String cacheKey = "ConstructorNull:" + clazz.getName() + ":" + paramClass.getName(); + + if (constructorCache.containsKey(cacheKey)) { + Constructor cachedConstructor = constructorCache.get(cacheKey); + return cachedConstructor.newInstance(clazz.cast(null)); + } else { + Constructor con = clazz.getConstructor(paramClass); + con.setAccessible(true); + constructorCache.put(cacheKey, con); + return con.newInstance(clazz.cast(null)); + } } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException ex) { ex.printStackTrace(); @@ -267,9 +103,17 @@ public class ReflectionUtil { @Nullable public static Object callFirstConstructor(Class clazz, Object... params) { try { - Constructor con = clazz.getConstructors()[0]; - con.setAccessible(true); - return con.newInstance(params); + String cacheKey = "FirstConstructor:" + clazz.getName(); + + if (constructorCache.containsKey(cacheKey)) { + Constructor cachedConstructor = constructorCache.get(cacheKey); + return cachedConstructor.newInstance(params); + } else { + Constructor con = clazz.getConstructors()[0]; + con.setAccessible(true); + constructorCache.put(cacheKey, con); + return con.newInstance(params); + } } catch (IllegalAccessException | InstantiationException | InvocationTargetException ex) { ex.printStackTrace(); @@ -280,9 +124,17 @@ public class ReflectionUtil { @Nullable public static Object callConstructor(Class clazz, Object... params) { try { - Constructor con = clazz.getConstructor(toParamTypes(params)); - con.setAccessible(true); - return con.newInstance(params); + String cacheKey = "Constructor:" + clazz.getName() + ":" + Arrays.hashCode(params); + + if (constructorCache.containsKey(cacheKey)) { + Constructor cachedConstructor = constructorCache.get(cacheKey); + return cachedConstructor.newInstance(params); + } else { + Constructor con = clazz.getConstructor(toParamTypes(params)); + con.setAccessible(true); + constructorCache.put(cacheKey, con); + return con.newInstance(params); + } } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException ex) { ex.printStackTrace(); @@ -293,9 +145,17 @@ public class ReflectionUtil { @Nullable public static Object callDeclaredConstructor(Class clazz, Object... params) { try { - Constructor con = clazz.getDeclaredConstructor(toParamTypes(params)); - con.setAccessible(true); - return con.newInstance(params); + String cacheKey = "DeclaredConstructor:" + clazz.getName() + ":" + Arrays.hashCode(params); + + if (constructorCache.containsKey(cacheKey)) { + Constructor cachedConstructor = constructorCache.get(cacheKey); + return cachedConstructor.newInstance(params); + } else { + Constructor con = clazz.getDeclaredConstructor(toParamTypes(params)); + con.setAccessible(true); + constructorCache.put(cacheKey, con); + return con.newInstance(params); + } } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException ex) { ex.printStackTrace(); @@ -306,9 +166,17 @@ public class ReflectionUtil { @Nullable public static Object callMethod(Class clazz, String method, Object... params) { try { - Method m = clazz.getMethod(method, toParamTypes(params)); - m.setAccessible(true); - return m.invoke(null, params); + String cacheKey = "Method:" + clazz.getName() + ":" + method + ":" + Arrays.hashCode(params); + + if (methodCache.containsKey(cacheKey)) { + Method cachedMethod = methodCache.get(cacheKey); + return cachedMethod.invoke(null, params); + } else { + Method m = clazz.getMethod(method, toParamTypes(params)); + m.setAccessible(true); + methodCache.put(cacheKey, m); + return m.invoke(null, params); + } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { ex.printStackTrace(); return null; @@ -318,9 +186,17 @@ public class ReflectionUtil { @Nullable public static Object callMethod(Object obj, String method, Object... params) { try { - Method m = obj.getClass().getMethod(method, toParamTypes(params)); - m.setAccessible(true); - return m.invoke(obj, params); + String cacheKey = "Method:" + obj.getClass().getName() + ":" + method + ":" + Arrays.hashCode(params); + + if (methodCache.containsKey(cacheKey)) { + Method cachedMethod = methodCache.get(cacheKey); + return cachedMethod.invoke(obj, params); + } else { + Method m = obj.getClass().getMethod(method, toParamTypes(params)); + m.setAccessible(true); + methodCache.put(cacheKey, m); + return m.invoke(obj, params); + } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { ex.printStackTrace(); return null; @@ -330,9 +206,17 @@ public class ReflectionUtil { @Nullable public static Object callDeclaredMethod(Object obj, String method, Object... params) { try { - Method m = obj.getClass().getDeclaredMethod(method, toParamTypes(params)); - m.setAccessible(true); - return m.invoke(obj, params); + String cacheKey = "DeclaredMethod:" + obj.getClass().getName() + ":" + method + ":" + Arrays.hashCode(params); + + if (methodCache.containsKey(cacheKey)) { + Method cachedMethod = methodCache.get(cacheKey); + return cachedMethod.invoke(obj, params); + } else { + Method m = obj.getClass().getDeclaredMethod(method, toParamTypes(params)); + m.setAccessible(true); + methodCache.put(cacheKey, m); + return m.invoke(obj, params); + } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { ex.printStackTrace(); return null; @@ -341,8 +225,15 @@ public class ReflectionUtil { public static boolean hasField(Object packet, String field) { try { - packet.getClass().getDeclaredField(field); - return true; + String cacheKey = "HasField:" + packet.getClass().getName() + ":" + field; + + if (fieldCache.containsKey(cacheKey)) { + return true; + } else { + packet.getClass().getDeclaredField(field); + fieldCache.put(cacheKey, null); + return true; + } } catch (NoSuchFieldException ex) { return false; } @@ -351,9 +242,17 @@ public class ReflectionUtil { @Nullable public static Object getField(Object object, String field) { try { - Field f = object.getClass().getField(field); - f.setAccessible(true); - return f.get(object); + String cacheKey = "Field:" + object.getClass().getName() + ":" + field; + + if (fieldCache.containsKey(cacheKey)) { + Field cachedField = fieldCache.get(cacheKey); + return cachedField.get(object); + } else { + Field f = object.getClass().getField(field); + f.setAccessible(true); + fieldCache.put(cacheKey, f); + return f.get(object); + } } catch (NoSuchFieldException | IllegalAccessException ex) { ex.printStackTrace(); return null; @@ -363,9 +262,17 @@ public class ReflectionUtil { @Nullable public static Object getDeclaredField(Class clazz, String field) { try { - Field f = clazz.getDeclaredField(field); - f.setAccessible(true); - return f.get(null); + String cacheKey = "DeclaredField:" + clazz.getName() + ":" + field; + + if (fieldCache.containsKey(cacheKey)) { + Field cachedField = fieldCache.get(cacheKey); + return cachedField.get(null); + } else { + Field f = clazz.getDeclaredField(field); + f.setAccessible(true); + fieldCache.put(cacheKey, f); + return f.get(null); + } } catch (NoSuchFieldException | IllegalAccessException ex) { ex.printStackTrace(); return null; @@ -375,9 +282,17 @@ public class ReflectionUtil { @Nullable public static Object getDeclaredField(Object object, String field) { try { - Field f = object.getClass().getDeclaredField(field); - f.setAccessible(true); - return f.get(object); + String cacheKey = "DeclaredField:" + object.getClass().getName() + ":" + field; + + if (fieldCache.containsKey(cacheKey)) { + Field cachedField = fieldCache.get(cacheKey); + return cachedField.get(object); + } else { + Field f = object.getClass().getDeclaredField(field); + f.setAccessible(true); + fieldCache.put(cacheKey, f); + return f.get(object); + } } catch (NoSuchFieldException | IllegalAccessException ex) { ex.printStackTrace(); return null; @@ -386,152 +301,19 @@ public class ReflectionUtil { public static void setDeclaredField(Object object, String field, Object value) { try { - Field f = object.getClass().getDeclaredField(field); - f.setAccessible(true); - f.set(object, value); + String cacheKey = "DeclaredField:" + object.getClass().getName() + ":" + field; + + if (fieldCache.containsKey(cacheKey)) { + Field cachedField = fieldCache.get(cacheKey); + cachedField.set(object, value); + } else { + Field f = object.getClass().getDeclaredField(field); + f.setAccessible(true); + fieldCache.put(cacheKey, f); + f.set(object, value); + } } catch (NoSuchFieldException | IllegalAccessException ex) { ex.printStackTrace(); } } - - /** - * Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility. - * - * @param newPackage the 1.17 package name. - * @param name the name of the class. - * @return the NMS class or null if not found. - * @since 4.0.0 - */ - @javax.annotation.Nullable - public static Class getNMSClass(@Nonnull String newPackage, @Nonnull String name) { - if (supports(17)) name = newPackage + '.' + name; - return getNMSClass(name); - } - - /** - * Get a NMS (net.minecraft.server) class. - * - * @param name the name of the class. - * @return the NMS class or null if not found. - * @since 1.0.0 - */ - @javax.annotation.Nullable - public static Class getNMSClass(@Nonnull String name) { - try { - return Class.forName(NMS + name); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - return null; - } - } - - /** - * Sends a packet to the player asynchronously if they're online. - * Packets are thread-safe. - * - * @param player the player to send the packet to. - * @param packets the packets to send. - * @return the async thread handling the packet. - * @see #sendPacketSync(Player, Object...) - * @since 1.0.0 - */ - @Nonnull - public static CompletableFuture sendPacket(@Nonnull Player player, @Nonnull Object... packets) { - return CompletableFuture.runAsync(() -> sendPacketSync(player, packets)) - .exceptionally(ex -> { - ex.printStackTrace(); - return null; - }); - } - - /** - * Sends a packet to the player synchronously if they're online. - * - * @param player the player to send the packet to. - * @param packets the packets to send. - * @see #sendPacket(Player, Object...) - * @since 2.0.0 - */ - public static void sendPacketSync(@Nonnull Player player, @Nonnull Object... packets) { - try { - Object handle = GET_HANDLE.invoke(player); - Object connection = PLAYER_CONNECTION.invoke(handle); - - // Checking if the connection is not null is enough. There is no need to check if the player is online. - if (connection != null) { - for (Object packet : packets) SEND_PACKET.invoke(connection, packet); - } - } catch (Throwable throwable) { - throwable.printStackTrace(); - } - } - - @javax.annotation.Nullable - public static Object getHandle(@Nonnull Player player) { - Objects.requireNonNull(player, "Cannot get handle of null player"); - try { - return GET_HANDLE.invoke(player); - } catch (Throwable throwable) { - throwable.printStackTrace(); - return null; - } - } - - @javax.annotation.Nullable - public static Object getHandle(@Nonnull World world) { - Objects.requireNonNull(world, "Cannot get handle of null world"); - try { - return GET_HANDLE_WORLD.invoke(world); - } catch (Throwable throwable) { - throwable.printStackTrace(); - return null; - } - } - - /** - * Get a CraftBukkit (org.bukkit.craftbukkit) class. - * - * @param name the name of the class to load. - * @return the CraftBukkit class or null if not found. - * @since 1.0.0 - */ - @javax.annotation.Nullable - public static Class getCraftClass(@Nonnull String name) { - try { - return Class.forName(CRAFTBUKKIT + name); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - return null; - } - } - - public static final class VersionHandler { - private int version; - private T handle; - - private VersionHandler(int version, T handle) { - if (supports(version)) { - this.version = version; - this.handle = handle; - } - } - - public VersionHandler v(int version, T handle) { - if (version == this.version) - throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version); - if (version > this.version && supports(version)) { - this.version = version; - this.handle = handle; - } - return this; - } - - public T orElse(T handle) { - return this.version == 0 ? handle : this.handle; - } - } - - private static int toInt(String string, int def) { - return string.isBlank() ? def : Integer.parseInt(string); - } } \ No newline at end of file diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java new file mode 100644 index 0000000..5a02c40 --- /dev/null +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java @@ -0,0 +1,515 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Crypto Morin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package tech.sbdevelopment.mapreflectionapi.utils; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Player; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * ReflectionUtils - Reflection handler for NMS and CraftBukkit.
+ * Caches the packet related methods and is asynchronous. + *

+ * This class does not handle null checks as most of the requests are from the + * other utility classes that already handle null checks. + *

+ * Clientbound Packets are considered fake + * updates to the client without changing the actual data. Since all the data is handled + * by the server. + *

+ * A useful resource used to compare mappings is Mini's Mapping Viewer + * + * @author Crypto Morin + * @version 7.0.0 + */ +public final class ReflectionUtils { + /** + * We use reflection mainly to avoid writing a new class for version barrier. + * The version barrier is for NMS that uses the Minecraft version as the main package name. + *

+ * E.g. EntityPlayer in 1.15 is in the class {@code net.minecraft.server.v1_15_R1} + * but in 1.14 it's in {@code net.minecraft.server.v1_14_R1} + * In order to maintain cross-version compatibility we cannot import these classes. + *

+ * Performance is not a concern for these specific statically initialized values. + *

+ * Versions Legacy + */ + public static final String NMS_VERSION; + + static { // This needs to be right below VERSION because of initialization order. + // This package loop is used to avoid implementation-dependant strings like Bukkit.getVersion() or Bukkit.getBukkitVersion() + // which allows easier testing as well. + String found = null; + for (Package pack : Package.getPackages()) { + String name = pack.getName(); + + // .v because there are other packages. + if (name.startsWith("org.bukkit.craftbukkit.v")) { + found = pack.getName().split("\\.")[3]; + + // Just a final guard to make sure it finds this important class. + // As a protection for forge+bukkit implementation that tend to mix versions. + // The real CraftPlayer should exist in the package. + // Note: Doesn't seem to function properly. Will need to separate the version + // handler for NMS and CraftBukkit for softwares like catmc. + try { + Class.forName("org.bukkit.craftbukkit." + found + ".entity.CraftPlayer"); + break; + } catch (ClassNotFoundException e) { + found = null; + } + } + } + if (found == null) + throw new IllegalArgumentException("Failed to parse server version. Could not find any package starting with name: 'org.bukkit.craftbukkit.v'"); + NMS_VERSION = found; + } + + /** + * The raw minor version number. + * E.g. {@code v1_17_R1} to {@code 17} + * + * @see #supports(int) + * @since 4.0.0 + */ + public static final int MINOR_NUMBER; + /** + * The raw patch version number. + * E.g. {@code v1_17_R1} to {@code 1} + *

+ * I'd not recommend developers to support individual patches at all. You should always support the latest patch. + * For example, between v1.14.0, v1.14.1, v1.14.2, v1.14.3 and v1.14.4 you should only support v1.14.4 + *

+ * This can be used to warn server owners when your plugin will break on older patches. + * + * @see #supportsPatch(int) + * @since 7.0.0 + */ + public static final int PATCH_NUMBER; + + static { + String[] split = NMS_VERSION.substring(1).split("_"); + if (split.length < 1) { + throw new IllegalStateException("Version number division error: " + Arrays.toString(split) + ' ' + getVersionInformation()); + } + + String minorVer = split[1]; + try { + MINOR_NUMBER = Integer.parseInt(minorVer); + if (MINOR_NUMBER < 0) + throw new IllegalStateException("Negative minor number? " + minorVer + ' ' + getVersionInformation()); + } catch (Throwable ex) { + throw new RuntimeException("Failed to parse minor number: " + minorVer + ' ' + getVersionInformation(), ex); + } + + // Bukkit.getBukkitVersion() = "1.12.2-R0.1-SNAPSHOT" + Matcher bukkitVer = Pattern.compile("^\\d+\\.\\d+\\.(\\d+)").matcher(Bukkit.getBukkitVersion()); + if (bukkitVer.find()) { // matches() won't work, we just want to match the start using "^" + try { + // group(0) gives the whole matched string, we just want the captured group. + PATCH_NUMBER = Integer.parseInt(bukkitVer.group(1)); + } catch (Throwable ex) { + throw new RuntimeException("Failed to parse minor number: " + bukkitVer + ' ' + getVersionInformation(), ex); + } + } else { + // 1.8-R0.1-SNAPSHOT + PATCH_NUMBER = 0; + } + } + + /** + * Gets the full version information of the server. Useful for including in errors. + * @since 7.0.0 + */ + public static String getVersionInformation() { + return "(NMS: " + NMS_VERSION + " | " + + "Minecraft: " + Bukkit.getVersion() + " | " + + "Bukkit: " + Bukkit.getBukkitVersion() + ')'; + } + + /** + * Gets the latest known patch number of the given minor version. + * For example: 1.14 -> 4, 1.17 -> 10 + * The latest version is expected to get newer patches, so make sure to account for unexpected results. + * + * @param minorVersion the minor version to get the patch number of. + * @return the patch number of the given minor version if recognized, otherwise null. + * @since 7.0.0 + */ + public static Integer getLatestPatchNumberOf(int minorVersion) { + if (minorVersion <= 0) throw new IllegalArgumentException("Minor version must be positive: " + minorVersion); + + // https://minecraft.fandom.com/wiki/Java_Edition_version_history + // There are many ways to do this, but this is more visually appealing. + int[] patches = { + /* 1 */ 1, + /* 2 */ 5, + /* 3 */ 2, + /* 4 */ 7, + /* 5 */ 2, + /* 6 */ 4, + /* 7 */ 10, + /* 8 */ 8, // I don't think they released a server version for 1.8.9 + /* 9 */ 4, + + /* 10 */ 2,// ,_ _ _, + /* 11 */ 2,// \o-o/ + /* 12 */ 2,// ,(.-.), + /* 13 */ 2,// _/ |) (| \_ + /* 14 */ 4,// /\=-=/\ + /* 15 */ 2,// ,| \=/ |, + /* 16 */ 5,// _/ \ | / \_ + /* 17 */ 1,// \_!_/ + /* 18 */ 2, + /* 19 */ 4, + /* 20 */ 0, + }; + + if (minorVersion > patches.length) return null; + return patches[minorVersion - 1]; + } + + /** + * Mojang remapped their NMS in 1.17: Spigot Thread + */ + public static final String + CRAFTBUKKIT_PACKAGE = "org.bukkit.craftbukkit." + NMS_VERSION + '.', + NMS_PACKAGE = v(17, "net.minecraft.").orElse("net.minecraft.server." + NMS_VERSION + '.'); + /** + * A nullable public accessible field only available in {@code EntityPlayer}. + * This can be null if the player is offline. + */ + private static final MethodHandle PLAYER_CONNECTION; + /** + * Responsible for getting the NMS handler {@code EntityPlayer} object for the player. + * {@code CraftPlayer} is simply a wrapper for {@code EntityPlayer}. + * Used mainly for handling packet related operations. + *

+ * This is also where the famous player {@code ping} field comes from! + */ + private static final MethodHandle GET_HANDLE; + /** + * Responsible for getting the NMS handler {@code WorldServer} object for the world. + * {@code CraftWorld} is simply a wrapper for {@code WorldServer}. + */ + private static final MethodHandle GET_HANDLE_WORLD; + /** + * Sends a packet to the player's client through a {@code NetworkManager} which + * is where {@code ProtocolLib} controls packets by injecting channels! + */ + private static final MethodHandle SEND_PACKET; + + static { + Class entityPlayer = getNMSClass("server.level", "EntityPlayer"); + Class worldServer = getNMSClass("server.level", "WorldServer"); + Class craftPlayer = getCraftClass("entity.CraftPlayer"); + Class craftWorld = getCraftClass("CraftWorld"); + Class playerConnection = getNMSClass("server.network", "PlayerConnection"); + + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle sendPacket = null, getHandle = null, getHandleWorld = null, connection = null; + + try { + connection = lookup.findGetter(entityPlayer, + v(20, "c").v(17, "b").orElse("playerConnection"), playerConnection); + getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer)); + getHandleWorld = lookup.findVirtual(craftWorld, "getHandle", MethodType.methodType(worldServer)); + sendPacket = lookup.findVirtual(playerConnection, + v(18, "a").orElse("sendPacket"), + MethodType.methodType(void.class, getNMSClass("network.protocol", "Packet"))); + } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) { + ex.printStackTrace(); + } + + PLAYER_CONNECTION = connection; + SEND_PACKET = sendPacket; + GET_HANDLE = getHandle; + GET_HANDLE_WORLD = getHandleWorld; + } + + private ReflectionUtils() { + } + + /** + * This method is purely for readability. + * No performance is gained. + * + * @since 5.0.0 + */ + public static VersionHandler v(int version, T handle) { + return new VersionHandler<>(version, handle); + } + + public static CallableVersionHandler v(int version, Callable handle) { + return new CallableVersionHandler<>(version, handle); + } + + /** + * Checks whether the server version is equal or greater than the given version. + * + * @param minorNumber the version to compare the server version with. + * @return true if the version is equal or newer, otherwise false. + * @see #MINOR_NUMBER + * @since 4.0.0 + */ + public static boolean supports(int minorNumber) { + return MINOR_NUMBER >= minorNumber; + } + + /** + * Checks whether the server version is equal or greater than the given version. + * + * @param patchNumber the version to compare the server version with. + * @return true if the version is equal or newer, otherwise false. + * @see #PATCH_NUMBER + * @since 7.0.0 + */ + public static boolean supportsPatch(int patchNumber) { + return PATCH_NUMBER >= patchNumber; + } + + /** + * Checks whether the server version is equal or greater than the given version. + * If minorNumber matches, it will check if patchNumber is equal or greater, + * if minorNumber does not match, it will check if minorNumber is greater. + * + * @param minorNumber the minor version to compare the server version with. + * @param patchNumber the patch version to compare the server version with. + * @see #MINOR_NUMBER + * @see #PATCH_NUMBER + */ + public static boolean supports(int minorNumber, int patchNumber) { + return (MINOR_NUMBER == minorNumber && supportsPatch(patchNumber)) || MINOR_NUMBER > minorNumber; + } + + /** + * Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility. + * + * @param newPackage the 1.17 package name. + * @param name the name of the class. + * @return the NMS class or null if not found. + * @since 4.0.0 + */ + @Nullable + public static Class getNMSClass(@Nonnull String newPackage, @Nonnull String name) { + if (supports(17)) name = newPackage + '.' + name; + return getNMSClass(name); + } + + /** + * Get a NMS (net.minecraft.server) class. + * + * @param name the name of the class. + * @return the NMS class or null if not found. + * @since 1.0.0 + */ + @Nullable + public static Class getNMSClass(@Nonnull String name) { + try { + return Class.forName(NMS_PACKAGE + name); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } + + /** + * Sends a packet to the player asynchronously if they're online. + * Packets are thread-safe. + * + * @param player the player to send the packet to. + * @param packets the packets to send. + * @return the async thread handling the packet. + * @see #sendPacketSync(Player, Object...) + * @since 1.0.0 + */ + @Nonnull + public static CompletableFuture sendPacket(@Nonnull Player player, @Nonnull Object... packets) { + return CompletableFuture.runAsync(() -> sendPacketSync(player, packets)) + .exceptionally(ex -> { + ex.printStackTrace(); + return null; + }); + } + + /** + * Sends a packet to the player synchronously if they're online. + * + * @param player the player to send the packet to. + * @param packets the packets to send. + * @see #sendPacket(Player, Object...) + * @since 2.0.0 + */ + public static void sendPacketSync(@Nonnull Player player, @Nonnull Object... packets) { + try { + Object handle = GET_HANDLE.invoke(player); + Object connection = PLAYER_CONNECTION.invoke(handle); + + // Checking if the connection is not null is enough. There is no need to check if the player is online. + if (connection != null) { + for (Object packet : packets) SEND_PACKET.invoke(connection, packet); + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } + + @Nullable + public static Object getHandle(@Nonnull Player player) { + Objects.requireNonNull(player, "Cannot get handle of null player"); + try { + return GET_HANDLE.invoke(player); + } catch (Throwable throwable) { + throwable.printStackTrace(); + return null; + } + } + + @Nullable + public static Object getHandle(@Nonnull World world) { + Objects.requireNonNull(world, "Cannot get handle of null world"); + try { + return GET_HANDLE_WORLD.invoke(world); + } catch (Throwable throwable) { + throwable.printStackTrace(); + return null; + } + } + + @Nullable + public static Object getConnection(@Nonnull Player player) { + Objects.requireNonNull(player, "Cannot get connection of null player"); + try { + Object handle = GET_HANDLE.invoke(player); + return PLAYER_CONNECTION.invoke(handle); + } catch (Throwable throwable) { + throwable.printStackTrace(); + return null; + } + } + + /** + * Get a CraftBukkit (org.bukkit.craftbukkit) class. + * + * @param name the name of the class to load. + * @return the CraftBukkit class or null if not found. + * @since 1.0.0 + */ + @Nullable + public static Class getCraftClass(@Nonnull String name) { + try { + return Class.forName(CRAFTBUKKIT_PACKAGE + name); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } + + public static Class getArrayClass(String clazz, boolean nms) { + clazz = "[L" + (nms ? NMS_PACKAGE : CRAFTBUKKIT_PACKAGE) + clazz + ';'; + try { + return Class.forName(clazz); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } + + public static Class toArrayClass(Class clazz) { + try { + return Class.forName("[L" + clazz.getName() + ';'); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } + + public static final class VersionHandler { + private int version; + private T handle; + + private VersionHandler(int version, T handle) { + if (supports(version)) { + this.version = version; + this.handle = handle; + } + } + + public VersionHandler v(int version, T handle) { + if (version == this.version) + throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version); + if (version > this.version && supports(version)) { + this.version = version; + this.handle = handle; + } + return this; + } + + public T orElse(T handle) { + return this.version == 0 ? handle : this.handle; + } + } + + public static final class CallableVersionHandler { + private int version; + private Callable handle; + + private CallableVersionHandler(int version, Callable handle) { + if (supports(version)) { + this.version = version; + this.handle = handle; + } + } + + public CallableVersionHandler v(int version, Callable handle) { + if (version == this.version) + throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version); + if (version > this.version && supports(version)) { + this.version = version; + this.handle = handle; + } + return this; + } + + public T orElse(Callable handle) { + try { + return (this.version == 0 ? handle : this.handle).call(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub b/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub deleted file mode 100644 index 1e22e3a122eb3ff6df0b047e3a13a62575a12db5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175777 zcma&NWmFXK*Y8U+^vuxB3@r`P4Fim%Akrm`BHbMW%+Mi7BS?tSB}fU3Fe)g5bazUp z)W!evoO7PD?pk-<7qb@gieK%$zx(su6EM9ybM9|9r{c zk1Ax2E)&=@LwwEftSv2v{C$0w!ul^06G;`g*2#*}j|7VHz8^{F5SbgvI%m4#*9*x2~;`)i@&=H})QuN~nc$%b#n z{FdwN#Lq8{ZlH>`9fLw^9m^y*vKOhVYF0)5IeJwAUxGD!4miI;LbM7C_p9NnNuc=< zqU3J;frW$>$>obURh5gUorT@`*_er-;A1N*s}6(8)n+pRp)~l#>y*Un+VI?;cUOvr zM<2MM7qfh{7uzWdD?{Ozd}~2i~*#au_}mywn%#(5z>rCSr77JT4&}(B8GM}uElPlfk#X5BW3&7!BLYn2Y8W| zYxupq1r%uA%ZsRRr z{t)Gd5Q(x9tdS?*NZ16NpcG)Q-OJSMz-ls0v0C;= zp?@A1{GK2f%UhQNx+NPcFFRTjefDUi*Y2yP;QMj-yROM@kWwr$KjaFLq_Ww<>Tv1fp>X|?PR_xqr^=_TEOlmah{{^CNaIg z=AYOw|B)w>ryg?pZ1~HEAb!KzEy8B~yZ@W;b-vQoaY|?2wo5>+@fyW5$ET4;|Y)E?KSJZ%u{+61@j{H^;=9|j&z7-KJOJX#S4e-JNBxnIr;27 z%Lfi_OZCtzac=P{4v zOxM{FuMQqV82?yOMZH^WNo|yEOOR?e;{xk2WI`oO_t>NACq?zA*8|FU{G_^6(46O{ zu(}={qaa(#=#i)U50SF4 zJMryHugC#q<5DZJ$VW&HurHPnqP7e5cluFt;6>vWpd-~6o0!UeEF_VS?ta&0+TSKJ%(l84K}M)lDsms)1k z%Q$V$eCeBX$J3e;k@fv=Xq|$Zy5bzACdwLD)i`ag%2|^}>oG zNEq^rF%mY6Kr}|T$6h-juOfmqAq0nU4WPa9 z2N6$$JQ#Z@SzOued!rk@!IhXj!A#;^13lO}C$*H%1GKTgT0fhTW?!mGp

A_uo9NRO3DB-IJ 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

c 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%0AvoE-VTDQ zy~yrSHOVQBjW?Fme-S~dN~~9AaEM`0DE9lKzv5IjR%GffjN%#li1FYmfw6^%%?$O$ zKl4ToEF@M1HJ*_upbHV|AxazqY$ z=_s})I^Xiws(Osp9ymAj?OqiNTavvpBn`$%1*;!wgZ>9JLn~r4BEdr$SY^aU#6oGO z4=Ct|J#PZ}RKgWcxS@fU)A5gjf(QDz%?C%TQlMfXR+eZ*qKX? z2^BL5x}{jSuVTdv_?-mEl!%;GOKuwTcIWQdnSpW`-waQ6VQnH~DO^5M5sFUOn*P+O z;pn~o+Q7gtlQb2vdUXj%7h_9N+1SBD*0t`n$_bBYR^z^=5GN6y#wyAa?XSqDVK=sJ z^p^LSBRO5v0;eTA!>)CdI9xXZ8s7k9@7;BKRudT+@l_I=SX1qZk3R4bIS-|(nbzHB z!hpj&mhny(O%1Yk!-%z}Z=fs@Hb zMIpiuH4^yu3ycqX!O3U6HnDoN%XAK1v7v*DP|DHy_Z*+8gsJRclAN0jBdS=I!y^h&F$u-3-DK>(8!%uH={wkg&DO+|B9`>9 zG>5O)_w<<^kQl7C`&8}pQMY^(U)z**ANBXD0O4Ok-`4#D{@`_#FXhmK@3zHrV#%d! z`Q!Ef{;EDA#nRIPc~jNStl{Ij=QkbejEfI>?%$z0Nsx@)UGcANXaxyJ&WrlS`wrPx zKlh6rNhYzjk$3Ojf^;*H+N0uNgc_EB3p2BV#A~R1u{lL3a@IBF653N*LH*d5bmK;S z9v*Q21)PAa>aQi=H-ktM?kM?=GS9*Q3 z*v5N4GIVO+*OsSJ&W~yZzJiWK2 z7U|R9*Z-AiVGQq>w>hNkEb}f9utTB@4CA@fuPNQ)x6dKH0LmC-UTe z=F=z8bTu7KhX_Gk=>Tt>h5vgbq5o z2BIMl>5%qSB4;4`k2s##M)#T7)T=<|P zo0qDY_A}>&S|iqj?L<@~hZ`(97i?MM-3wZ6>6tUs0jSv0tjvP^3_!d9^Z(X4m;1KX z&~J0y>~rc4;@$22KH{|_&ifL!N4S)nhc#ax|Lv#9F5MTbl@m%5sk*IK0^R1Tt#hdU z2ezQ@3y-+R^-?#5pBbWJ>yBURH}yFe3HFHRAT>>@qmDW~ZoYfZonmLn_#dm5T=?M3 z^@vk9z9?R}Q|k?=23qx&#Z0K^rY1ryJf-HU2oO7c{U>FD(BJ(omSk{cuWiWhS@jLX ze#X<^v=or_XB=bB08>%Ml2UI;km?>P8jq{AJWD=o3R?m0Nmu>IL5a-}I9-y~*1hwNEV2xd7C}odU)1h{`E;jL32C8pZ z@zP6+pMS_POeTJpg~R$sBq!E9E1ChVTnV16p3+DrOo(Rokkq)ayVTlBu3Eo_Sz-5R z|CAj@w zLOyn<6J#m#h{O;+QiembAC8dfbO<`H9s!X}f@uMTBIy4YJ6)fbcCpYF27b2p^;a7& zp5t(g(@Yd!iDa>aKr3%#3c|+SWl88QWMn!*4q}<^N$3w&!<2Rh#W{LCY9yj!D4Goa zPYGh>H3FQ(htEl^GU8AnaUA$*vX^3s(KD!i%;Rbf6<$L`KwOZe4T;GSwjb=-UBhWM zYpI^w|Jxi>^pMw$mwqD)ix%E_GpzR00)}sZ4vkpeEB@$t@pu@2=DL&S%8Qr%<|m){ zTKVZ2-76tw6C~0Jt((6%=y1;$scMI_B+|bVsb12m%h~=N>G^rnSg|1Yr+L@>4@Lxt zFg0GA{<(J5ouoDSRjGcA6If;Qyl+f@y@fRMAa&=3$`0KVixs;aKNRECFDhSf(dvvN z^w=UV2o`?zs&XUkfzvDRl{i_zLhXBT5p8O~urD7l2`815raV2MC2T-<qyIkD( zxdG@!udGR<;Kre?Yfb~iIAo-ecJ-NJu@2h8IL%9F0Uni+y~<3nTpY(nB3Kg~Sn(#$F=<*&-3H))hGI{JaVnkF|#@eXK zn;)G7N*YPkDK40fJ+`CUrkl~&wXgJv+=R2qZe3v{7<8|DS}dn#RnN(JCwvqeHV#ez zR&a3)x$JjM;>y-JE3b@MuJ(yrk#%H_=Y!a@vsk<*k2}*8(zN);JqJ(s8?^XweeWH! z>3ECSZZU3~v+AGi@nbra=hwzuQ98O4nXtxRgYti$!Jcizh?z$hEWLQI;~@R+F%nNv z+>pAq9PPl@og4x)&Xe0!Wz&aflDNx&`sun#QNs|ntLrVGfnqkZMoma{e{8vKU1kES zFGN2$f$c7RPwO}~ZFurwdWVYM&p`euzw?4wX~FUnn`~{}fo$$GZn&)4+yOl={W_D1;~4gNeg12e1UR;Ll88C#Rh?< zHmC_5Nq`)esFV{C#G~eD++&tEZlauRR3wtg`anrhocJO~rX0hwgIEb>v?Bj-rM|3} zQ)dUNU~IfbO5QvR_$h1_`>5Kt!uWZunkOp}$3WWVio(Il>IF27(Yi;gQdSd8dzwU{ zksA1S4?za=iY=yqm1h=yHhQ5rS0dsMAZs!(r9rsGP}KkiAQpoeIO1*^2Rm}NKeD?@ zz=6`t0lz}e^JNWb{pQi&yy{M@!K5E& zVry>gGpm`Fl^0Zm-@)eI){Q*g#QigS9lpy%KHb63Kk!jxZ@jhFr<}q-gr8o1``TN7 z8)qV7J!z{;iybbSDcD5JE{{a~7CZo1+MCW}tl5|~JQc@~p4h~0CQ^xkkR(5oj^*6c zBf*XO5AZE)^CWabrn;x?8!gYcUq}LHW3w1kK^P==Yd`(V*m(UJ{6!`bhQgPr-4FFB zLW>IN8#3`a{CJn7RUSa&zf{Pa%4Di(ANp|LZ*pX&71>yb(5_#Xq) z@fOI2#u+#@4ZNp8pOPt3Q)E3((HYSSDXzmFo^VJGVT=!sFWEES*8#N=(l~&;|NDvdNuZeWVvPQQ1#Vv=#4(>hy@} z6$%_Nyh4S&qUwPl**1(gD6L1h;df_p5g>hC6&jinD+e(Jaah&3ZB_%t0#TQa|1z^x zD60K4TCx4?d_lh>(H)k5e<|$OxAnXBoFDwj*UMZ^5Ezv!EujmZL)M0!!i|fu%n5h~ zsR^OXxdV6^6Ms6xHiv%hc$;={@93z4cgNs=KioLy8Y^VGoMa$-{!IZbKE zfZ-fhaD0A#DdAc9hXr84khrE|)BR9|BtkP5x$~521u9Ua76YR=EKzh3W=JnXuq!Nq zr`~+9=!V7Q2PVJ>5GvB;DkxCjjX|?elNz2#f~b;5aGuBk+1Dp8IW=^iXVAm?Ku7`8 z#1t?S=LrQ-vFOcQ?wnduh?nGbRlGFjOw5LCIA~qyY|@6SM#h1@?kAF2LBjJ>@$sO^ zDijg}s!rrzoxS#!&1x=dV$)b;1_wbN##1V+uCnFhRNRsfR%YQ^&$r?;Ft=z-T4En&OHy zc)psF``OIJC3Ilq)X!ma#~ZW}Jwk1)+{zlt^dXgW`S-qI(ftJ}Ef_rUjU^p7|5dGh zO-)yE;rXRJFZxfic5&{VQRlwdM_lmX1DJRZ}5(o{5*Vowcly2|K`72x2Ymq2sfu0tJ zd!87xYH5j+MS)}g_^gxm@WU4;9vm8}J@9$EnR(Mqk}J(xu4L&E&c8a>h>e_=+5+&d z#&^DxlVaZUW_RP|6NQmCSG?W{39_CdGf-o?RmF^=Zpd>2iy)2P-6hM(n&Ixwe=Zbw z#?oL|IP&*R4wn;rrx*x1vSVAb03u?Y9)C#Qx&AZ?37x;Qs5Hw}T6}!AHI;~dWB1vj z*LI{j5EyP^3U4Vmj!y`|v3vsU1%_RFrf>&k!U3`h!ezhtNX)g6LPF)S8D!RCKuXP0 z&DG3i{2!2Hh%~Gwj&i*Nl9#B4K>kMZ1b}l9sShHkMH`1WHw>m~BN~zZ=WP93gFKDr zXCi$E^+`Vk*$PQGgC(X_#yN#rxL@?*sQ02VAw>D4!$?`t88vDgOKbsj2Fay)w4X_< z6f7jO`V>-!^CC;no>lL9rJ73QAcYIoe=*QAzfS_Fr1+4gtk{t?n|0!6GMF1)3c^GX zq@(yZohD8qz&^piYVsW^Xr#GfeYn^jBo{FGw~P6`(ivL|zQHkv-6H?{;R%6^yCu$T z9`u{>wjhD~SPwK{^Cv|e{$95sKJTx$$sL*Kt?&$4Ic8$8kMb7teta2;l;Kp%MPK>j z@88?fUQSI|Svqu{vEY>4mK_mksa)3DcnR+ZP2%?iE7Zv?={}dem=yCk>t(Q`q>3NI z;wZxkUWd%D_4pyz>hb@MJE!i-Gzz+H3WHKC_KX+mhlQK4*IJXGRp9!J=t7HQb(ANL zMLbvilUI{dmhv^jG|oSYBXBtF!y14u4e%wnS(VHeBEVmWQH0i4V31Sc)@W6OFVa6T zn4)N*5C>1YFVGK^x$szJ9Jn5%ve92!$vaRV8Ywfg(!L^oD-m5HmbF=Tlld>HR&(Siv z82k9gr5(qfS*a#t@CQH1arkm7UbJ7*Fm-E&N=EH2Z!|-7jA&$~9lRAO@%xd7cO=oZ zfCU_U^8LL2i$Tt~B#K3$4w9VCsw`SrxscXPr9032`pCuG&-fd(Q&(xrZuEQSFctSD zhgJ9YzIXGFTI{L2J?`7vyH9L%*RxYZOq$jt4eTd{b8x{wkLSo+yB}^M65>hA4DvxW z^@9NIffdW{lPYD_x^=zxvWoRs59D&^zK6k9hYOfJ9V}&~SHAqJ{|W*_k{BahrLPqC zMepZw1WV6*wTH#v2`ti|A-afTxABtFFNN~kpzi7@nE&sqXfm;D`gm0WjXDaEXyo`$ z^x$*aWJH-=%k0e{n;h+Vsl<88n%JsF@l$aC)ZpuGsTBu(c?RX?WST{k7UA#{DHWJHdt&)9d)!9T&ZGI>oxd<<-6RLEij{ zQ7(VVyviEd*^ke2l&|+Uv2RY4zUi^sSt#OJ!YHvjwazquDFDqIBqsK6pLWIdPEN6ChY|h62v3Ud*tsvu; z&|#;<+SR&)YKbeVtiIob@7=+T8}8^|vZVFActoW=aBjR8NZn)P4M%<+H-L_-61yL} z;%|Aae;R@v0&hKf1UjCfX?w?*HP)9~@Y2cyaC}fdMMn_dmkGO2oj8L<4(dWBUzliOHoLt^^V#53Jz-jC zkXT$(F^NVJ%G(Y2iVF&g^E1}0&Zs+qGoEb^^+F`uOJpImf{1vcDN7;o+a-lg5wqjq zbiPf45=9ZupI*MG7{W8ehFyeq6#U$H5xvVP-#{(+HDL{rA-UQB@1R2gSOQAqw>uR3 z-iluIdP>^r>hZ%A`)2=w$|A}tS7M+e&AfqCdtqW$#P1kvDrj3~N1S2<%V51srITDy z`LJ=J;0w-&%^j9#Nc?N6Bl?@1sx|GUrR8hxSyX}y(ZUbl$ok!DNJC&XL0MUgexE6xn|+Nn3_vl%JMKfvPQiq9Gj z6B0f&LslgZ@a6q^Wr53|MU6}oWjteSVgPudrI@X2SyPxiWJe@+GQ}{hkdbzL{>A!0 z_my;{;#fREB2a&M07N!x=r+sPI+n$g0Yuh9Cj1H@&-ws4A5hyrJ-) z?uN1s5%v2^2nIDH8}_QKCnL)JNV^fL<-yYp*DYVCHGvyhTh&gSyv|19tnDkYu1KMsX$tvAS zcTCC-e^1(9R9f2W+%Z-6AC7|YjJ>q>e3Pdp+`7v()b9Vw*jPsZ5Vk&OKrS~*!~Ss; z&WKF@V(l9YFA_=Hj<_pQj(6I`x-s7TcIhBCNFI|Q+E{f6PB-Bey2PQT@r6_}sk;M? z5}*8Iyx`q(uz&;=lH8t_fs`a~!xA!x+MT|<5y@m`$lnXy$y|Nj{09d&LxUxiX&R92 zIxdu`M2LtT<8(wY9|gtuOa}x5M4l{F6AL{B2W*0r<`|8m5*yw_J_Z{vc-0f|dH>cg z>77q$Wv&xiNhfq1c7^LC=Xasl2J23CP?rY_FS>vHT}|%V&H)A~`PL2fMTejL-M#bM z$`^{5MlLHWxoZ-ZuiYV&8tr|Hnkuo>^M|Yh7JoCV7cq7r|IXE32k*+vj|4OO0k_|K z+mqBLbm#&XP=m>8QoWu}lRFXa_~HfhN%L-9V@GZGUyqSTtU7S-;KtI@^m5ixJN!di z>k8$z>jPL5tDi}yl2d1YIo=c@FK}nH#xI@4#K!!IFDFAJlu@ZX&>i-)-YZjxn4b4@ zBk3O4ynK_v{)BI83P@FyP-{Phk03`n>Y`NdibM)lcxY9SBOT6%#&xBorK>F>!xPSo ztJBd=c{er*Mdc6Ujcd^U8)`cXhn#XaewQ zossnS?{Rv}kL#Kd_x-i)PHdMC-IM)uFLXzBX)T&W7%Kd}!pWKaH~FBvnuptiO8{B} zfo29BC$>oH_IB zVqHXB4KE6<+MS^Y`R2EzUmu!ut74m#Yt??x*ESgNSL~tU;^S|$r*iH#K5gcMo<3vVT~aZuUESU;YUY~oS>yTCy%lmhuiB66+cfEL zwMH?DF4pF3F{cOhgDWE0=W5;O{)CY=#wN6;`d*n@no)!scOUOyN+F7vDIh#;#y=B? z5~$f{8xtlVRO2ZlR(rVeIRWlqRuPA~ywC3Xib0?L!E#p=b@|X-Oa< zA%qZp6W)IKkBP}zr&vxbXlxT7Kko6><1>+?gSt@bJw?yP@=YQkFXCv;UqXb@%D;&g z{z>tl3~P_Bc&4T|T12bxQ1x%pRt5f;8RBF=gP=i?J{}NamYzvH$~-$ZbA-8u*3RJs z@7(w@48ogr5;ZPVo0B%djPHL$>iW>dAY$D3?Wk5AtC{0( zGcN0Y!u)OM+yT|ogsWfVwNwJq`dsdENkv&)+*wQ2jP+Y1Yu<`q?Av@yhw0~SuK7Xw+9L7Wb!!4Q}UKCir{gfcSDf3EtNhRSR5H|E3q zW}t8;9Tf{mmWSfyGCAFR1S0C783zsJ9Z!_a?ezF(2|1+?+@iMnJqZaHr%wAczIUx3 za^!N68urnJsL4<&bCH4-IXG=oazy-lq*x)O$QL$8&5IoY=GW{b3VkBzKH0cbYmnV2 z^Pwdrg4V#oS1pCO((K0JveO|8$|xxnJWE{H_I}H;5!OD#k0La+^ib0h0! z{~0!SA547ze9K396B>g(aZ623XO-tS14S+KVtQ!2T~_sK|Jx@%#B17wPtuh5XzHEV zi5^j331^Cl#{P=Yc<@@0&-{IJR9Z&QTPJ8%{c39|gt>Q|_k1&!6=u@b^n5(6VEOGI z7LwJ|a|z70Co|igfl9uip=3CQxEu!w57GI(?HJNEh8qh zfI5N=KiBFp$bWu%vt)*D!-fhWt$8>rb>(*aIX3a9Gv(aJ&|tzA>ml?IHNCmf+x99v~4E# z$KqD4oCwhx5MbIPvsIjv)gzr!pQZloL7v8a_C6g)w#<7r11Fe&1lS~QUV2hH$r0|L zISH{#hDc#X8QP2j8vDP1*Y&hAlyg4@jMl9= zsFvrAVX<<&Yg#l1ubiVO_f9?c9SqLpJ$l<a6Vnl$KHyax;?e9b)q+|l51s>aWO$o^QW@2N=gn>lhA`wlCEunw{ zge&Q2xz}75yl15R@CBAG(Us;EwAEk<*k1AT_jT)tb1U?s5E2y5PK)T?k!LN*4)W)< za#4u5)|%L}5IouV8F>$`S2Q`e36$^84z-_#%C+(+gN=DaiH}N5E5NQwRCciv$Wx;m z|Df?x^$O~*R8u`|l$nswcUf#I9rRjFC`#&lpJM|O2PTi^+&DFEXTYYMw=^Cd{`L7y zv?T{X!TfL3a6H+{leW}MYki`&r}xB`abhK16jJZb`Ma)u*z6b>5ex`DUxXbE^H0{A ztV#IbOG|@GAdt+fX1}Klk9vOWENN8GoW(pXj0%kyB(O6LTHHMBMB1eWT`LRN#M(NPxs~KsV8e84C1^voRm=@jExiMhxn7l zfx#>aB=h4q9!nFa|0v!4^ zj2IlROGu#5mSDzk7f*2b@+&CB;sYAu5*CBuK7v{l4Fi;W0tDG177HkBjqr>l@QOn} zzdD?9&_U#VKQAl;cu&h!sP=p)0{tSmP^$~|cEd+O&5K`7#b(6-;aD#}Q( z(T4@U$9%Fo6N!KqlWB3Qc8Zw>fm_~2F&U0uW4ieq-|Yp}>G+#EXk7RSfUe-zxRa%E zCgG>EnD&vt-HP5iohp%7QsU!-q*q)#O~8mGz50#k*t9+x{c;=pH()OurPyRhs}*~8`Qmr9 z_Fr~wNLysRv|f^JGNCcyQxbb7Re3z!$~JG_yd!jaR>W)XwV%S+BEe!@u59Plx52^` z*@Nab@*=mCH!_D7EjXi=vlac|=fVBpH|+cKUvTxEvRX@Vu^PKFgG;zIR@W zjMuC0hV{Ar`-x3N42}2|a4VaGcF#*Z9KtENP(;_@em6UJYeW0@V2~)F{hJA*i%1M@ zn_!MPGMn)C;qTJFcWc9N;bx!Jhy>^&9+c2d5dLUZE=tLb)T+<7? zyuOcfNkk)OXYij$MWq+{qbE=m3PDxt<2L*2P6TSbWLN}a<4pO>;cpl5wo%pT=owA{ zap0TAS`Y^s9Ez5*l&l!!Sc_n7D4ZtQ@98N8d8gB3YA!Vy|G{3cC(EdrOn{AG&G6C` z=dVf2?2AU>ugUENUFrP%jMO!FAzOSf5rP^BtX5m9bKdgfaIT~^h506RW1Suut{~2k zbUCw-CLMN(44OjB3of}KWte!al_IHhtP&FX8W>$hYM>}?k(k+cT6TuA213wmAm zGZtJZn_HRp#%o&}0*V*#+M>)eyCa?TBNpEB44{!cR3;A`+0#s`^|ZV6qKgH2QBjNE ziq+5lf&MXj-`2Lr-&HKk4D>PedWGI#53MA)u9DGL3R8jK9fa<^R#!+MUs4w3qNYhJ z`e2nkKSwyf#V^S4aDsQdF-(>+gO9;BlAOyWYrIk9DzPag{>F>BtV5B@MjHH8%6yj4 zLd#79Bbu%R3MyM__&>wLYA^rMukp6b?Mg9?iET~TDl!2M1Y$Lk1~SE1)&RLl9wl1J zLtW=}vWAp(2vboqy2OG}xm~vTE7~hs@A5bly4PP&6cKt%4%r|5GdyU2N_)##C~%TG z{^sSqjf$3}&Yk(Ekz}I`{Z1rVq<-)90%)mZq-9jQ_cSBR zWHTN;=ROpt)=Eeg&;M)c+T)>E|Nk?4Ggxc4!mhPil!UT4EiJK>bfI*SIxfw+RdQJ+ z>E_I?wU!W$N{2+wH~NxR3L%D-N~<`P2&Y9CMM92r()asY=ljp^&zaBV{eHb?J~N-^ z^~^j^;j;fXGqdo;1SiAUJLKu-z5m-vVNG(tKXXVW8+%GD3@6M7tFvJzn+tc{e}3-DeUe;Y|8uje;r?KsM% z4{__4s++d5mQk?FV~b0Q&y~k9)*E&oP@4qnHGq#5PzeYHGk7-sp+o%kZYo>UkRR5F<$xypsAhs;`My{&ZhACl?Ot&Jc-rMexS~u z3^B*B3jDI!wbQlOP3n)%80+AkkJ9QxQSFrnS)g2G7u_Y==U?RVU{4}(Q=%dU_Ag)W z5aM^6!uKm1MSApAM;o7f^h4FmpTG3Vt?rA9_h6EEMf$wF9eh`fVL$d<=f?C!Pr`RK z43oQB29X`C5E5Z(?Zek|dTdg$Dl+cp3w?Zz+06QTbcvm`GW5O|I#94?fB)3 z6&QB7waBx-J2U?FhZmJxW`Bat??;)*#}rXDwu(LK3Ubky_7F7VimoP_IU0Iam#{t^ z^n6wx=$-dRR3phRj>%%lZh-zRG%8hpSt1d8J(ux1YmukZZ!B-4&V{VIZ^p0D_hTLi zX(SEm>F}ujwvkp;{%MNDX4A}A_+X}a=DUNL7fMh3R({v;Z26}MM`qxq!0hPf zgLVTt%<%C3o~y8!Dy24A%M)0@sJQ z3z?Pp<6FsM9cDieqa1=5{tv0yO$#@4$aMN2oU03=ff-WR58qEZd@zNA*w<7=JsL4A zA`rB3ZtyqLKsPCxSLz`vO_e45d}a1_`Jr^-nhICWDr;UnRFnAu*_s1j9Bh)n%6@q! z`#(Ct)GxPX3rosYnw!bee;^S7u{G|WFX+f$Y)pyL;`4eAG18H0m7gWktHS8kz-sqTswY-@eLkioj)fp)Dl zeqYpc3#H4u#fJb@t)loPElA^+XCL3XEB!Vuv{zo5XUmISdj#q^x~7$_$(w0Qa(q#0~O zsryIEyB%8T#c>=a;HZ1Yp<~0x)TDX)bB|bJe&lebdqBnyjP%e+>03F>)%sA;B?z|% zbQDftP4N;gPu3T$(?is?5iO~E^`e*k8K6hb2YBp7b%-n_2?2yg%0D9T8dnP~RaV@G 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 From 592c270e6861ca850b5a844bcd2bd08af34ef434 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Fri, 22 Sep 2023 14:35:41 +0200 Subject: [PATCH 20/52] Bumped legacy NMS branch to 1.20.2 --- .idea/encodings.xml | 2 + NMS-v1_20_R2/pom.xml | 72 ++++++ .../nms/MapSender_v1_20_R2.java | 138 ++++++++++ .../nms/MapWrapper_v1_20_R2.java | 239 ++++++++++++++++++ .../nms/PacketListener_v1_20_R2.java | 126 +++++++++ pom.xml | 1 + 6 files changed, 578 insertions(+) create mode 100644 NMS-v1_20_R2/pom.xml create mode 100644 NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R2.java create mode 100644 NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R2.java create mode 100644 NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R2.java diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 4879e46..eb43565 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -25,6 +25,8 @@ + + diff --git a/NMS-v1_20_R2/pom.xml b/NMS-v1_20_R2/pom.xml new file mode 100644 index 0000000..337240b --- /dev/null +++ b/NMS-v1_20_R2/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_20_R2 + + + 1.20.2-R0.1-SNAPSHOT + 17 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R2.java b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R2.java new file mode 100644 index 0000000..dd25b06 --- /dev/null +++ b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R2.java @@ -0,0 +1,138 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_20_R2} sends the Map packets to players. + */ +public class MapSender_v1_20_R2 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_20_R2() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().c.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R2.java b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R2.java new file mode 100644 index 0000000..98ca1e2 --- /dev/null +++ b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R2.java @@ -0,0 +1,239 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_20_R2 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_20_R2.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_20_R2.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_20_R2.sendMap(id, MapWrapper_v1_20_R2.this.content, player); + } else { + MapSender_v1_20_R2.addToQueue(id, MapWrapper_v1_20_R2.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_20_R2.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().bR.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().bR.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().c.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_20_R2.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.w().a("map", mapId); //getOrCreateTag putInt + + List> list = new ArrayList<>(); + DataWatcher.b dataWatcherItem = DataWatcher.b.a(EntityItemFrame.g, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().c.a(packet); + } + }; + + public MapWrapper_v1_20_R2(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R2.java b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R2.java new file mode 100644 index 0000000..736343d --- /dev/null +++ b/NMS-v1_20_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R2.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +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.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_20_R2 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.a(); + ItemStack item = packetPlayInSetCreativeSlot.d(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + ChannelPipeline pipeline = networkManager.n.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + Channel channel = networkManager.n; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.c, vec3dObj.d, vec3dObj.e); //x, y, z + } +} diff --git a/pom.xml b/pom.xml index 6c05711..c73d09d 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ API Dist + NMS-v1_20_R2 NMS-v1_20_R1 NMS-v1_19_R3 NMS-v1_18_R2 From 4bb68cc5d2c0e6aec5c09a73225a21a1f8d291fb Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Fri, 22 Sep 2023 14:37:01 +0200 Subject: [PATCH 21/52] Create dependabot.yml --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..d4cd657 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "maven" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" From 8a2daf15801a9203889b49a9894fc4e07466174a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Sep 2023 12:37:50 +0000 Subject: [PATCH 22/52] :arrow_up: Bump org.bstats:bstats-bukkit from 3.0.0 to 3.0.2 Bumps [org.bstats:bstats-bukkit](https://github.com/Bastian/bStats-Metrics) from 3.0.0 to 3.0.2. - [Release notes](https://github.com/Bastian/bStats-Metrics/releases) - [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.0...v3.0.2) --- updated-dependencies: - dependency-name: org.bstats:bstats-bukkit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f60f473..1df5be4 100644 --- a/pom.xml +++ b/pom.xml @@ -174,7 +174,7 @@ org.bstats bstats-bukkit - 3.0.0 + 3.0.2 compile From fa08e1b5ff684c45d10a3ccb9d2400413792e208 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Fri, 22 Sep 2023 16:37:43 +0200 Subject: [PATCH 23/52] Bumped to 1.20.2 --- pom.xml | 6 +++--- .../tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java | 2 +- .../mapreflectionapi/listeners/PacketListener.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 1df5be4..0ed60ae 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,7 @@ org.projectlombok lombok - 1.18.24 + 1.18.30 @@ -161,13 +161,13 @@ org.spigotmc spigot-api - 1.20.1-R0.1-SNAPSHOT + 1.20.2-R0.1-SNAPSHOT provided org.projectlombok lombok - 1.18.24 + 1.18.30 provided diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index 4dca45d..b964af9 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -166,7 +166,7 @@ public class MapWrapper extends AbstractMapWrapper { String inventoryMenuName; if (supports(20)) { //1.20 - inventoryMenuName = "bQ"; + inventoryMenuName = PATCH_NUMBER == 2 ? "bR" : "bQ"; //1.20.2 = bR, 1.20(.1) = bQ } else if (supports(19)) { //1.19 inventoryMenuName = PATCH_NUMBER == 3 ? "bO" : "bT"; //1.19.4 = bO, >= 1.19.3 = bT } else if (supports(18)) { //1.18 diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index c7d15d4..72748a1 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -145,7 +145,7 @@ public class PacketListener implements Listener { Object playerHandle = getHandle(player); Object playerConnection = getDeclaredField(playerHandle, supports(20) ? "c" : supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection Object networkManager = getDeclaredField(playerConnection, supports(19, 3) ? "h" : supports(19) ? "b" : supports(17) ? "a" : "networkManager"); //1.20 & 1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager - return (Channel) getDeclaredField(networkManager, supports(18) ? "m" : supports(17) ? "k" : "channel"); //1.19 & 1.18 = m, 1.17 = k, 1.16 = channel + return (Channel) getDeclaredField(networkManager, supports(20, 2) ? "n" : supports(18) ? "m" : supports(17) ? "k" : "channel"); //1.20.2 = n, 1.20(.1), 1.19 & 1.18 = m, 1.17 = k, 1.16 = channel } private Vector vec3DToVector(Object vec3d) { From a5e7a4afdc8af4a8b7e2d7ac6a87d6b753e2dcdf Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Mon, 2 Oct 2023 13:15:55 +0200 Subject: [PATCH 24/52] Updated deps --- pom.xml | 2 +- .../utils/ReflectionUtils.java | 122 ++++++++++++------ 2 files changed, 85 insertions(+), 39 deletions(-) diff --git a/pom.xml b/pom.xml index 0ed60ae..1c9a571 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ tech.sbdevelopment MapReflectionAPI - 1.6 + 1.6.1 jar MapReflectionAPI diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java index 5a02c40..1edf971 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java @@ -51,7 +51,7 @@ import java.util.regex.Pattern; * A useful resource used to compare mappings is Mini's Mapping Viewer * * @author Crypto Morin - * @version 7.0.0 + * @version 7.1.0 */ public final class ReflectionUtils { /** @@ -151,6 +151,7 @@ public final class ReflectionUtils { /** * Gets the full version information of the server. Useful for including in errors. + * * @since 7.0.0 */ public static String getVersionInformation() { @@ -171,7 +172,7 @@ public final class ReflectionUtils { public static Integer getLatestPatchNumberOf(int minorVersion) { if (minorVersion <= 0) throw new IllegalArgumentException("Minor version must be positive: " + minorVersion); - // https://minecraft.fandom.com/wiki/Java_Edition_version_history + // https://minecraft.wiki/w/Java_Edition_version_history // There are many ways to do this, but this is more visually appealing. int[] patches = { /* 1 */ 1, @@ -194,7 +195,7 @@ public final class ReflectionUtils { /* 17 */ 1,// \_!_/ /* 18 */ 2, /* 19 */ 4, - /* 20 */ 0, + /* 20 */ 2, }; if (minorVersion > patches.length) return null; @@ -237,6 +238,13 @@ public final class ReflectionUtils { Class craftPlayer = getCraftClass("entity.CraftPlayer"); Class craftWorld = getCraftClass("CraftWorld"); Class playerConnection = getNMSClass("server.network", "PlayerConnection"); + Class playerCommonConnection; + if (supports(20) && supportsPatch(2)) { + // The packet send method has been abstracted from ServerGamePacketListenerImpl to ServerCommonPacketListenerImpl in 1.20.2 + playerCommonConnection = getNMSClass("server.network", "ServerCommonPacketListenerImpl"); + } else { + playerCommonConnection = playerConnection; + } MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle sendPacket = null, getHandle = null, getHandleWorld = null, connection = null; @@ -247,7 +255,7 @@ public final class ReflectionUtils { getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer)); getHandleWorld = lookup.findVirtual(craftWorld, "getHandle", MethodType.methodType(worldServer)); sendPacket = lookup.findVirtual(playerConnection, - v(18, "a").orElse("sendPacket"), + v(20, 2, "b").v(18, "a").orElse("sendPacket"), MethodType.methodType(void.class, getNMSClass("network.protocol", "Packet"))); } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) { ex.printStackTrace(); @@ -263,15 +271,26 @@ public final class ReflectionUtils { } /** - * This method is purely for readability. - * No performance is gained. + * Gives the {@code handle} object if the server version is equal or greater than the given version. + * This method is purely for readability and should be always used with {@link VersionHandler#orElse(Object)}. * + * @see #v(int, int, Object) + * @see VersionHandler#orElse(Object) * @since 5.0.0 */ public static VersionHandler v(int version, T handle) { return new VersionHandler<>(version, handle); } + /** + * Overload for {@link #v(int, T)} that supports patch versions + * + * @since 9.5.0 + */ + public static VersionHandler v(int version, int patch, T handle) { + return new VersionHandler<>(version, patch, handle); + } + public static CallableVersionHandler v(int version, Callable handle) { return new CallableVersionHandler<>(version, handle); } @@ -288,6 +307,20 @@ public final class ReflectionUtils { return MINOR_NUMBER >= minorNumber; } + /** + * Checks whether the server version is equal or greater than the given version. + * + * @param minorNumber the minor version to compare the server version with. + * @param patchNumber the patch number to compare the server version with. + * @return true if the version is equal or newer, otherwise false. + * @see #MINOR_NUMBER + * @see #PATCH_NUMBER + * @since 7.1.0 + */ + public static boolean supports(int minorNumber, int patchNumber) { + return (MINOR_NUMBER == minorNumber && supportsPatch(patchNumber)) || MINOR_NUMBER > minorNumber; + } + /** * Checks whether the server version is equal or greater than the given version. * @@ -300,36 +333,28 @@ public final class ReflectionUtils { return PATCH_NUMBER >= patchNumber; } - /** - * Checks whether the server version is equal or greater than the given version. - * If minorNumber matches, it will check if patchNumber is equal or greater, - * if minorNumber does not match, it will check if minorNumber is greater. - * - * @param minorNumber the minor version to compare the server version with. - * @param patchNumber the patch version to compare the server version with. - * @see #MINOR_NUMBER - * @see #PATCH_NUMBER - */ - public static boolean supports(int minorNumber, int patchNumber) { - return (MINOR_NUMBER == minorNumber && supportsPatch(patchNumber)) || MINOR_NUMBER > minorNumber; - } - /** * Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility. * - * @param newPackage the 1.17 package name. - * @param name the name of the class. + * @param packageName the 1.17+ package name of this class. + * @param name the name of the class. * @return the NMS class or null if not found. * @since 4.0.0 */ @Nullable - public static Class getNMSClass(@Nonnull String newPackage, @Nonnull String name) { - if (supports(17)) name = newPackage + '.' + name; - return getNMSClass(name); + public static Class getNMSClass(@Nullable String packageName, @Nonnull String name) { + if (packageName != null && supports(17)) name = packageName + '.' + name; + + try { + return Class.forName(NMS_PACKAGE + name); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } } /** - * Get a NMS (net.minecraft.server) class. + * Get a NMS {@link #NMS_PACKAGE} class. * * @param name the name of the class. * @return the NMS class or null if not found. @@ -337,12 +362,7 @@ public final class ReflectionUtils { */ @Nullable public static Class getNMSClass(@Nonnull String name) { - try { - return Class.forName(NMS_PACKAGE + name); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - return null; - } + return getNMSClass(null, name); } /** @@ -437,6 +457,10 @@ public final class ReflectionUtils { } } + /** + * @deprecated Use {@link #toArrayClass(Class)} instead. + */ + @Deprecated public static Class getArrayClass(String clazz, boolean nms) { clazz = "[L" + (nms ? NMS_PACKAGE : CRAFTBUKKIT_PACKAGE) + clazz + ';'; try { @@ -447,6 +471,15 @@ public final class ReflectionUtils { } } + /** + * Gives an array version of a class. For example if you wanted {@code EntityPlayer[]} you'd use: + *
{@code
+     *     Class EntityPlayer = ReflectionUtils.getNMSClass("...", "EntityPlayer");
+     *     Class EntityPlayerArray = ReflectionUtils.toArrayClass(EntityPlayer);
+     * }
+ * + * @param clazz the class to get the array version of. You could use for multi-dimensions arrays too. + */ public static Class toArrayClass(Class clazz) { try { return Class.forName("[L" + clazz.getName() + ';'); @@ -457,26 +490,39 @@ public final class ReflectionUtils { } public static final class VersionHandler { - private int version; + private int version, patch; private T handle; private VersionHandler(int version, T handle) { - if (supports(version)) { + this(version, 0, handle); + } + + private VersionHandler(int version, int patch, T handle) { + if (supports(version) && supportsPatch(patch)) { this.version = version; + this.patch = patch; this.handle = handle; } } public VersionHandler v(int version, T handle) { - if (version == this.version) - throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version); - if (version > this.version && supports(version)) { + return v(version, 0, handle); + } + + public VersionHandler v(int version, int patch, T handle) { + if (version == this.version && patch == this.patch) + throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version + '.' + patch); + if (version > this.version && supports(version) && patch >= this.patch && supportsPatch(patch)) { this.version = version; + this.patch = patch; this.handle = handle; } return this; } + /** + * If none of the previous version checks matched, it'll return this object. + */ public T orElse(T handle) { return this.version == 0 ? handle : this.handle; } @@ -512,4 +558,4 @@ public final class ReflectionUtils { } } } -} \ No newline at end of file +} From e9039eb56d2c087e06356cb6e71cca77abbf40d1 Mon Sep 17 00:00:00 2001 From: StanByes Date: Mon, 23 Oct 2023 13:18:40 +0200 Subject: [PATCH 25/52] Patch reflection channel getter at PlayerJoinEvent --- .../listeners/PacketListener.java | 10 ++++++++-- .../utils/ReflectionUtil.java | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index 72748a1..22677cf 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -144,7 +144,13 @@ public class PacketListener implements Listener { private Channel getChannel(Player player) { Object playerHandle = getHandle(player); Object playerConnection = getDeclaredField(playerHandle, supports(20) ? "c" : supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection - Object networkManager = getDeclaredField(playerConnection, supports(19, 3) ? "h" : supports(19) ? "b" : supports(17) ? "a" : "networkManager"); //1.20 & 1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager + + Object networkManager; + if (supports(20, 2)) + networkManager = getSuperDeclaredField(playerConnection, "c"); //1.20.2 = c + else + networkManager = getDeclaredField(playerConnection, supports(19, 3) ? "h" : supports(19) ? "b" : supports(17) ? "a" : "networkManager"); //1.20 & 1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager + return (Channel) getDeclaredField(networkManager, supports(20, 2) ? "n" : supports(18) ? "m" : supports(17) ? "k" : "channel"); //1.20.2 = n, 1.20(.1), 1.19 & 1.18 = m, 1.17 = k, 1.16 = channel } @@ -158,4 +164,4 @@ public class PacketListener implements Listener { return new Vector(x, y, z); } -} +} \ No newline at end of file diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java index b998d74..4fb2aea 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java @@ -299,6 +299,26 @@ public class ReflectionUtil { } } + @Nullable + public static Object getSuperDeclaredField(Object object, String field) { + try { + String cacheKey = "SuperDeclaredField:" + object.getClass().getSuperclass().getName() + ":" + field; + + if (fieldCache.containsKey(cacheKey)) { + Field cachedField = fieldCache.get(cacheKey); + return cachedField.get(object); + } else { + Field f = object.getClass().getSuperclass().getDeclaredField(field); + f.setAccessible(true); + fieldCache.put(cacheKey, f); + return f.get(object); + } + } catch (NoSuchFieldException | IllegalAccessException ex) { + ex.printStackTrace(); + return null; + } + } + public static void setDeclaredField(Object object, String field, Object value) { try { String cacheKey = "DeclaredField:" + object.getClass().getName() + ":" + field; From eff2fb016fdfa8d17fcd375bdd58bc55e24a7530 Mon Sep 17 00:00:00 2001 From: StanByes Date: Mon, 23 Oct 2023 18:52:14 +0200 Subject: [PATCH 26/52] Patch reflection item getter at PacketPlayInSetCreativeSlot --- .../mapreflectionapi/listeners/PacketListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index 22677cf..f1a04be 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -117,7 +117,7 @@ public class PacketListener implements Listener { Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet); int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(19, 3) ? "a" : supports(13) ? "b" : "a"); //1.19.4 = a, 1.19.3 - 1.13 = b, 1.12 = a - Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(18) ? "c" : "getItemStack"); //1.18 = c, 1.17 = getItemStack + Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(20, 2) ? "d" : supports(18) ? "c" : "getItemStack"); //1.20.2 = d, >= 1.18 = c, 1.17 = getItemStack ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack); boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); From 2af12469be80c88e64a8ae9eb916462bbd94fc77 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Mon, 23 Oct 2023 19:05:25 +0200 Subject: [PATCH 27/52] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8bef8df..af968b9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MapReflectionAPI -This plugin helps developer with viewing images on maps. It supports Spigot 1.12 - 1.19. +This plugin helps developer with displaying images on maps. It supports Spigot 1.12 - 1.20. ## Usage: From 293c239fb855f819a4ad0e63a5ae68d3ab0a08c9 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Mon, 13 Nov 2023 21:30:32 +0100 Subject: [PATCH 28/52] v1.6.2: More version fixes (1.20.2 and 1.19.2) --- pom.xml | 2 +- .../mapreflectionapi/api/MapWrapper.java | 2 +- .../listeners/PacketListener.java | 22 +++++++++++-------- .../utils/ReflectionUtil.java | 6 ++--- .../utils/ReflectionUtils.java | 4 ++-- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index 1c9a571..2d2e3c3 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ tech.sbdevelopment MapReflectionAPI - 1.6.1 + 1.6.2 jar MapReflectionAPI diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index b964af9..1c063b7 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -344,7 +344,7 @@ public class MapWrapper extends AbstractMapWrapper { ReflectionUtil.ListParam list = new ReflectionUtil.ListParam<>(); Object packet; - if (supports(19, 2)) { //1.19.3 + if (supports(19, 3)) { //1.19.3 Class dataWatcherRecordClass = getNMSClass("network.syncher", "DataWatcher$b"); // Sadly not possible to use ReflectionUtil (in its current state), because of the Object parameter Object dataWatcherItem; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index f1a04be..616d620 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -47,6 +47,16 @@ public class PacketListener implements Listener { private static final Class packetPlayInSetCreativeSlotClass = getNMSClass("network.protocol.game", "PacketPlayInSetCreativeSlot"); private static final Class vec3DClass = getNMSClass("world.phys", "Vec3D"); private static final Class craftStackClass = getCraftClass("inventory.CraftItemStack"); + private static final Class playerCommonConnection; + + static { + if (supports(20) && supportsPatch(2)) { + // The packet send method has been abstracted from ServerGamePacketListenerImpl to ServerCommonPacketListenerImpl in 1.20.2 + playerCommonConnection = getNMSClass("server.network", "ServerCommonPacketListenerImpl"); + } else { + playerCommonConnection = getNMSClass("server.network", "PlayerConnection"); + } + } @EventHandler public void onJoin(PlayerJoinEvent e) { @@ -116,7 +126,7 @@ public class PacketListener implements Listener { } else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) { Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet); - int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(19, 3) ? "a" : supports(13) ? "b" : "a"); //1.19.4 = a, 1.19.3 - 1.13 = b, 1.12 = a + int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(19, 4) ? "a" : supports(13) ? "b" : "a"); //1.19.4 = a, 1.19.3 - 1.13 = b, 1.12 = a Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(20, 2) ? "d" : supports(18) ? "c" : "getItemStack"); //1.20.2 = d, >= 1.18 = c, 1.17 = getItemStack ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack); @@ -144,13 +154,7 @@ public class PacketListener implements Listener { private Channel getChannel(Player player) { Object playerHandle = getHandle(player); Object playerConnection = getDeclaredField(playerHandle, supports(20) ? "c" : supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection - - Object networkManager; - if (supports(20, 2)) - networkManager = getSuperDeclaredField(playerConnection, "c"); //1.20.2 = c - else - networkManager = getDeclaredField(playerConnection, supports(19, 3) ? "h" : supports(19) ? "b" : supports(17) ? "a" : "networkManager"); //1.20 & 1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager - + Object networkManager = getDeclaredField(playerCommonConnection, playerConnection, supports(20, 2) ? "c" : supports(19, 4) ? "h" : supports(19) ? "b" : supports(17) ? "a" : "networkManager"); //1.20.2 = ServerCommonPacketListenerImpl#c, 1.20(.1) & 1.19.4 = h, >= 1.19.3 = b, 1.18 - 1.17 = a, 1.16 = networkManager return (Channel) getDeclaredField(networkManager, supports(20, 2) ? "n" : supports(18) ? "m" : supports(17) ? "k" : "channel"); //1.20.2 = n, 1.20(.1), 1.19 & 1.18 = m, 1.17 = k, 1.16 = channel } @@ -164,4 +168,4 @@ public class PacketListener implements Listener { return new Vector(x, y, z); } -} \ No newline at end of file +} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java index 4fb2aea..a1cbe13 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java @@ -300,15 +300,15 @@ public class ReflectionUtil { } @Nullable - public static Object getSuperDeclaredField(Object object, String field) { + public static Object getDeclaredField(Class clazz, Object object, String field) { try { - String cacheKey = "SuperDeclaredField:" + object.getClass().getSuperclass().getName() + ":" + field; + String cacheKey = "DeclaredField:" + clazz.getName() + ":" + field; if (fieldCache.containsKey(cacheKey)) { Field cachedField = fieldCache.get(cacheKey); return cachedField.get(object); } else { - Field f = object.getClass().getSuperclass().getDeclaredField(field); + Field f = clazz.getDeclaredField(field); f.setAccessible(true); fieldCache.put(cacheKey, f); return f.get(object); diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java index 1edf971..804a628 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java @@ -107,7 +107,7 @@ public final class ReflectionUtils { public static final int MINOR_NUMBER; /** * The raw patch version number. - * E.g. {@code v1_17_R1} to {@code 1} + * E.g. {@code 1.19.2} to {@code 2} *

* I'd not recommend developers to support individual patches at all. You should always support the latest patch. * For example, between v1.14.0, v1.14.1, v1.14.2, v1.14.3 and v1.14.4 you should only support v1.14.4 @@ -254,7 +254,7 @@ public final class ReflectionUtils { v(20, "c").v(17, "b").orElse("playerConnection"), playerConnection); getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer)); getHandleWorld = lookup.findVirtual(craftWorld, "getHandle", MethodType.methodType(worldServer)); - sendPacket = lookup.findVirtual(playerConnection, + sendPacket = lookup.findVirtual(playerCommonConnection, v(20, 2, "b").v(18, "a").orElse("sendPacket"), MethodType.methodType(void.class, getNMSClass("network.protocol", "Packet"))); } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) { From 8206cb403b1f478ea3ecef0da4c8b6bea61be831 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Sat, 9 Dec 2023 20:19:49 +0100 Subject: [PATCH 29/52] Added render distance info to README --- .idea/misc.xml | 2 ++ README.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/.idea/misc.xml b/.idea/misc.xml index 4a803e8..863f295 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,3 +1,4 @@ + @@ -28,6 +29,7 @@ + diff --git a/README.md b/README.md index af968b9..98f254a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ This plugin helps developer with displaying images on maps. It supports Spigot 1 ## Usage: +### Using the API: + First, include the API using Maven: ```xml @@ -80,6 +82,84 @@ controller.showInFrames(p, frames, true); More information can be found on the [JavaDoc](https://sbdevelopment.tech/javadoc/mapreflectionapi/). +### Notes on map render distance: + +MapReflectionAPI does not implement render distance to images shown on maps. This should be implemented by yourself. An example of this is below. + +```java +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerRespawnEvent; + +public class MapRenderDistanceListener implements Listener { + private static final int MAP_RENDER_DISTANCE_SQUARED = 1024; + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent e) { + Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> { + //Show the maps to the player which are within distance + }); + } + + @EventHandler + public void onPlayerLeave(PlayerQuitEvent e) { + Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> { + //Hide the maps to the player which are within distance + }); + } + + @EventHandler + public void onPlayerChangeWorld(PlayerChangedWorldEvent e) { + //Hide all the maps in the e.getFrom() world + + Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> { + //Show the maps to the player which are within distance in e.getPlayer().getWorld() + }, 20); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerDeath(PlayerDeathEvent e) { + //Hide all the maps in the e.getEntity().getWorld() + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerRespawn(PlayerRespawnEvent e) { + Bukkit.getScheduler().runTaskLaterAsynchronously(MyPluginInstance, () -> { + //Show the maps to the player which are within distance in e.getPlayer().getWorld() + }, 20); + } + + @EventHandler + public void onPlayerMove(PlayerMoveEvent e) { + //Hide all the maps in the e.getFrom() world + //Show the maps to the player which are within distance in e.getTo().getWorld() + + //FOR EXAMPLE: + if (e.getTo() == null) return; + if (e.getFrom().getChunk().equals(e.getTo().getChunk())) return; + + for (Frame frame : API.getFramesInWorld(e.getPlayer().getWorld())) { + double distanceSquared = e.getTo().distanceSquared(frame.getLocation()); + + if (distanceSquared > MAP_RENDER_DISTANCE_SQUARED) { + API.hideFrame(e.getPlayer(), frame); + } else { + API.showFrame(e.getPlayer(), frame); + } + } + } + + @EventHandler + public void onPlayerTeleport(PlayerTeleportEvent e) { + //Hide all the maps in the e.getFrom() world + //Show the maps to the player which are within distance in e.getTo().getWorld() + + //SEE EXAMPLE ABOVE + } +} +``` + ## Credits: This is a fork of [MapManager](https://github.com/InventivetalentDev/MapManager). It updates the API to 1.19 and uses From 89cbc9b8be43b5c8ae68622e2cf587e6e0305c21 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Fri, 22 Dec 2023 20:37:28 +0100 Subject: [PATCH 30/52] Added Spigot 1.20.4 support --- .github/dependabot.yml | 2 +- .idea/misc.xml | 2 +- pom.xml | 4 +- .../mapreflectionapi/api/MapWrapper.java | 58 +++++++++---------- .../utils/ReflectionUtils.java | 15 +++-- 5 files changed, 42 insertions(+), 39 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d4cd657..4cae9d2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,4 +8,4 @@ updates: - package-ecosystem: "maven" # See documentation for possible values directory: "/" # Location of package manifests schedule: - interval: "weekly" + interval: "daily" \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 863f295..f0021fd 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -31,7 +31,7 @@ - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2d2e3c3..8d7f65d 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ tech.sbdevelopment MapReflectionAPI - 1.6.2 + 1.6.3 jar MapReflectionAPI @@ -161,7 +161,7 @@ org.spigotmc spigot-api - 1.20.2-R0.1-SNAPSHOT + 1.20.4-R0.1-SNAPSHOT provided diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index 1c063b7..eac90da 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -18,6 +18,7 @@ package tech.sbdevelopment.mapreflectionapi.api; +import lombok.Getter; import org.bukkit.*; import org.bukkit.entity.ItemFrame; import org.bukkit.entity.Player; @@ -33,7 +34,6 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.sql.Ref; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -43,6 +43,7 @@ import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; /** * A {@link MapWrapper} wraps one image. */ +@Getter public class MapWrapper extends AbstractMapWrapper { private static final String REFERENCE_METADATA = "MAP_WRAPPER_REF"; protected ArrayImage content; @@ -108,7 +109,8 @@ public class MapWrapper extends AbstractMapWrapper { @Override public void update(@NotNull ArrayImage content) { - MapContentUpdateEvent event = new MapContentUpdateEvent(MapWrapper.this, content); + boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); + MapContentUpdateEvent event = new MapContentUpdateEvent(MapWrapper.this, content, async); Bukkit.getPluginManager().callEvent(event); if (Configuration.getInstance().isImageCache()) { @@ -165,27 +167,25 @@ public class MapWrapper extends AbstractMapWrapper { } String inventoryMenuName; - if (supports(20)) { //1.20 - inventoryMenuName = PATCH_NUMBER == 2 ? "bR" : "bQ"; //1.20.2 = bR, 1.20(.1) = bQ - } else if (supports(19)) { //1.19 - inventoryMenuName = PATCH_NUMBER == 3 ? "bO" : "bT"; //1.19.4 = bO, >= 1.19.3 = bT - } else if (supports(18)) { //1.18 - inventoryMenuName = PATCH_NUMBER == 1 ? "bV" : "bU"; //1.18.1 = ap, 1.18(.2) = ao - } else if (supports(17)) { //1.17, same as 1.18(.2) + if (supports(20)) { + //>= 1.20.2 = bR, 1.20(.1) = bQ + inventoryMenuName = supports(20, 2) ? "bR" : "bQ"; + } else if (supports(19)) { + //1.19.4 = bO, >= 1.19.3 = bT + inventoryMenuName = supports(19, 3) ? "bO" : "bT"; + } else if (supports(18)) { + //1.18.1 = ap, 1.18(.2) = ao + inventoryMenuName = supports(18, 1) ? "bV" : "bU"; + } else if (supports(17)) { + //1.17, same as 1.18(.2) inventoryMenuName = "bU"; - } else { //1.12-1.16 + } else { + //1.12-1.16 inventoryMenuName = "defaultContainer"; } Object inventoryMenu = ReflectionUtil.getField(getHandle(player), inventoryMenuName); - ItemStack stack; - if (supports(13)) { - stack = new ItemStack(MAP_MATERIAL, 1); - } else { - stack = new ItemStack(MAP_MATERIAL, 1, (short) getMapId(player)); - } - - Object nmsStack = createCraftItemStack(stack, (short) getMapId(player)); + Object nmsStack = asCraftItemStack(player); Object packet; if (supports(17)) { //1.17+ @@ -298,6 +298,13 @@ public class MapWrapper extends AbstractMapWrapper { return null; } + private Object asCraftItemStack(Player player) { + return createCraftItemStack(supports(13) + ? new ItemStack(MAP_MATERIAL, 1) + : new ItemStack(MAP_MATERIAL, 1, (short) getMapId(player) + ), (short) getMapId(player)); + } + private Object createCraftItemStack(@NotNull ItemStack stack, int mapId) { if (mapId < 0) return null; @@ -310,7 +317,7 @@ public class MapWrapper extends AbstractMapWrapper { } else if (supports(19)) { //1.19 nbtObjectName = "v"; } else if (supports(18)) { //1.18 - nbtObjectName = PATCH_NUMBER == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u + nbtObjectName = supports(18, 1) ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u } else { //1.13 - 1.17 nbtObjectName = "getOrCreateTag"; } @@ -329,7 +336,7 @@ public class MapWrapper extends AbstractMapWrapper { } else if (supports(19)) { //1.19-1.19.2 dataWatcherObjectName = "ao"; } else if (supports(18)) { //1.18 - dataWatcherObjectName = PATCH_NUMBER == 1 ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao + dataWatcherObjectName = supports(18, 1) ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao } else if (supports(17)) { //1.17 dataWatcherObjectName = "ao"; } else if (supports(14)) { //1.14 - 1.16 @@ -341,7 +348,7 @@ public class MapWrapper extends AbstractMapWrapper { } Object dataWatcherObject = ReflectionUtil.getDeclaredField(entityItemFrameClass, dataWatcherObjectName); - ReflectionUtil.ListParam list = new ReflectionUtil.ListParam<>(); + ReflectionUtil.ListParam list = new ReflectionUtil.ListParam<>(); Object packet; if (supports(19, 3)) { //1.19.3 @@ -379,13 +386,4 @@ public class MapWrapper extends AbstractMapWrapper { sendPacketSync(player, packet); } }; - - public ArrayImage getContent() { - return content; - } - - @Override - public MapController getController() { - return controller; - } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java index 804a628..912a2c4 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java @@ -51,7 +51,7 @@ import java.util.regex.Pattern; * A useful resource used to compare mappings is Mini's Mapping Viewer * * @author Crypto Morin - * @version 7.1.0 + * @version 7.1.0.0.1 */ public final class ReflectionUtils { /** @@ -106,8 +106,13 @@ public final class ReflectionUtils { */ public static final int MINOR_NUMBER; /** - * The raw patch version number. - * E.g. {@code 1.19.2} to {@code 2} + * The raw patch version number. Refers to the major.minor.patch version scheme. + * E.g. + *
    + *
  • {@code v1.20.4} to {@code 4}
  • + *
  • {@code v1.18.2} to {@code 2}
  • + *
  • {@code v1.19.1} to {@code 1}
  • + *
*

* I'd not recommend developers to support individual patches at all. You should always support the latest patch. * For example, between v1.14.0, v1.14.1, v1.14.2, v1.14.3 and v1.14.4 you should only support v1.14.4 @@ -195,7 +200,7 @@ public final class ReflectionUtils { /* 17 */ 1,// \_!_/ /* 18 */ 2, /* 19 */ 4, - /* 20 */ 2, + /* 20 */ 4, }; if (minorVersion > patches.length) return null; @@ -558,4 +563,4 @@ public final class ReflectionUtils { } } } -} +} \ No newline at end of file From 4f187b92bb422395ab3c63dbf4450986f3be8a7a Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Fri, 22 Dec 2023 20:38:54 +0100 Subject: [PATCH 31/52] LEGACY: Added v1_20_R3 --- .idea/encodings.xml | 2 + NMS-v1_20_R3/pom.xml | 72 ++++++ .../nms/MapSender_v1_20_R3.java | 138 ++++++++++ .../nms/MapWrapper_v1_20_R3.java | 239 ++++++++++++++++++ .../nms/PacketListener_v1_20_R3.java | 126 +++++++++ pom.xml | 1 + 6 files changed, 578 insertions(+) create mode 100644 NMS-v1_20_R3/pom.xml create mode 100644 NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R3.java create mode 100644 NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R3.java create mode 100644 NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R3.java diff --git a/.idea/encodings.xml b/.idea/encodings.xml index eb43565..e45f72b 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -27,6 +27,8 @@ + + diff --git a/NMS-v1_20_R3/pom.xml b/NMS-v1_20_R3/pom.xml new file mode 100644 index 0000000..3b5b147 --- /dev/null +++ b/NMS-v1_20_R3/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_20_R3 + + + 1.20.4-R0.1-SNAPSHOT + 17 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R3.java b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R3.java new file mode 100644 index 0000000..bb9de64 --- /dev/null +++ b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R3.java @@ -0,0 +1,138 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_20_R3} sends the Map packets to players. + */ +public class MapSender_v1_20_R3 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_20_R3() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().c.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R3.java b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R3.java new file mode 100644 index 0000000..698a53e --- /dev/null +++ b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R3.java @@ -0,0 +1,239 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_20_R3 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_20_R3.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_20_R3.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_20_R3.sendMap(id, MapWrapper_v1_20_R3.this.content, player); + } else { + MapSender_v1_20_R3.addToQueue(id, MapWrapper_v1_20_R3.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_20_R3.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().bR.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().bR.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().c.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_20_R3.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.w().a("map", mapId); //getOrCreateTag putInt + + List> list = new ArrayList<>(); + DataWatcher.b dataWatcherItem = DataWatcher.b.a(EntityItemFrame.g, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().c.a(packet); + } + }; + + public MapWrapper_v1_20_R3(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R3.java b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R3.java new file mode 100644 index 0000000..d66b550 --- /dev/null +++ b/NMS-v1_20_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R3.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_20_R3 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.a(); + ItemStack item = packetPlayInSetCreativeSlot.d(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + ChannelPipeline pipeline = networkManager.n.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + Channel channel = networkManager.n; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.c, vec3dObj.d, vec3dObj.e); //x, y, z + } +} diff --git a/pom.xml b/pom.xml index c73d09d..1f55a66 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ API Dist + NMS-v1_20_R3 NMS-v1_20_R2 NMS-v1_20_R1 NMS-v1_19_R3 From 5557a76976a3ff3c2a0310d6346c10f3c5a0f66e Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Fri, 22 Dec 2023 20:45:36 +0100 Subject: [PATCH 32/52] Fixed typo in event name --- ...pdateEvent.java => CreativeInventoryMapUpdateEvent.java} | 6 +++--- .../mapreflectionapi/listeners/PacketListener.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/{CreateInventoryMapUpdateEvent.java => CreativeInventoryMapUpdateEvent.java} (91%) diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java similarity index 91% rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java rename to src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java index 6629266..ffea71f 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java @@ -37,7 +37,7 @@ import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; */ @RequiredArgsConstructor @Getter -public class CreateInventoryMapUpdateEvent extends Event implements Cancellable { +public class CreativeInventoryMapUpdateEvent extends Event implements Cancellable { private static final HandlerList handlerList = new HandlerList(); @Setter private boolean cancelled; @@ -48,14 +48,14 @@ public class CreateInventoryMapUpdateEvent extends Event implements Cancellable private MapWrapper mapWrapper; /** - * Construct a new {@link CreateInventoryMapUpdateEvent} + * Construct a new {@link CreativeInventoryMapUpdateEvent} * * @param player The player whose inventory is updated * @param slot The new slot * @param item The item in the new slot * @param isAsync Is this event called async? */ - public CreateInventoryMapUpdateEvent(Player player, int slot, ItemStack item, boolean isAsync) { + public CreativeInventoryMapUpdateEvent(Player player, int slot, ItemStack item, boolean isAsync) { super(isAsync); this.player = player; this.slot = slot; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index 616d620..fd2f737 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -31,7 +31,7 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; -import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.CreativeInventoryMapUpdateEvent; import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; @@ -131,7 +131,7 @@ public class PacketListener implements Listener { ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack); boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); - CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(player, slot, craftStack, async); + CreativeInventoryMapUpdateEvent event = new CreativeInventoryMapUpdateEvent(player, slot, craftStack, async); if (event.getMapWrapper() != null) { Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) return; From b5df2bb80ce2c1ca77283a7ce395f12aebc0a2b4 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Sun, 24 Dec 2023 15:38:40 +0100 Subject: [PATCH 33/52] Fixed multiple events, still WIP for Interact event --- .../mapreflectionapi/api/MapController.java | 9 - .../mapreflectionapi/api/MapManager.java | 29 + .../mapreflectionapi/api/MapWrapper.java | 42 +- .../CreativeInventoryMapUpdateEvent.java | 25 +- .../api/events/MapCancelEvent.java | 19 +- .../api/events/MapContentUpdateEvent.java | 13 +- .../api/events/MapInteractEvent.java | 28 +- .../api/events/types/CancellableEvent.java | 54 + .../api/events/types/Event.java | 46 + .../listeners/PacketListener.java | 34 +- .../mapreflectionapi/utils/UpdateManager.java | 2 - .../mapreflectionapi/utils/XMaterial.java | 2406 +++++++++++++++++ 12 files changed, 2587 insertions(+), 120 deletions(-) create mode 100644 src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/CancellableEvent.java create mode 100644 src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/Event.java create mode 100644 src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java index 11a71cd..8618aa4 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java @@ -117,13 +117,4 @@ public interface MapController extends IMapController { * @param frame {@link ItemFrame} to clear */ void clearFrame(Player player, ItemFrame frame); - - /** - * Get an {@link ItemFrame} by its entity ID - * - * @param world The world the {@link ItemFrame} is in - * @param entityId Entity-ID of the {@link ItemFrame} - * @return The found {@link ItemFrame}, or null - */ - ItemFrame getItemFrameById(World world, int entityId); } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java index 7120429..60fbc15 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java @@ -19,10 +19,13 @@ package tech.sbdevelopment.mapreflectionapi.api; import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.entity.ItemFrame; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; import tech.sbdevelopment.mapreflectionapi.managers.Configuration; +import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import java.awt.image.BufferedImage; import java.util.HashSet; @@ -30,6 +33,8 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; + /** * The {@link MapManager} manages all the maps. It also contains functions for wrapping. */ @@ -172,6 +177,30 @@ public class MapManager { return null; } + /** + * Get an {@link ItemFrame} by its entity ID + * + * @param world The world the {@link ItemFrame} is in + * @param entityId Entity-ID of the {@link ItemFrame} + * @return The found {@link ItemFrame}, or null + */ + public ItemFrame getItemFrameById(World world, int entityId) { + Object worldHandle = getHandle(world); + Object nmsEntity = ReflectionUtil.callMethod(worldHandle, supports(18) ? "a" : "getEntity", entityId); + if (nmsEntity == null) return null; + + Object craftEntity = ReflectionUtil.callMethod(nmsEntity, "getBukkitEntity"); + if (craftEntity == null) return null; + + Class itemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame"); + if (itemFrameClass == null) return null; + + if (craftEntity.getClass().isAssignableFrom(itemFrameClass)) + return (ItemFrame) itemFrameClass.cast(craftEntity); + + return null; + } + /** * Register an occupied map ID * diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index eac90da..a7eae14 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -31,6 +31,7 @@ import tech.sbdevelopment.mapreflectionapi.api.events.MapContentUpdateEvent; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; import tech.sbdevelopment.mapreflectionapi.managers.Configuration; import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; +import tech.sbdevelopment.mapreflectionapi.utils.XMaterial; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -45,15 +46,9 @@ import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; */ @Getter public class MapWrapper extends AbstractMapWrapper { - private static final String REFERENCE_METADATA = "MAP_WRAPPER_REF"; + public static final String REFERENCE_METADATA = "MAP_WRAPPER_REF"; protected ArrayImage content; - private static final Material MAP_MATERIAL; - - static { - MAP_MATERIAL = supports(13) ? Material.FILLED_MAP : Material.MAP; - } - /** * Construct a new {@link MapWrapper} * @@ -215,7 +210,7 @@ public class MapWrapper extends AbstractMapWrapper { @Override public void showInHand(Player player, boolean force) { - if (player.getInventory().getItemInMainHand().getType() != MAP_MATERIAL && !force) + if (player.getInventory().getItemInMainHand().getType() != XMaterial.FILLED_MAP.parseMaterial() && !force) return; showInInventory(player, player.getInventory().getHeldItemSlot(), force); @@ -233,7 +228,7 @@ public class MapWrapper extends AbstractMapWrapper { @Override public void showInFrame(Player player, ItemFrame frame, boolean force) { - if (frame.getItem().getType() != MAP_MATERIAL && !force) + if (frame.getItem().getType() != XMaterial.FILLED_MAP.parseMaterial() && !force) return; showInFrame(player, frame.getEntityId()); @@ -248,7 +243,7 @@ public class MapWrapper extends AbstractMapWrapper { public void showInFrame(Player player, int entityId, String debugInfo) { if (!isViewing(player)) return; - ItemStack stack = new ItemStack(MAP_MATERIAL, 1); + ItemStack stack = new ItemStack(XMaterial.FILLED_MAP.parseMaterial(), 1); if (debugInfo != null) { ItemMeta itemMeta = stack.getItemMeta(); itemMeta.setDisplayName(debugInfo); @@ -256,7 +251,7 @@ public class MapWrapper extends AbstractMapWrapper { } Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { - ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + ItemFrame frame = MapReflectionAPI.getMapManager().getItemFrameById(player.getWorld(), entityId); if (frame != null) { frame.removeMetadata(REFERENCE_METADATA, MapReflectionAPI.getInstance()); frame.setMetadata(REFERENCE_METADATA, new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper.this)); @@ -270,7 +265,7 @@ public class MapWrapper extends AbstractMapWrapper { public void clearFrame(Player player, int entityId) { sendItemFramePacket(player, entityId, null, -1); Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { - ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + ItemFrame frame = MapReflectionAPI.getMapManager().getItemFrameById(player.getWorld(), entityId); if (frame != null) frame.removeMetadata(REFERENCE_METADATA, MapReflectionAPI.getInstance()); }); } @@ -280,29 +275,8 @@ public class MapWrapper extends AbstractMapWrapper { clearFrame(player, frame.getEntityId()); } - @Override - public ItemFrame getItemFrameById(World world, int entityId) { - Object worldHandle = getHandle(world); - Object nmsEntity = ReflectionUtil.callMethod(worldHandle, supports(18) ? "a" : "getEntity", entityId); - if (nmsEntity == null) return null; - - Object craftEntity = ReflectionUtil.callMethod(nmsEntity, "getBukkitEntity"); - if (craftEntity == null) return null; - - Class itemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame"); - if (itemFrameClass == null) return null; - - if (craftEntity.getClass().isAssignableFrom(itemFrameClass)) - return (ItemFrame) itemFrameClass.cast(craftEntity); - - return null; - } - private Object asCraftItemStack(Player player) { - return createCraftItemStack(supports(13) - ? new ItemStack(MAP_MATERIAL, 1) - : new ItemStack(MAP_MATERIAL, 1, (short) getMapId(player) - ), (short) getMapId(player)); + return createCraftItemStack(new ItemStack(XMaterial.FILLED_MAP.parseMaterial(), 1, (short) getMapId(player)), (short) getMapId(player)); } private Object createCraftItemStack(@NotNull ItemStack stack, int mapId) { diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java index ffea71f..561ae96 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,28 +20,24 @@ package tech.sbdevelopment.mapreflectionapi.api.events; import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.Setter; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; +import org.bukkit.map.MapView; import org.jetbrains.annotations.Nullable; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.events.types.CancellableEvent; +import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils; +import tech.sbdevelopment.mapreflectionapi.utils.XMaterial; /** * This event gets fired when a map in the creative inventory gets updated */ @RequiredArgsConstructor @Getter -public class CreativeInventoryMapUpdateEvent extends Event implements Cancellable { - private static final HandlerList handlerList = new HandlerList(); - @Setter - private boolean cancelled; - +public class CreativeInventoryMapUpdateEvent extends CancellableEvent { private final Player player; private final int slot; private final ItemStack item; @@ -62,11 +58,6 @@ public class CreativeInventoryMapUpdateEvent extends Event implements Cancellabl this.item = item; } - @Override - public @NotNull HandlerList getHandlers() { - return handlerList; - } - /** * Get the {@link MapWrapper} of the map of this event * @@ -76,7 +67,7 @@ public class CreativeInventoryMapUpdateEvent extends Event implements Cancellabl public MapWrapper getMapWrapper() { if (mapWrapper == null) { if (item == null) return null; - if (item.getType() != Material.MAP) return null; + if (!XMaterial.FILLED_MAP.isSimilar(item)) return null; mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, item.getDurability()); } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java index 4cc282f..5fe3ac1 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,23 +20,15 @@ package tech.sbdevelopment.mapreflectionapi.api.events; import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.Setter; import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.jetbrains.annotations.NotNull; +import tech.sbdevelopment.mapreflectionapi.api.events.types.CancellableEvent; /** * This event gets fired when a map creation is cancelled */ @RequiredArgsConstructor @Getter -public class MapCancelEvent extends Event implements Cancellable { - private static final HandlerList handlerList = new HandlerList(); - @Setter - private boolean cancelled; - +public class MapCancelEvent extends CancellableEvent { private final Player player; private final int id; @@ -52,9 +44,4 @@ public class MapCancelEvent extends Event implements Cancellable { this.player = player; this.id = id; } - - @Override - public @NotNull HandlerList getHandlers() { - return handlerList; - } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java index d106f06..ce3f59d 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,11 +21,9 @@ package tech.sbdevelopment.mapreflectionapi.api.events; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.jetbrains.annotations.NotNull; import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.events.types.Event; /** * This event gets fired when the content of a {@link MapWrapper} is updated @@ -33,8 +31,6 @@ import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; @RequiredArgsConstructor @Getter public class MapContentUpdateEvent extends Event { - private static final HandlerList handlerList = new HandlerList(); - private final MapWrapper wrapper; private final ArrayImage content; @Setter @@ -52,9 +48,4 @@ public class MapContentUpdateEvent extends Event { this.wrapper = wrapper; this.content = content; } - - @Override - public @NotNull HandlerList getHandlers() { - return handlerList; - } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java index 6e2dbb4..99fc4ab 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java @@ -1,6 +1,6 @@ /* * This file is part of MapReflectionAPI. - * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,28 +20,20 @@ package tech.sbdevelopment.mapreflectionapi.api.events; import lombok.Getter; import lombok.RequiredArgsConstructor; -import lombok.Setter; import org.bukkit.entity.ItemFrame; import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.events.types.CancellableEvent; /** * This event gets fired when a player interact with a map */ @RequiredArgsConstructor @Getter -public class MapInteractEvent extends Event implements Cancellable { - private static final HandlerList handlerList = new HandlerList(); - @Setter - private boolean cancelled; - +public class MapInteractEvent extends CancellableEvent { private final Player player; private final int entityID; private final int action; @@ -69,11 +61,6 @@ public class MapInteractEvent extends Event implements Cancellable { this.hand = hand; } - @Override - public @NotNull HandlerList getHandlers() { - return handlerList; - } - /** * Get the {@link ItemFrame} the map is in * @@ -81,10 +68,8 @@ public class MapInteractEvent extends Event implements Cancellable { */ @Nullable public ItemFrame getFrame() { - if (getMapWrapper() == null) return null; - if (frame == null) { - frame = getMapWrapper().getController().getItemFrameById(player.getWorld(), entityID); + frame = MapReflectionAPI.getMapManager().getItemFrameById(player.getWorld(), entityID); } return frame; } @@ -96,10 +81,11 @@ public class MapInteractEvent extends Event implements Cancellable { */ @Nullable public MapWrapper getMapWrapper() { + if (getFrame() == null) return null; if (mapWrapper == null) { - mapWrapper = MapReflectionAPI.getMapManager().getWrapperForId(player, entityID); + if (!frame.hasMetadata(MapWrapper.REFERENCE_METADATA)) return null; + mapWrapper = (MapWrapper) frame.getMetadata(MapWrapper.REFERENCE_METADATA).get(0).value(); } - return mapWrapper; } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/CancellableEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/CancellableEvent.java new file mode 100644 index 0000000..96987f7 --- /dev/null +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/CancellableEvent.java @@ -0,0 +1,54 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.api.events.types; + +import lombok.NoArgsConstructor; +import org.bukkit.event.Cancellable; + +@NoArgsConstructor +public class CancellableEvent extends Event implements Cancellable { + /** + * If this event gets cancelled. + */ + private boolean cancelled; + + public CancellableEvent(boolean isAsync) { + super(isAsync); + } + + /** + * Check if this event gets cancelled. + * + * @return true if cancelled, false if not + */ + @Override + public boolean isCancelled() { + return cancelled; + } + + /** + * Set if this event gets cancelled. + * + * @param cancelled true if you wish to cancel this event + */ + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } +} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/Event.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/Event.java new file mode 100644 index 0000000..fac8c14 --- /dev/null +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/types/Event.java @@ -0,0 +1,46 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.api.events.types; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.bukkit.event.HandlerList; + +@NoArgsConstructor +public class Event extends org.bukkit.event.Event { + public Event(boolean isAsync) { + super(isAsync); + } + + /** + * A list of EventHandlers listening to this event. + */ + @Getter + private static final HandlerList handlerList = new HandlerList(); + + /** + * Get the EventHandlers listening to this event. + * + * @return The EventHandlers listening to this event. + */ + @Override + public HandlerList getHandlers() { + return handlerList; + } +} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index fd2f737..b5c3f17 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -72,6 +72,8 @@ public class PacketListener implements Listener { ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { @Override public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + boolean cancel = false; + if (packet.getClass().isAssignableFrom(packetPlayOutMapClass)) { Object packetPlayOutMap = packetPlayOutMapClass.cast(packet); @@ -83,18 +85,19 @@ public class PacketListener implements Listener { boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); MapCancelEvent event = new MapCancelEvent(player, id, async); if (MapReflectionAPI.getMapManager().isIdUsedBy(player, id)) event.setCancelled(true); - if (event.getHandlers().getRegisteredListeners().length > 0) - Bukkit.getPluginManager().callEvent(event); + Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) return; + if (event.isCancelled()) cancel = true; } } - super.write(ctx, packet, promise); + if (!cancel) super.write(ctx, packet, promise); } @Override public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + boolean cancel = false; + if (packet.getClass().isAssignableFrom(packetPlayInUseEntityClass)) { Object packetPlayInEntity = packetPlayInUseEntityClass.cast(packet); @@ -105,9 +108,20 @@ public class PacketListener implements Listener { Object pos; if (supports(17)) { Object action = getDeclaredField(packetPlayInEntity, "b"); - actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type - hand = hasField(action, "a") ? (Enum) getDeclaredField(action, "a") : null; - pos = hasField(action, "b") ? getDeclaredField(action, "b") : null; + actionEnum = (Enum) callDeclaredMethod(action, "a"); + Class d = getNMSClass("network.protocol.game", "PacketPlayInUseEntity$d"); + Class e = getNMSClass("network.protocol.game", "PacketPlayInUseEntity$e"); + if (action.getClass().isAssignableFrom(e)) { + hand = (Enum) getDeclaredField(action, "a"); + pos = getDeclaredField(action, "b"); + } else { + pos = null; + if (action.getClass().isAssignableFrom(d)) { + hand = (Enum) getDeclaredField(action, "a"); + } else { + hand = null; + } + } } else { actionEnum = (Enum) callDeclaredMethod(packetPlayInEntity, supports(13) ? "b" : "a"); //1.13 = b, 1.12 = a hand = (Enum) callDeclaredMethod(packetPlayInEntity, supports(13) ? "c" : "b"); //1.13 = c, 1.12 = b @@ -122,7 +136,7 @@ public class PacketListener implements Listener { return event.isCancelled(); } return false; - }).get(1, TimeUnit.SECONDS)) return; + }).get(1, TimeUnit.SECONDS)) cancel = true; } else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) { Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet); @@ -134,11 +148,11 @@ public class PacketListener implements Listener { CreativeInventoryMapUpdateEvent event = new CreativeInventoryMapUpdateEvent(player, slot, craftStack, async); if (event.getMapWrapper() != null) { Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) return; + if (event.isCancelled()) cancel = true; } } - super.channelRead(ctx, packet); + if (!cancel) super.channelRead(ctx, packet); } }; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java index 7acc5f3..671a511 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java @@ -143,14 +143,12 @@ public class UpdateManager { File pluginFile = getPluginFile(); // /plugins/XXX.jar if (pluginFile == null) { this.downloadResponse.accept(DownloadResponse.ERROR, null); - Bukkit.getLogger().info("Pluginfile is null"); return; } File updateFolder = Bukkit.getUpdateFolderFile(); if (!updateFolder.exists()) { if (!updateFolder.mkdirs()) { this.downloadResponse.accept(DownloadResponse.ERROR, null); - Bukkit.getLogger().info("Updatefolder doesn't exists, and can't be made"); return; } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java new file mode 100644 index 0000000..95ecaa0 --- /dev/null +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java @@ -0,0 +1,2406 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Hex_27 + * Copyright (c) 2023 Crypto Morin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package tech.sbdevelopment.mapreflectionapi.utils; + +import com.google.common.base.Enums; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SpawnEggMeta; +import org.bukkit.potion.Potion; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; + +/** + * XMaterial - Data Values/Pre-flattening
+ * 1.13 and above as priority. + *

+ * This class is mainly designed to support {@link ItemStack}. If you want to use it on blocks, you'll have to use + * XBlock + *

+ * Pre-flattening: https://minecraft.wiki/w/Java_Edition_data_values/Pre-flattening + * Materials: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html + * Materials (1.12): https://helpch.at/docs/1.12.2/index.html?org/bukkit/Material.html + * Material IDs: https://minecraft-ids.grahamedgecombe.com/ + * Material Source Code: https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/Material.java + * XMaterial v1: https://www.spigotmc.org/threads/329630/ + *

+ * This class will throw an "unsupported material" error if someone tries to use an item with an invalid data value which can only happen in 1.12 servers and below or when the + * utility is missing a new material in that specific version. + * To get an invalid item, (aka Missing Texture Block) you can use the command + * /give @p minecraft:dirt 1 10 where 1 is the item amount, and 10 is the data value. The material {@link #DIRT} with a data value of {@code 10} doesn't exist. + * + * @author Crypto Morin + * @version 11.5.0 + * @see Material + * @see ItemStack + */ +public enum XMaterial { + ACACIA_BOAT("BOAT_ACACIA"), + ACACIA_BUTTON("WOOD_BUTTON"), + ACACIA_CHEST_BOAT, + ACACIA_DOOR("ACACIA_DOOR", "ACACIA_DOOR_ITEM"), + ACACIA_FENCE, + ACACIA_FENCE_GATE, + ACACIA_HANGING_SIGN, + ACACIA_LEAVES(0, "LEAVES_2"), + ACACIA_LOG(0, "LOG_2"), + ACACIA_PLANKS(4, "WOOD"), + ACACIA_PRESSURE_PLATE("WOOD_PLATE"), + ACACIA_SAPLING(4, "SAPLING"), + ACACIA_SIGN("SIGN_POST", "SIGN"), + ACACIA_SLAB(4, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + ACACIA_STAIRS, + ACACIA_TRAPDOOR("TRAP_DOOR"), + ACACIA_WALL_HANGING_SIGN, + ACACIA_WALL_SIGN("WALL_SIGN"), + ACACIA_WOOD(0, "LOG_2"), + ACTIVATOR_RAIL, + /** + * Air + * {@link Material#isAir()} + * + * @see #VOID_AIR + * @see #CAVE_AIR + */ + AIR, + ALLAY_SPAWN_EGG, + ALLIUM(2, "RED_ROSE"), + AMETHYST_BLOCK, + AMETHYST_CLUSTER, + AMETHYST_SHARD, + ANCIENT_DEBRIS, + ANDESITE(5, "STONE"), + ANDESITE_SLAB, + ANDESITE_STAIRS, + ANDESITE_WALL, + ANGLER_POTTERY_SHERD, + ANVIL, + APPLE, + ARCHER_POTTERY_SHERD, + ARMOR_STAND, + ARMS_UP_POTTERY_SHERD, + ARROW, + ATTACHED_MELON_STEM(7, "MELON_STEM"), + ATTACHED_PUMPKIN_STEM(7, "PUMPKIN_STEM"), + AXOLOTL_BUCKET, + AXOLOTL_SPAWN_EGG, + AZALEA, + AZALEA_LEAVES, + AZURE_BLUET(3, "RED_ROSE"), + BAKED_POTATO, + BAMBOO, + BAMBOO_BLOCK, + BAMBOO_BUTTON, + BAMBOO_CHEST_RAFT, + BAMBOO_DOOR, + BAMBOO_FENCE, + BAMBOO_FENCE_GATE, + BAMBOO_HANGING_SIGN, + BAMBOO_MOSAIC, + BAMBOO_MOSAIC_SLAB, + BAMBOO_MOSAIC_STAIRS, + BAMBOO_PLANKS, + BAMBOO_PRESSURE_PLATE, + BAMBOO_RAFT, + BAMBOO_SAPLING, + BAMBOO_SIGN, + BAMBOO_SLAB, + BAMBOO_STAIRS, + BAMBOO_TRAPDOOR, + BAMBOO_WALL_HANGING_SIGN, + BAMBOO_WALL_SIGN, + BARREL, + BARRIER, + BASALT, + BAT_SPAWN_EGG(65, "MONSTER_EGG"), + BEACON, + BEDROCK, + BEEF("RAW_BEEF"), + BEEHIVE, + /** + * Beetroot is a known material in pre-1.13 + */ + BEETROOT("BEETROOT_BLOCK"), + BEETROOTS("BEETROOT"), + BEETROOT_SEEDS, + BEETROOT_SOUP, + BEE_NEST, + BEE_SPAWN_EGG, + BELL, + BIG_DRIPLEAF, + BIG_DRIPLEAF_STEM, + BIRCH_BOAT("BOAT_BIRCH"), + BIRCH_BUTTON("WOOD_BUTTON"), + BIRCH_CHEST_BOAT, + BIRCH_DOOR("BIRCH_DOOR", "BIRCH_DOOR_ITEM"), + BIRCH_FENCE, + BIRCH_FENCE_GATE, + BIRCH_HANGING_SIGN, + BIRCH_LEAVES(2, "LEAVES"), + BIRCH_LOG(2, "LOG"), + BIRCH_PLANKS(2, "WOOD"), + BIRCH_PRESSURE_PLATE("WOOD_PLATE"), + BIRCH_SAPLING(2, "SAPLING"), + BIRCH_SIGN("SIGN_POST", "SIGN"), + BIRCH_SLAB(2, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + BIRCH_STAIRS("BIRCH_WOOD_STAIRS"), + BIRCH_TRAPDOOR("TRAP_DOOR"), + BIRCH_WALL_HANGING_SIGN, + BIRCH_WALL_SIGN("WALL_SIGN"), + BIRCH_WOOD(2, "LOG"), + BLACKSTONE, + BLACKSTONE_SLAB, + BLACKSTONE_STAIRS, + BLACKSTONE_WALL, + BLACK_BANNER("STANDING_BANNER", "BANNER"), + /** + * Version 1.12+ interprets "BED" as BLACK_BED due to enum alphabetic ordering. + */ + BLACK_BED(supports(12) ? 15 : 0, "BED_BLOCK", "BED"), + BLACK_CANDLE, + BLACK_CANDLE_CAKE, + BLACK_CARPET(15, "CARPET"), + BLACK_CONCRETE(15, "CONCRETE"), + BLACK_CONCRETE_POWDER(15, "CONCRETE_POWDER"), + BLACK_DYE, + BLACK_GLAZED_TERRACOTTA, + BLACK_SHULKER_BOX, + BLACK_STAINED_GLASS(15, "STAINED_GLASS"), + BLACK_STAINED_GLASS_PANE(15, "STAINED_GLASS_PANE"), + BLACK_TERRACOTTA(15, "STAINED_CLAY"), + BLACK_WALL_BANNER("WALL_BANNER"), + BLACK_WOOL(15, "WOOL"), + BLADE_POTTERY_SHERD, + BLAST_FURNACE, + BLAZE_POWDER, + BLAZE_ROD, + BLAZE_SPAWN_EGG(61, "MONSTER_EGG"), + BLUE_BANNER(4, "STANDING_BANNER", "BANNER"), + BLUE_BED(supports(12) ? 11 : 0, "BED_BLOCK", "BED"), + BLUE_CANDLE, + BLUE_CANDLE_CAKE, + BLUE_CARPET(11, "CARPET"), + BLUE_CONCRETE(11, "CONCRETE"), + BLUE_CONCRETE_POWDER(11, "CONCRETE_POWDER"), + BLUE_DYE(4, "INK_SACK", "LAPIS_LAZULI"), + BLUE_GLAZED_TERRACOTTA, + BLUE_ICE, + BLUE_ORCHID(1, "RED_ROSE"), + BLUE_SHULKER_BOX, + BLUE_STAINED_GLASS(11, "STAINED_GLASS"), + BLUE_STAINED_GLASS_PANE(11, "THIN_GLASS", "STAINED_GLASS_PANE"), + BLUE_TERRACOTTA(11, "STAINED_CLAY"), + BLUE_WALL_BANNER(4, "WALL_BANNER"), + BLUE_WOOL(11, "WOOL"), + BONE, + BONE_BLOCK, + BONE_MEAL(15, "INK_SACK"), + BOOK, + BOOKSHELF, + BOW, + BOWL, + BRAIN_CORAL, + BRAIN_CORAL_BLOCK, + BRAIN_CORAL_FAN, + BRAIN_CORAL_WALL_FAN, + BREAD, + BREEZE_SPAWN_EGG, + BREWER_POTTERY_SHERD, + BREWING_STAND("BREWING_STAND", "BREWING_STAND_ITEM"), + BRICK("CLAY_BRICK"), + BRICKS("BRICK"), + BRICK_SLAB(4, "STEP"), + BRICK_STAIRS, + BRICK_WALL, + BROWN_BANNER(3, "STANDING_BANNER", "BANNER"), + BROWN_BED(supports(12) ? 12 : 0, "BED_BLOCK", "BED"), + BROWN_CANDLE, + BROWN_CANDLE_CAKE, + BROWN_CARPET(12, "CARPET"), + BROWN_CONCRETE(12, "CONCRETE"), + BROWN_CONCRETE_POWDER(12, "CONCRETE_POWDER"), + BROWN_DYE(3, "INK_SACK", "DYE", "COCOA_BEANS"), + BROWN_GLAZED_TERRACOTTA, + BROWN_MUSHROOM, + BROWN_MUSHROOM_BLOCK("BROWN_MUSHROOM", "HUGE_MUSHROOM_1"), + BROWN_SHULKER_BOX, + BROWN_STAINED_GLASS(12, "STAINED_GLASS"), + BROWN_STAINED_GLASS_PANE(12, "THIN_GLASS", "STAINED_GLASS_PANE"), + BROWN_TERRACOTTA(12, "STAINED_CLAY"), + BROWN_WALL_BANNER(3, "WALL_BANNER"), + BROWN_WOOL(12, "WOOL"), + BRUSH, + BUBBLE_COLUMN, + BUBBLE_CORAL, + BUBBLE_CORAL_BLOCK, + BUBBLE_CORAL_FAN, + BUBBLE_CORAL_WALL_FAN, + BUCKET, + BUDDING_AMETHYST, + BUNDLE, + BURN_POTTERY_SHERD, + CACTUS, + CAKE("CAKE_BLOCK"), + CALCITE, + CALIBRATED_SCULK_SENSOR, + CAMEL_SPAWN_EGG, + CAMPFIRE, + CANDLE, + CANDLE_CAKE, + CARROT("CARROT_ITEM"), + CARROTS("CARROT"), + CARROT_ON_A_STICK("CARROT_STICK"), + CARTOGRAPHY_TABLE, + CARVED_PUMPKIN, + CAT_SPAWN_EGG, + CAULDRON("CAULDRON", "CAULDRON_ITEM"), + /** + * 1.13 tag is not added because it's the same thing as {@link #AIR} + * + * @see #VOID_AIR + */ + CAVE_AIR("AIR"), + CAVE_SPIDER_SPAWN_EGG(59, "MONSTER_EGG"), + CAVE_VINES, + CAVE_VINES_PLANT, + CHAIN, + CHAINMAIL_BOOTS, + CHAINMAIL_CHESTPLATE, + CHAINMAIL_HELMET, + CHAINMAIL_LEGGINGS, + CHAIN_COMMAND_BLOCK("COMMAND", "COMMAND_CHAIN"), + CHARCOAL(1, "COAL"), + CHERRY_BOAT, + CHERRY_BUTTON, + CHERRY_CHEST_BOAT, + CHERRY_DOOR, + CHERRY_FENCE, + CHERRY_FENCE_GATE, + CHERRY_HANGING_SIGN, + CHERRY_LEAVES, + CHERRY_LOG, + CHERRY_PLANKS, + CHERRY_PRESSURE_PLATE, + CHERRY_SAPLING, + CHERRY_SIGN, + CHERRY_SLAB, + CHERRY_STAIRS, + CHERRY_TRAPDOOR, + CHERRY_WALL_HANGING_SIGN, + CHERRY_WALL_SIGN, + CHERRY_WOOD, + CHEST("LOCKED_CHEST"), + CHEST_MINECART("STORAGE_MINECART"), + CHICKEN("RAW_CHICKEN"), + CHICKEN_SPAWN_EGG(93, "MONSTER_EGG"), + CHIPPED_ANVIL(1, "ANVIL"), + CHISELED_BOOKSHELF, + CHISELED_COPPER, + CHISELED_DEEPSLATE, + CHISELED_NETHER_BRICKS(1, "NETHER_BRICKS"), + CHISELED_POLISHED_BLACKSTONE("POLISHED_BLACKSTONE"), + CHISELED_QUARTZ_BLOCK(1, "QUARTZ_BLOCK"), + CHISELED_RED_SANDSTONE(1, "RED_SANDSTONE"), + CHISELED_SANDSTONE(1, "SANDSTONE"), + CHISELED_STONE_BRICKS(3, "SMOOTH_BRICK"), + CHISELED_TUFF, + CHISELED_TUFF_BRICKS, + CHORUS_FLOWER, + CHORUS_FRUIT, + CHORUS_PLANT, + CLAY, + CLAY_BALL, + CLOCK("WATCH"), + COAL, + COAL_BLOCK, + COAL_ORE, + COARSE_DIRT(1, "DIRT"), + COAST_ARMOR_TRIM_SMITHING_TEMPLATE, + COBBLED_DEEPSLATE, + COBBLED_DEEPSLATE_SLAB, + COBBLED_DEEPSLATE_STAIRS, + COBBLED_DEEPSLATE_WALL, + COBBLESTONE, + COBBLESTONE_SLAB(3, "STEP"), + COBBLESTONE_STAIRS, + COBBLESTONE_WALL("COBBLE_WALL"), + COBWEB("WEB"), + COCOA, + COCOA_BEANS(3, "INK_SACK"), + COD("RAW_FISH"), + COD_BUCKET, + COD_SPAWN_EGG, + COMMAND_BLOCK("COMMAND"), + COMMAND_BLOCK_MINECART("COMMAND_MINECART"), + /** + * Unlike redstone torch and redstone lamp... neither REDTONE_COMPARATOR_OFF nor REDSTONE_COMPARATOR_ON + * are items. REDSTONE_COMPARATOR is. + * + * @see #REDSTONE_TORCH + * @see #REDSTONE_LAMP + */ + COMPARATOR("REDSTONE_COMPARATOR_OFF", "REDSTONE_COMPARATOR_ON", "REDSTONE_COMPARATOR"), + COMPASS, + COMPOSTER, + CONDUIT, + COOKED_BEEF, + COOKED_CHICKEN, + COOKED_COD("COOKED_FISH"), + COOKED_MUTTON, + COOKED_PORKCHOP("GRILLED_PORK"), + COOKED_RABBIT, + COOKED_SALMON(1, "COOKED_FISH"), + COOKIE, + COPPER_BLOCK, + COPPER_BULB, + COPPER_DOOR, + COPPER_GRATE, + COPPER_INGOT, + COPPER_ORE, + COPPER_TRAPDOOR, + CORNFLOWER, + COW_SPAWN_EGG(92, "MONSTER_EGG"), + CRACKED_DEEPSLATE_BRICKS, + CRACKED_DEEPSLATE_TILES, + CRACKED_NETHER_BRICKS(2, "NETHER_BRICKS"), + CRACKED_POLISHED_BLACKSTONE_BRICKS("POLISHED_BLACKSTONE_BRICKS"), + CRACKED_STONE_BRICKS(2, "SMOOTH_BRICK"), + CRAFTER, + CRAFTING_TABLE("WORKBENCH"), + CREEPER_BANNER_PATTERN, + CREEPER_HEAD(4, "SKULL", "SKULL_ITEM"), + CREEPER_SPAWN_EGG(50, "MONSTER_EGG"), + CREEPER_WALL_HEAD(4, "SKULL", "SKULL_ITEM"), + CRIMSON_BUTTON, + CRIMSON_DOOR, + CRIMSON_FENCE, + CRIMSON_FENCE_GATE, + CRIMSON_FUNGUS, + CRIMSON_HANGING_SIGN, + CRIMSON_HYPHAE, + CRIMSON_NYLIUM, + CRIMSON_PLANKS, + CRIMSON_PRESSURE_PLATE, + CRIMSON_ROOTS, + CRIMSON_SIGN("SIGN_POST"), + CRIMSON_SLAB, + CRIMSON_STAIRS, + CRIMSON_STEM, + CRIMSON_TRAPDOOR, + CRIMSON_WALL_HANGING_SIGN, + CRIMSON_WALL_SIGN("WALL_SIGN"), + CROSSBOW, + CRYING_OBSIDIAN, + CUT_COPPER, + CUT_COPPER_SLAB, + CUT_COPPER_STAIRS, + CUT_RED_SANDSTONE, + CUT_RED_SANDSTONE_SLAB("STONE_SLAB2"), + CUT_SANDSTONE, + CUT_SANDSTONE_SLAB(1, "STEP"), + CYAN_BANNER(6, "STANDING_BANNER", "BANNER"), + CYAN_BED(supports(12) ? 9 : 0, "BED_BLOCK", "BED"), + CYAN_CANDLE, + CYAN_CANDLE_CAKE, + CYAN_CARPET(9, "CARPET"), + CYAN_CONCRETE(9, "CONCRETE"), + CYAN_CONCRETE_POWDER(9, "CONCRETE_POWDER"), + CYAN_DYE(6, "INK_SACK"), + CYAN_GLAZED_TERRACOTTA, + CYAN_SHULKER_BOX, + CYAN_STAINED_GLASS(9, "STAINED_GLASS"), + CYAN_STAINED_GLASS_PANE(9, "STAINED_GLASS_PANE"), + CYAN_TERRACOTTA(9, "STAINED_CLAY"), + CYAN_WALL_BANNER(6, "WALL_BANNER"), + CYAN_WOOL(9, "WOOL"), + DAMAGED_ANVIL(2, "ANVIL"), + DANDELION("YELLOW_FLOWER"), + DANGER_POTTERY_SHERD, + DARK_OAK_BOAT("BOAT_DARK_OAK"), + DARK_OAK_BUTTON("WOOD_BUTTON"), + DARK_OAK_CHEST_BOAT, + DARK_OAK_DOOR("DARK_OAK_DOOR", "DARK_OAK_DOOR_ITEM"), + DARK_OAK_FENCE, + DARK_OAK_FENCE_GATE, + DARK_OAK_HANGING_SIGN, + DARK_OAK_LEAVES(1, "LEAVES_2"), + DARK_OAK_LOG(1, "LOG_2"), + DARK_OAK_PLANKS(5, "WOOD"), + DARK_OAK_PRESSURE_PLATE("WOOD_PLATE"), + DARK_OAK_SAPLING(5, "SAPLING"), + DARK_OAK_SIGN("SIGN_POST", "SIGN"), + DARK_OAK_SLAB(5, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + DARK_OAK_STAIRS, + DARK_OAK_TRAPDOOR("TRAP_DOOR"), + DARK_OAK_WALL_HANGING_SIGN, + DARK_OAK_WALL_SIGN("WALL_SIGN"), + DARK_OAK_WOOD(1, "LOG_2"), + DARK_PRISMARINE(2, "PRISMARINE"), + DARK_PRISMARINE_SLAB, + DARK_PRISMARINE_STAIRS, + DAYLIGHT_DETECTOR("DAYLIGHT_DETECTOR_INVERTED"), + DEAD_BRAIN_CORAL, + DEAD_BRAIN_CORAL_BLOCK, + DEAD_BRAIN_CORAL_FAN, + DEAD_BRAIN_CORAL_WALL_FAN, + DEAD_BUBBLE_CORAL, + DEAD_BUBBLE_CORAL_BLOCK, + DEAD_BUBBLE_CORAL_FAN, + DEAD_BUBBLE_CORAL_WALL_FAN, + DEAD_BUSH("LONG_GRASS"), + DEAD_FIRE_CORAL, + DEAD_FIRE_CORAL_BLOCK, + DEAD_FIRE_CORAL_FAN, + DEAD_FIRE_CORAL_WALL_FAN, + DEAD_HORN_CORAL, + DEAD_HORN_CORAL_BLOCK, + DEAD_HORN_CORAL_FAN, + DEAD_HORN_CORAL_WALL_FAN, + DEAD_TUBE_CORAL, + DEAD_TUBE_CORAL_BLOCK, + DEAD_TUBE_CORAL_FAN, + DEAD_TUBE_CORAL_WALL_FAN, + DEBUG_STICK, + DECORATED_POT, + DEEPSLATE, + DEEPSLATE_BRICKS, + DEEPSLATE_BRICK_SLAB, + DEEPSLATE_BRICK_STAIRS, + DEEPSLATE_BRICK_WALL, + DEEPSLATE_COAL_ORE, + DEEPSLATE_COPPER_ORE, + DEEPSLATE_DIAMOND_ORE, + DEEPSLATE_EMERALD_ORE, + DEEPSLATE_GOLD_ORE, + DEEPSLATE_IRON_ORE, + DEEPSLATE_LAPIS_ORE, + DEEPSLATE_REDSTONE_ORE, + DEEPSLATE_TILES, + DEEPSLATE_TILE_SLAB, + DEEPSLATE_TILE_STAIRS, + DEEPSLATE_TILE_WALL, + DETECTOR_RAIL, + DIAMOND, + DIAMOND_AXE, + DIAMOND_BLOCK, + DIAMOND_BOOTS, + DIAMOND_CHESTPLATE, + DIAMOND_HELMET, + DIAMOND_HOE, + DIAMOND_HORSE_ARMOR("DIAMOND_BARDING"), + DIAMOND_LEGGINGS, + DIAMOND_ORE, + DIAMOND_PICKAXE, + DIAMOND_SHOVEL("DIAMOND_SPADE"), + DIAMOND_SWORD, + DIORITE(3, "STONE"), + DIORITE_SLAB, + DIORITE_STAIRS, + DIORITE_WALL, + DIRT, + /** + * Changed in 1.17 + */ + DIRT_PATH("GRASS_PATH"), + DISC_FRAGMENT_5, + DISPENSER, + DOLPHIN_SPAWN_EGG, + DONKEY_SPAWN_EGG(32, "MONSTER_EGG"), + DRAGON_BREATH("DRAGONS_BREATH"), + DRAGON_EGG, + DRAGON_HEAD(5, "SKULL", "SKULL_ITEM"), + DRAGON_WALL_HEAD(5, "SKULL", "SKULL_ITEM"), + DRIED_KELP, + DRIED_KELP_BLOCK, + DRIPSTONE_BLOCK, + DROPPER, + DROWNED_SPAWN_EGG, + DUNE_ARMOR_TRIM_SMITHING_TEMPLATE, + ECHO_SHARD, + EGG, + ELDER_GUARDIAN_SPAWN_EGG(4, "MONSTER_EGG"), + ELYTRA, + EMERALD, + EMERALD_BLOCK, + EMERALD_ORE, + ENCHANTED_BOOK, + ENCHANTED_GOLDEN_APPLE(1, "GOLDEN_APPLE"), + ENCHANTING_TABLE("ENCHANTMENT_TABLE"), + ENDERMAN_SPAWN_EGG(58, "MONSTER_EGG"), + ENDERMITE_SPAWN_EGG(67, "MONSTER_EGG"), + ENDER_CHEST, + ENDER_DRAGON_SPAWN_EGG, + ENDER_EYE("EYE_OF_ENDER"), + ENDER_PEARL, + END_CRYSTAL, + END_GATEWAY, + END_PORTAL("ENDER_PORTAL"), + END_PORTAL_FRAME("ENDER_PORTAL_FRAME"), + END_ROD, + END_STONE("ENDER_STONE"), + END_STONE_BRICKS("END_BRICKS"), + END_STONE_BRICK_SLAB, + END_STONE_BRICK_STAIRS, + END_STONE_BRICK_WALL, + EVOKER_SPAWN_EGG(34, "MONSTER_EGG"), + EXPERIENCE_BOTTLE("EXP_BOTTLE"), + EXPLORER_POTTERY_SHERD, + EXPOSED_CHISELED_COPPER, + EXPOSED_COPPER, + EXPOSED_COPPER_BULB, + EXPOSED_COPPER_DOOR, + EXPOSED_COPPER_GRATE, + EXPOSED_COPPER_TRAPDOOR, + EXPOSED_CUT_COPPER, + EXPOSED_CUT_COPPER_SLAB, + EXPOSED_CUT_COPPER_STAIRS, + EYE_ARMOR_TRIM_SMITHING_TEMPLATE, + FARMLAND("SOIL"), + FEATHER, + FERMENTED_SPIDER_EYE, + FERN(2, "LONG_GRASS"), + /** + * For some reasons filled map items are really special. + * Their data value starts from 0 and every time a player + * creates a new map that maps data value increases. + * GitHub Issue + */ + FILLED_MAP("MAP"), + FIRE, + FIREWORK_ROCKET("FIREWORK"), + FIREWORK_STAR("FIREWORK_CHARGE"), + FIRE_CHARGE("FIREBALL"), + FIRE_CORAL, + FIRE_CORAL_BLOCK, + FIRE_CORAL_FAN, + FIRE_CORAL_WALL_FAN, + FISHING_ROD, + FLETCHING_TABLE, + FLINT, + FLINT_AND_STEEL, + FLOWERING_AZALEA, + FLOWERING_AZALEA_LEAVES, + FLOWER_BANNER_PATTERN, + FLOWER_POT("FLOWER_POT", "FLOWER_POT_ITEM"), + FOX_SPAWN_EGG, + FRIEND_POTTERY_SHERD, + FROGSPAWN, + FROG_SPAWN_EGG, + /** + * This special material cannot be obtained as an item. + */ + FROSTED_ICE, + FURNACE("BURNING_FURNACE"), + FURNACE_MINECART("POWERED_MINECART"), + GHAST_SPAWN_EGG(56, "MONSTER_EGG"), + GHAST_TEAR, + GILDED_BLACKSTONE, + GLASS, + GLASS_BOTTLE, + GLASS_PANE("THIN_GLASS"), + GLISTERING_MELON_SLICE("SPECKLED_MELON"), + GLOBE_BANNER_PATTERN, + GLOWSTONE, + GLOWSTONE_DUST, + GLOW_BERRIES, + GLOW_INK_SAC, + GLOW_ITEM_FRAME, + GLOW_LICHEN, + GLOW_SQUID_SPAWN_EGG, + GOAT_HORN, + GOAT_SPAWN_EGG, + GOLDEN_APPLE, + GOLDEN_AXE("GOLD_AXE"), + GOLDEN_BOOTS("GOLD_BOOTS"), + GOLDEN_CARROT, + GOLDEN_CHESTPLATE("GOLD_CHESTPLATE"), + GOLDEN_HELMET("GOLD_HELMET"), + GOLDEN_HOE("GOLD_HOE"), + GOLDEN_HORSE_ARMOR("GOLD_BARDING"), + GOLDEN_LEGGINGS("GOLD_LEGGINGS"), + GOLDEN_PICKAXE("GOLD_PICKAXE"), + GOLDEN_SHOVEL("GOLD_SPADE"), + GOLDEN_SWORD("GOLD_SWORD"), + GOLD_BLOCK, + GOLD_INGOT, + GOLD_NUGGET, + GOLD_ORE, + GRANITE(1, "STONE"), + GRANITE_SLAB, + GRANITE_STAIRS, + GRANITE_WALL, + GRASS_BLOCK("GRASS"), + GRAVEL, + GRAY_BANNER(8, "STANDING_BANNER", "BANNER"), + GRAY_BED(supports(12) ? 7 : 0, "BED_BLOCK", "BED"), + GRAY_CANDLE, + GRAY_CANDLE_CAKE, + GRAY_CARPET(7, "CARPET"), + GRAY_CONCRETE(7, "CONCRETE"), + GRAY_CONCRETE_POWDER(7, "CONCRETE_POWDER"), + GRAY_DYE(8, "INK_SACK"), + GRAY_GLAZED_TERRACOTTA, + GRAY_SHULKER_BOX, + GRAY_STAINED_GLASS(7, "STAINED_GLASS"), + GRAY_STAINED_GLASS_PANE(7, "THIN_GLASS", "STAINED_GLASS_PANE"), + GRAY_TERRACOTTA(7, "STAINED_CLAY"), + GRAY_WALL_BANNER(8, "WALL_BANNER"), + GRAY_WOOL(7, "WOOL"), + GREEN_BANNER(2, "STANDING_BANNER", "BANNER"), + GREEN_BED(supports(12) ? 13 : 0, "BED_BLOCK", "BED"), + GREEN_CANDLE, + GREEN_CANDLE_CAKE, + GREEN_CARPET(13, "CARPET"), + GREEN_CONCRETE(13, "CONCRETE"), + GREEN_CONCRETE_POWDER(13, "CONCRETE_POWDER"), + /** + * 1.13 renamed to CACTUS_GREEN + * 1.14 renamed to GREEN_DYE + */ + GREEN_DYE(2, "INK_SACK", "CACTUS_GREEN"), + GREEN_GLAZED_TERRACOTTA, + GREEN_SHULKER_BOX, + GREEN_STAINED_GLASS(13, "STAINED_GLASS"), + GREEN_STAINED_GLASS_PANE(13, "THIN_GLASS", "STAINED_GLASS_PANE"), + GREEN_TERRACOTTA(13, "STAINED_CLAY"), + GREEN_WALL_BANNER(2, "WALL_BANNER"), + GREEN_WOOL(13, "WOOL"), + GRINDSTONE, + GUARDIAN_SPAWN_EGG(68, "MONSTER_EGG"), + GUNPOWDER("SULPHUR"), + HANGING_ROOTS, + HAY_BLOCK, + HEARTBREAK_POTTERY_SHERD, + HEART_OF_THE_SEA, + HEART_POTTERY_SHERD, + HEAVY_WEIGHTED_PRESSURE_PLATE("IRON_PLATE"), + HOGLIN_SPAWN_EGG("MONSTER_EGG"), + HONEYCOMB, + HONEYCOMB_BLOCK, + HONEY_BLOCK, + HONEY_BOTTLE, + HOPPER, + HOPPER_MINECART, + HORN_CORAL, + HORN_CORAL_BLOCK, + HORN_CORAL_FAN, + HORN_CORAL_WALL_FAN, + HORSE_SPAWN_EGG(100, "MONSTER_EGG"), + HOST_ARMOR_TRIM_SMITHING_TEMPLATE, + HOWL_POTTERY_SHERD, + HUSK_SPAWN_EGG(23, "MONSTER_EGG"), + ICE, + INFESTED_CHISELED_STONE_BRICKS(5, "MONSTER_EGGS"), + INFESTED_COBBLESTONE(1, "MONSTER_EGGS"), + INFESTED_CRACKED_STONE_BRICKS(4, "MONSTER_EGGS"), + INFESTED_DEEPSLATE, + INFESTED_MOSSY_STONE_BRICKS(3, "MONSTER_EGGS"), + INFESTED_STONE("MONSTER_EGGS"), + INFESTED_STONE_BRICKS(2, "MONSTER_EGGS"), + /** + * We will only add "INK_SAC" for {@link #BLACK_DYE} since it's + * the only material (linked with this material) that is added + * after 1.13, which means it can use both INK_SACK and INK_SAC. + */ + INK_SAC("INK_SACK"), + IRON_AXE, + IRON_BARS("IRON_FENCE"), + IRON_BLOCK, + IRON_BOOTS, + IRON_CHESTPLATE, + IRON_DOOR("IRON_DOOR_BLOCK"), + IRON_GOLEM_SPAWN_EGG, + IRON_HELMET, + IRON_HOE, + IRON_HORSE_ARMOR("IRON_BARDING"), + IRON_INGOT, + IRON_LEGGINGS, + IRON_NUGGET, + IRON_ORE, + IRON_PICKAXE, + IRON_SHOVEL("IRON_SPADE"), + IRON_SWORD, + IRON_TRAPDOOR, + ITEM_FRAME, + JACK_O_LANTERN, + JIGSAW, + JUKEBOX, + JUNGLE_BOAT("BOAT_JUNGLE"), + JUNGLE_BUTTON("WOOD_BUTTON"), + JUNGLE_CHEST_BOAT, + JUNGLE_DOOR("JUNGLE_DOOR", "JUNGLE_DOOR_ITEM"), + JUNGLE_FENCE, + JUNGLE_FENCE_GATE, + JUNGLE_HANGING_SIGN, + JUNGLE_LEAVES(3, "LEAVES"), + JUNGLE_LOG(3, "LOG"), + JUNGLE_PLANKS(3, "WOOD"), + JUNGLE_PRESSURE_PLATE("WOOD_PLATE"), + JUNGLE_SAPLING(3, "SAPLING"), + JUNGLE_SIGN("SIGN_POST", "SIGN"), + JUNGLE_SLAB(3, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + JUNGLE_STAIRS("JUNGLE_WOOD_STAIRS"), + JUNGLE_TRAPDOOR("TRAP_DOOR"), + JUNGLE_WALL_HANGING_SIGN, + JUNGLE_WALL_SIGN("WALL_SIGN"), + JUNGLE_WOOD(3, "LOG"), + KELP, + KELP_PLANT, + KNOWLEDGE_BOOK("BOOK"), + LADDER, + LANTERN, + LAPIS_BLOCK, + LAPIS_LAZULI(4, "INK_SACK"), + LAPIS_ORE, + LARGE_AMETHYST_BUD, + LARGE_FERN(3, "DOUBLE_PLANT"), + LAVA("STATIONARY_LAVA"), + LAVA_BUCKET, + LAVA_CAULDRON, + LEAD("LEASH"), + LEATHER, + LEATHER_BOOTS, + LEATHER_CHESTPLATE, + LEATHER_HELMET, + LEATHER_HORSE_ARMOR("IRON_HORSE_ARMOR"), + LEATHER_LEGGINGS, + LECTERN, + LEVER, + LIGHT, + LIGHTNING_ROD, + LIGHT_BLUE_BANNER(12, "STANDING_BANNER", "BANNER"), + LIGHT_BLUE_BED(supports(12) ? 3 : 0, "BED_BLOCK", "BED"), + LIGHT_BLUE_CANDLE, + LIGHT_BLUE_CANDLE_CAKE, + LIGHT_BLUE_CARPET(3, "CARPET"), + LIGHT_BLUE_CONCRETE(3, "CONCRETE"), + LIGHT_BLUE_CONCRETE_POWDER(3, "CONCRETE_POWDER"), + LIGHT_BLUE_DYE(12, "INK_SACK"), + LIGHT_BLUE_GLAZED_TERRACOTTA, + LIGHT_BLUE_SHULKER_BOX, + LIGHT_BLUE_STAINED_GLASS(3, "STAINED_GLASS"), + LIGHT_BLUE_STAINED_GLASS_PANE(3, "THIN_GLASS", "STAINED_GLASS_PANE"), + LIGHT_BLUE_TERRACOTTA(3, "STAINED_CLAY"), + LIGHT_BLUE_WALL_BANNER(12, "WALL_BANNER", "STANDING_BANNER", "BANNER"), + LIGHT_BLUE_WOOL(3, "WOOL"), + LIGHT_GRAY_BANNER(7, "STANDING_BANNER", "BANNER"), + LIGHT_GRAY_BED(supports(12) ? 8 : 0, "BED_BLOCK", "BED"), + LIGHT_GRAY_CANDLE, + LIGHT_GRAY_CANDLE_CAKE, + LIGHT_GRAY_CARPET(8, "CARPET"), + LIGHT_GRAY_CONCRETE(8, "CONCRETE"), + LIGHT_GRAY_CONCRETE_POWDER(8, "CONCRETE_POWDER"), + LIGHT_GRAY_DYE(7, "INK_SACK"), + /** + * Renamed to SILVER_GLAZED_TERRACOTTA in 1.12 + * Renamed to LIGHT_GRAY_GLAZED_TERRACOTTA in 1.14 + */ + LIGHT_GRAY_GLAZED_TERRACOTTA("SILVER_GLAZED_TERRACOTTA"), + LIGHT_GRAY_SHULKER_BOX("SILVER_SHULKER_BOX"), + LIGHT_GRAY_STAINED_GLASS(8, "STAINED_GLASS"), + LIGHT_GRAY_STAINED_GLASS_PANE(8, "THIN_GLASS", "STAINED_GLASS_PANE"), + LIGHT_GRAY_TERRACOTTA(8, "STAINED_CLAY"), + LIGHT_GRAY_WALL_BANNER(7, "WALL_BANNER"), + LIGHT_GRAY_WOOL(8, "WOOL"), + LIGHT_WEIGHTED_PRESSURE_PLATE("GOLD_PLATE"), + LILAC(1, "DOUBLE_PLANT"), + LILY_OF_THE_VALLEY, + LILY_PAD("WATER_LILY"), + LIME_BANNER(10, "STANDING_BANNER", "BANNER"), + LIME_BED(supports(12) ? 5 : 0, "BED_BLOCK", "BED"), + LIME_CANDLE, + LIME_CANDLE_CAKE, + LIME_CARPET(5, "CARPET"), + LIME_CONCRETE(5, "CONCRETE"), + LIME_CONCRETE_POWDER(5, "CONCRETE_POWDER"), + LIME_DYE(10, "INK_SACK"), + LIME_GLAZED_TERRACOTTA, + LIME_SHULKER_BOX, + LIME_STAINED_GLASS(5, "STAINED_GLASS"), + LIME_STAINED_GLASS_PANE(5, "STAINED_GLASS_PANE"), + LIME_TERRACOTTA(5, "STAINED_CLAY"), + LIME_WALL_BANNER(10, "WALL_BANNER"), + LIME_WOOL(5, "WOOL"), + LINGERING_POTION, + LLAMA_SPAWN_EGG(103, "MONSTER_EGG"), + LODESTONE, + LOOM, + MAGENTA_BANNER(13, "STANDING_BANNER", "BANNER"), + MAGENTA_BED(supports(12) ? 2 : 0, "BED_BLOCK", "BED"), + MAGENTA_CANDLE, + MAGENTA_CANDLE_CAKE, + MAGENTA_CARPET(2, "CARPET"), + MAGENTA_CONCRETE(2, "CONCRETE"), + MAGENTA_CONCRETE_POWDER(2, "CONCRETE_POWDER"), + MAGENTA_DYE(13, "INK_SACK"), + MAGENTA_GLAZED_TERRACOTTA, + MAGENTA_SHULKER_BOX, + MAGENTA_STAINED_GLASS(2, "STAINED_GLASS"), + MAGENTA_STAINED_GLASS_PANE(2, "THIN_GLASS", "STAINED_GLASS_PANE"), + MAGENTA_TERRACOTTA(2, "STAINED_CLAY"), + MAGENTA_WALL_BANNER(13, "WALL_BANNER"), + MAGENTA_WOOL(2, "WOOL"), + MAGMA_BLOCK("MAGMA"), + MAGMA_CREAM, + MAGMA_CUBE_SPAWN_EGG(62, "MONSTER_EGG"), + MANGROVE_BOAT, + MANGROVE_BUTTON, + MANGROVE_CHEST_BOAT, + MANGROVE_DOOR, + MANGROVE_FENCE, + MANGROVE_FENCE_GATE, + MANGROVE_HANGING_SIGN, + MANGROVE_LEAVES, + MANGROVE_LOG, + MANGROVE_PLANKS, + MANGROVE_PRESSURE_PLATE, + MANGROVE_PROPAGULE, + MANGROVE_ROOTS, + MANGROVE_SIGN, + MANGROVE_SLAB, + MANGROVE_STAIRS, + MANGROVE_TRAPDOOR, + MANGROVE_WALL_HANGING_SIGN, + MANGROVE_WALL_SIGN, + MANGROVE_WOOD, + /** + * Adding this to the duplicated list will give you a filled map + * for 1.13+ versions and removing it from duplicated list will + * still give you a filled map in -1.12 versions. + * Since higher versions are our priority I'll keep 1.13+ support + * until I can come up with something to fix it. + */ + MAP("EMPTY_MAP"), + MEDIUM_AMETHYST_BUD, + MELON("MELON_BLOCK"), + MELON_SEEDS, + MELON_SLICE("MELON"), + MELON_STEM, + MILK_BUCKET, + MINECART, + MINER_POTTERY_SHERD, + MOJANG_BANNER_PATTERN, + MOOSHROOM_SPAWN_EGG(96, "MONSTER_EGG"), + MOSSY_COBBLESTONE, + MOSSY_COBBLESTONE_SLAB(), + MOSSY_COBBLESTONE_STAIRS, + MOSSY_COBBLESTONE_WALL(1, "COBBLE_WALL", "COBBLESTONE_WALL"), + MOSSY_STONE_BRICKS(1, "SMOOTH_BRICK"), + MOSSY_STONE_BRICK_SLAB, + MOSSY_STONE_BRICK_STAIRS, + MOSSY_STONE_BRICK_WALL, + MOSS_BLOCK, + MOSS_CARPET, + MOURNER_POTTERY_SHERD, + MOVING_PISTON("PISTON_MOVING_PIECE"), + MUD, + MUDDY_MANGROVE_ROOTS, + MUD_BRICKS, + MUD_BRICK_SLAB, + MUD_BRICK_STAIRS, + MUD_BRICK_WALL, + MULE_SPAWN_EGG(32, "MONSTER_EGG"), + MUSHROOM_STEM("BROWN_MUSHROOM"), + MUSHROOM_STEW("MUSHROOM_SOUP"), + MUSIC_DISC_11("RECORD_11"), + MUSIC_DISC_13("GOLD_RECORD"), + MUSIC_DISC_5, + MUSIC_DISC_BLOCKS("RECORD_3"), + MUSIC_DISC_CAT("GREEN_RECORD"), + MUSIC_DISC_CHIRP("RECORD_4"), + MUSIC_DISC_FAR("RECORD_5"), + MUSIC_DISC_MALL("RECORD_6"), + MUSIC_DISC_MELLOHI("RECORD_7"), + MUSIC_DISC_OTHERSIDE, + MUSIC_DISC_PIGSTEP, + MUSIC_DISC_RELIC, + MUSIC_DISC_STAL("RECORD_8"), + MUSIC_DISC_STRAD("RECORD_9"), + MUSIC_DISC_WAIT("RECORD_12"), + MUSIC_DISC_WARD("RECORD_10"), + MUTTON, + MYCELIUM("MYCEL"), + NAME_TAG, + NAUTILUS_SHELL, + NETHERITE_AXE, + NETHERITE_BLOCK, + NETHERITE_BOOTS, + NETHERITE_CHESTPLATE, + NETHERITE_HELMET, + NETHERITE_HOE, + NETHERITE_INGOT, + NETHERITE_LEGGINGS, + NETHERITE_PICKAXE, + NETHERITE_SCRAP, + NETHERITE_SHOVEL, + NETHERITE_SWORD, + NETHERITE_UPGRADE_SMITHING_TEMPLATE, + NETHERRACK, + NETHER_BRICK("NETHER_BRICK_ITEM"), + NETHER_BRICKS("NETHER_BRICK"), + NETHER_BRICK_FENCE("NETHER_FENCE"), + NETHER_BRICK_SLAB(6, "STEP"), + NETHER_BRICK_STAIRS, + NETHER_BRICK_WALL, + NETHER_GOLD_ORE, + NETHER_PORTAL("PORTAL"), + NETHER_QUARTZ_ORE("QUARTZ_ORE"), + NETHER_SPROUTS, + NETHER_STAR, + /** + * Just like mentioned in Nether Wart + * Nether wart is also known as nether stalk in the code. + * NETHER_STALK is the planted state of nether warts. + */ + NETHER_WART("NETHER_WARTS", "NETHER_STALK"), + NETHER_WART_BLOCK, + NOTE_BLOCK, + OAK_BOAT("BOAT"), + OAK_BUTTON("WOOD_BUTTON"), + OAK_CHEST_BOAT, + OAK_DOOR("WOODEN_DOOR", "WOOD_DOOR"), + OAK_FENCE("FENCE"), + OAK_FENCE_GATE("FENCE_GATE"), + OAK_HANGING_SIGN, + OAK_LEAVES("LEAVES"), + OAK_LOG("LOG"), + OAK_PLANKS("WOOD"), + OAK_PRESSURE_PLATE("WOOD_PLATE"), + OAK_SAPLING("SAPLING"), + OAK_SIGN("SIGN_POST", "SIGN"), + OAK_SLAB("WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + OAK_STAIRS("WOOD_STAIRS"), + OAK_TRAPDOOR("TRAP_DOOR"), + OAK_WALL_HANGING_SIGN, + OAK_WALL_SIGN("WALL_SIGN"), + OAK_WOOD("LOG"), + OBSERVER, + OBSIDIAN, + OCELOT_SPAWN_EGG(98, "MONSTER_EGG"), + OCHRE_FROGLIGHT, + ORANGE_BANNER(14, "STANDING_BANNER", "BANNER"), + ORANGE_BED(supports(12) ? 1 : 0, "BED_BLOCK", "BED"), + ORANGE_CANDLE, + ORANGE_CANDLE_CAKE, + ORANGE_CARPET(1, "CARPET"), + ORANGE_CONCRETE(1, "CONCRETE"), + ORANGE_CONCRETE_POWDER(1, "CONCRETE_POWDER"), + ORANGE_DYE(14, "INK_SACK"), + ORANGE_GLAZED_TERRACOTTA, + ORANGE_SHULKER_BOX, + ORANGE_STAINED_GLASS(1, "STAINED_GLASS"), + ORANGE_STAINED_GLASS_PANE(1, "STAINED_GLASS_PANE"), + ORANGE_TERRACOTTA(1, "STAINED_CLAY"), + ORANGE_TULIP(5, "RED_ROSE"), + ORANGE_WALL_BANNER(14, "WALL_BANNER"), + ORANGE_WOOL(1, "WOOL"), + OXEYE_DAISY(8, "RED_ROSE"), + OXIDIZED_CHISELED_COPPER, + OXIDIZED_COPPER, + OXIDIZED_COPPER_BULB, + OXIDIZED_COPPER_DOOR, + OXIDIZED_COPPER_GRATE, + OXIDIZED_COPPER_TRAPDOOR, + OXIDIZED_CUT_COPPER, + OXIDIZED_CUT_COPPER_SLAB, + OXIDIZED_CUT_COPPER_STAIRS, + PACKED_ICE, + PACKED_MUD, + PAINTING, + PANDA_SPAWN_EGG, + PAPER, + PARROT_SPAWN_EGG(105, "MONSTER_EGG"), + PEARLESCENT_FROGLIGHT, + PEONY(5, "DOUBLE_PLANT"), + PETRIFIED_OAK_SLAB("WOOD_STEP"), + PHANTOM_MEMBRANE, + PHANTOM_SPAWN_EGG, + PIGLIN_BANNER_PATTERN, + PIGLIN_BRUTE_SPAWN_EGG, + PIGLIN_HEAD, + PIGLIN_SPAWN_EGG(57, "MONSTER_EGG"), + PIGLIN_WALL_HEAD, + PIG_SPAWN_EGG(90, "MONSTER_EGG"), + PILLAGER_SPAWN_EGG, + PINK_BANNER(9, "STANDING_BANNER", "BANNER"), + PINK_BED(supports(12) ? 6 : 0, "BED_BLOCK", "BED"), + PINK_CANDLE, + PINK_CANDLE_CAKE, + PINK_CARPET(6, "CARPET"), + PINK_CONCRETE(6, "CONCRETE"), + PINK_CONCRETE_POWDER(6, "CONCRETE_POWDER"), + PINK_DYE(9, "INK_SACK"), + PINK_GLAZED_TERRACOTTA, + PINK_PETALS, + PINK_SHULKER_BOX, + PINK_STAINED_GLASS(6, "STAINED_GLASS"), + PINK_STAINED_GLASS_PANE(6, "THIN_GLASS", "STAINED_GLASS_PANE"), + PINK_TERRACOTTA(6, "STAINED_CLAY"), + PINK_TULIP(7, "RED_ROSE"), + PINK_WALL_BANNER(9, "WALL_BANNER"), + PINK_WOOL(6, "WOOL"), + PISTON("PISTON_BASE"), + PISTON_HEAD("PISTON_EXTENSION"), + PITCHER_CROP, + PITCHER_PLANT, + PITCHER_POD, + PLAYER_HEAD(3, "SKULL", "SKULL_ITEM"), + PLAYER_WALL_HEAD(3, "SKULL", "SKULL_ITEM"), + PLENTY_POTTERY_SHERD, + PODZOL(2, "DIRT"), + POINTED_DRIPSTONE, + POISONOUS_POTATO, + POLAR_BEAR_SPAWN_EGG(102, "MONSTER_EGG"), + POLISHED_ANDESITE(6, "STONE"), + POLISHED_ANDESITE_SLAB, + POLISHED_ANDESITE_STAIRS, + POLISHED_BASALT, + POLISHED_BLACKSTONE, + POLISHED_BLACKSTONE_BRICKS, + POLISHED_BLACKSTONE_BRICK_SLAB, + POLISHED_BLACKSTONE_BRICK_STAIRS, + POLISHED_BLACKSTONE_BRICK_WALL, + POLISHED_BLACKSTONE_BUTTON, + POLISHED_BLACKSTONE_PRESSURE_PLATE, + POLISHED_BLACKSTONE_SLAB, + POLISHED_BLACKSTONE_STAIRS, + POLISHED_BLACKSTONE_WALL, + POLISHED_DEEPSLATE, + POLISHED_DEEPSLATE_SLAB, + POLISHED_DEEPSLATE_STAIRS, + POLISHED_DEEPSLATE_WALL, + POLISHED_DIORITE(4, "STONE"), + POLISHED_DIORITE_SLAB, + POLISHED_DIORITE_STAIRS, + POLISHED_GRANITE(2, "STONE"), + POLISHED_GRANITE_SLAB, + POLISHED_GRANITE_STAIRS, + POLISHED_TUFF, + POLISHED_TUFF_SLAB, + POLISHED_TUFF_STAIRS, + POLISHED_TUFF_WALL, + POPPED_CHORUS_FRUIT("CHORUS_FRUIT_POPPED"), + POPPY("RED_ROSE"), + PORKCHOP("PORK"), + POTATO("POTATO_ITEM"), + POTATOES("POTATO"), + POTION, + POTTED_ACACIA_SAPLING(4, "FLOWER_POT"), + POTTED_ALLIUM(2, "FLOWER_POT"), + POTTED_AZALEA_BUSH, + POTTED_AZURE_BLUET(3, "FLOWER_POT"), + POTTED_BAMBOO, + POTTED_BIRCH_SAPLING(2, "FLOWER_POT"), + POTTED_BLUE_ORCHID(1, "FLOWER_POT"), + POTTED_BROWN_MUSHROOM("FLOWER_POT"), + POTTED_CACTUS("FLOWER_POT"), + POTTED_CHERRY_SAPLING, + POTTED_CORNFLOWER, + POTTED_CRIMSON_FUNGUS, + POTTED_CRIMSON_ROOTS, + POTTED_DANDELION("FLOWER_POT"), + POTTED_DARK_OAK_SAPLING(5, "FLOWER_POT"), + POTTED_DEAD_BUSH("FLOWER_POT"), + POTTED_FERN(2, "FLOWER_POT"), + POTTED_FLOWERING_AZALEA_BUSH, + POTTED_JUNGLE_SAPLING(3, "FLOWER_POT"), + POTTED_LILY_OF_THE_VALLEY, + POTTED_MANGROVE_PROPAGULE, + POTTED_OAK_SAPLING("FLOWER_POT"), + POTTED_ORANGE_TULIP(5, "FLOWER_POT"), + POTTED_OXEYE_DAISY(8, "FLOWER_POT"), + POTTED_PINK_TULIP(7, "FLOWER_POT"), + POTTED_POPPY("FLOWER_POT"), + POTTED_RED_MUSHROOM("FLOWER_POT"), + POTTED_RED_TULIP(4, "FLOWER_POT"), + POTTED_SPRUCE_SAPLING(1, "FLOWER_POT"), + POTTED_TORCHFLOWER, + POTTED_WARPED_FUNGUS, + POTTED_WARPED_ROOTS, + POTTED_WHITE_TULIP(6, "FLOWER_POT"), + POTTED_WITHER_ROSE, + POTTERY_SHARD_ARCHER, + POTTERY_SHARD_ARMS_UP, + POTTERY_SHARD_PRIZE, + POTTERY_SHARD_SKULL, + POWDER_SNOW, + POWDER_SNOW_BUCKET, + POWDER_SNOW_CAULDRON, + POWERED_RAIL, + PRISMARINE, + PRISMARINE_BRICKS(1, "PRISMARINE"), + PRISMARINE_BRICK_SLAB, + PRISMARINE_BRICK_STAIRS, + PRISMARINE_CRYSTALS, + PRISMARINE_SHARD, + PRISMARINE_SLAB, + PRISMARINE_STAIRS, + PRISMARINE_WALL, + PRIZE_POTTERY_SHERD, + PUFFERFISH(3, "RAW_FISH"), + PUFFERFISH_BUCKET, + PUFFERFISH_SPAWN_EGG, + PUMPKIN, + PUMPKIN_PIE, + PUMPKIN_SEEDS, + PUMPKIN_STEM, + PURPLE_BANNER(5, "STANDING_BANNER", "BANNER"), + PURPLE_BED(supports(12) ? 10 : 0, "BED_BLOCK", "BED"), + PURPLE_CANDLE, + PURPLE_CANDLE_CAKE, + PURPLE_CARPET(10, "CARPET"), + PURPLE_CONCRETE(10, "CONCRETE"), + PURPLE_CONCRETE_POWDER(10, "CONCRETE_POWDER"), + PURPLE_DYE(5, "INK_SACK"), + PURPLE_GLAZED_TERRACOTTA, + PURPLE_SHULKER_BOX, + PURPLE_STAINED_GLASS(10, "STAINED_GLASS"), + PURPLE_STAINED_GLASS_PANE(10, "THIN_GLASS", "STAINED_GLASS_PANE"), + PURPLE_TERRACOTTA(10, "STAINED_CLAY"), + PURPLE_WALL_BANNER(5, "WALL_BANNER"), + PURPLE_WOOL(10, "WOOL"), + PURPUR_BLOCK, + PURPUR_PILLAR, + PURPUR_SLAB("PURPUR_DOUBLE_SLAB"), + PURPUR_STAIRS, + QUARTZ, + QUARTZ_BLOCK, + QUARTZ_BRICKS, + QUARTZ_PILLAR(2, "QUARTZ_BLOCK"), + QUARTZ_SLAB(7, "STEP"), + QUARTZ_STAIRS, + RABBIT, + RABBIT_FOOT, + RABBIT_HIDE, + RABBIT_SPAWN_EGG(101, "MONSTER_EGG"), + RABBIT_STEW, + RAIL("RAILS"), + RAISER_ARMOR_TRIM_SMITHING_TEMPLATE, + RAVAGER_SPAWN_EGG, + RAW_COPPER, + RAW_COPPER_BLOCK, + RAW_GOLD, + RAW_GOLD_BLOCK, + RAW_IRON, + RAW_IRON_BLOCK, + RECOVERY_COMPASS, + REDSTONE, + REDSTONE_BLOCK, + /** + * Unlike redstone torch, REDSTONE_LAMP_ON isn't an item. + * The name is just here on the list for matching. + * + * @see #REDSTONE_TORCH + */ + REDSTONE_LAMP("REDSTONE_LAMP_ON", "REDSTONE_LAMP_OFF"), + REDSTONE_ORE("GLOWING_REDSTONE_ORE"), + /** + * REDSTONE_TORCH_OFF isn't an item, but a block. + * But REDSTONE_TORCH_ON is the item. + * The name is just here on the list for matching. + */ + REDSTONE_TORCH("REDSTONE_TORCH_OFF", "REDSTONE_TORCH_ON"), + REDSTONE_WALL_TORCH, + REDSTONE_WIRE, + RED_BANNER(1, "STANDING_BANNER", "BANNER"), + /** + * Data value 14 or 0 + */ + RED_BED(supports(12) ? 14 : 0, "BED_BLOCK", "BED"), + RED_CANDLE, + RED_CANDLE_CAKE, + RED_CARPET(14, "CARPET"), + RED_CONCRETE(14, "CONCRETE"), + RED_CONCRETE_POWDER(14, "CONCRETE_POWDER"), + /** + * 1.13 renamed to ROSE_RED + * 1.14 renamed to RED_DYE + */ + RED_DYE(1, "INK_SACK", "ROSE_RED"), + RED_GLAZED_TERRACOTTA, + RED_MUSHROOM, + RED_MUSHROOM_BLOCK("RED_MUSHROOM", "HUGE_MUSHROOM_2"), + RED_NETHER_BRICKS("RED_NETHER_BRICK"), + RED_NETHER_BRICK_SLAB, + RED_NETHER_BRICK_STAIRS, + RED_NETHER_BRICK_WALL, + RED_SAND(1, "SAND"), + RED_SANDSTONE, + RED_SANDSTONE_SLAB("DOUBLE_STONE_SLAB2", "STONE_SLAB2"), + RED_SANDSTONE_STAIRS, + RED_SANDSTONE_WALL, + RED_SHULKER_BOX, + RED_STAINED_GLASS(14, "STAINED_GLASS"), + RED_STAINED_GLASS_PANE(14, "THIN_GLASS", "STAINED_GLASS_PANE"), + RED_TERRACOTTA(14, "STAINED_CLAY"), + RED_TULIP(4, "RED_ROSE"), + RED_WALL_BANNER(1, "WALL_BANNER"), + RED_WOOL(14, "WOOL"), + REINFORCED_DEEPSLATE, + REPEATER("DIODE_BLOCK_ON", "DIODE_BLOCK_OFF", "DIODE"), + REPEATING_COMMAND_BLOCK("COMMAND", "COMMAND_REPEATING"), + RESPAWN_ANCHOR, + RIB_ARMOR_TRIM_SMITHING_TEMPLATE, + ROOTED_DIRT, + ROSE_BUSH(4, "DOUBLE_PLANT"), + ROTTEN_FLESH, + SADDLE, + SALMON(1, "RAW_FISH"), + SALMON_BUCKET, + SALMON_SPAWN_EGG, + SAND, + SANDSTONE, + SANDSTONE_SLAB(1, "DOUBLE_STEP", "STEP", "STONE_SLAB"), + SANDSTONE_STAIRS, + SANDSTONE_WALL, + SCAFFOLDING, + SCULK, + SCULK_CATALYST, + SCULK_SENSOR, + SCULK_SHRIEKER, + SCULK_VEIN, + SCUTE, + SEAGRASS, + SEA_LANTERN, + SEA_PICKLE, + SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE, + SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE, + SHEAF_POTTERY_SHERD, + SHEARS, + SHEEP_SPAWN_EGG(91, "MONSTER_EGG"), + SHELTER_POTTERY_SHERD, + SHIELD, + /** + * 1.13.0: LONG_GRASS:1 + * 1.20.4: GRASS -> SHORT_GRASS + */ + SHORT_GRASS(1, "GRASS", "LONG_GRASS"), + SHROOMLIGHT, + SHULKER_BOX("PURPLE_SHULKER_BOX"), + SHULKER_SHELL, + SHULKER_SPAWN_EGG(69, "MONSTER_EGG"), + SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE, + SILVERFISH_SPAWN_EGG(60, "MONSTER_EGG"), + SKELETON_HORSE_SPAWN_EGG(28, "MONSTER_EGG"), + SKELETON_SKULL("SKULL", "SKULL_ITEM"), + SKELETON_SPAWN_EGG(51, "MONSTER_EGG"), + SKELETON_WALL_SKULL("SKULL", "SKULL_ITEM"), + SKULL_BANNER_PATTERN, + SKULL_POTTERY_SHERD, + SLIME_BALL, + SLIME_BLOCK, + SLIME_SPAWN_EGG(55, "MONSTER_EGG"), + SMALL_AMETHYST_BUD, + SMALL_DRIPLEAF, + SMITHING_TABLE, + SMOKER, + SMOOTH_BASALT, + SMOOTH_QUARTZ, + SMOOTH_QUARTZ_SLAB, + SMOOTH_QUARTZ_STAIRS, + SMOOTH_RED_SANDSTONE(2, "RED_SANDSTONE"), + SMOOTH_RED_SANDSTONE_SLAB("STONE_SLAB2"), + SMOOTH_RED_SANDSTONE_STAIRS, + SMOOTH_SANDSTONE(2, "SANDSTONE"), + SMOOTH_SANDSTONE_SLAB, + SMOOTH_SANDSTONE_STAIRS, + SMOOTH_STONE, + SMOOTH_STONE_SLAB, + SNIFFER_EGG, + SNIFFER_SPAWN_EGG, + SNORT_POTTERY_SHERD, + SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE, + SNOW, + SNOWBALL("SNOW_BALL"), + SNOW_BLOCK, + SNOW_GOLEM_SPAWN_EGG, + SOUL_CAMPFIRE, + SOUL_FIRE, + SOUL_LANTERN, + SOUL_SAND, + SOUL_SOIL, + SOUL_TORCH, + SOUL_WALL_TORCH, + SPAWNER("MOB_SPAWNER"), + SPECTRAL_ARROW, + SPIDER_EYE, + SPIDER_SPAWN_EGG(52, "MONSTER_EGG"), + SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE, + SPLASH_POTION("POTION"), + SPONGE, + SPORE_BLOSSOM, + SPRUCE_BOAT("BOAT_SPRUCE"), + SPRUCE_BUTTON("WOOD_BUTTON"), + SPRUCE_CHEST_BOAT, + SPRUCE_DOOR("SPRUCE_DOOR", "SPRUCE_DOOR_ITEM"), + SPRUCE_FENCE, + SPRUCE_FENCE_GATE, + SPRUCE_HANGING_SIGN, + SPRUCE_LEAVES(1, "LEAVES"), + SPRUCE_LOG(1, "LOG"), + SPRUCE_PLANKS(1, "WOOD"), + SPRUCE_PRESSURE_PLATE("WOOD_PLATE"), + SPRUCE_SAPLING(1, "SAPLING"), + SPRUCE_SIGN("SIGN_POST", "SIGN"), + SPRUCE_SLAB(1, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + SPRUCE_STAIRS("SPRUCE_WOOD_STAIRS"), + SPRUCE_TRAPDOOR("TRAP_DOOR"), + SPRUCE_WALL_HANGING_SIGN, + SPRUCE_WALL_SIGN("WALL_SIGN"), + SPRUCE_WOOD(1, "LOG"), + SPYGLASS, + SQUID_SPAWN_EGG(94, "MONSTER_EGG"), + STICK, + STICKY_PISTON("PISTON_BASE", "PISTON_STICKY_BASE"), + STONE, + STONECUTTER, + STONE_AXE, + STONE_BRICKS("SMOOTH_BRICK"), + STONE_BRICK_SLAB(5, "DOUBLE_STEP", "STEP", "STONE_SLAB"), + STONE_BRICK_STAIRS("SMOOTH_STAIRS"), + STONE_BRICK_WALL, + STONE_BUTTON, + STONE_HOE, + STONE_PICKAXE, + STONE_PRESSURE_PLATE("STONE_PLATE"), + STONE_SHOVEL("STONE_SPADE"), + STONE_SLAB("DOUBLE_STEP", "STEP"), + STONE_STAIRS, + STONE_SWORD, + STRAY_SPAWN_EGG(6, "MONSTER_EGG"), + STRIDER_SPAWN_EGG, + STRING, + STRIPPED_ACACIA_LOG, + STRIPPED_ACACIA_WOOD, + STRIPPED_BAMBOO_BLOCK, + STRIPPED_BIRCH_LOG, + STRIPPED_BIRCH_WOOD, + STRIPPED_CHERRY_LOG, + STRIPPED_CHERRY_WOOD, + STRIPPED_CRIMSON_HYPHAE, + STRIPPED_CRIMSON_STEM, + STRIPPED_DARK_OAK_LOG, + STRIPPED_DARK_OAK_WOOD, + STRIPPED_JUNGLE_LOG, + STRIPPED_JUNGLE_WOOD, + STRIPPED_MANGROVE_LOG, + STRIPPED_MANGROVE_WOOD, + STRIPPED_OAK_LOG, + STRIPPED_OAK_WOOD, + STRIPPED_SPRUCE_LOG, + STRIPPED_SPRUCE_WOOD, + STRIPPED_WARPED_HYPHAE, + STRIPPED_WARPED_STEM, + STRUCTURE_BLOCK, + /** + * Originally developers used barrier blocks for its purpose. + * So technically this isn't really considered as a suggested material. + */ + STRUCTURE_VOID(10, "BARRIER"), + SUGAR, + /** + * Sugar Cane is a known material in pre-1.13 + */ + SUGAR_CANE("SUGAR_CANE_BLOCK"), + SUNFLOWER("DOUBLE_PLANT"), + SUSPICIOUS_GRAVEL, + SUSPICIOUS_SAND, + SUSPICIOUS_STEW, + SWEET_BERRIES, + SWEET_BERRY_BUSH, + TADPOLE_BUCKET, + TADPOLE_SPAWN_EGG, + TALL_GRASS(2, "DOUBLE_PLANT"), + TALL_SEAGRASS, + TARGET, + TERRACOTTA("HARD_CLAY"), + TIDE_ARMOR_TRIM_SMITHING_TEMPLATE, + TINTED_GLASS, + TIPPED_ARROW, + TNT, + TNT_MINECART("EXPLOSIVE_MINECART"), + TORCH, + TORCHFLOWER, + TORCHFLOWER_CROP, + TORCHFLOWER_SEEDS, + TOTEM_OF_UNDYING("TOTEM"), + TRADER_LLAMA_SPAWN_EGG, + TRAPPED_CHEST, + TRIAL_KEY, + TRIAL_SPAWNER, + TRIDENT, + TRIPWIRE, + TRIPWIRE_HOOK, + TROPICAL_FISH(2, "RAW_FISH"), + TROPICAL_FISH_BUCKET("BUCKET", "WATER_BUCKET"), + TROPICAL_FISH_SPAWN_EGG("MONSTER_EGG"), + TUBE_CORAL, + TUBE_CORAL_BLOCK, + TUBE_CORAL_FAN, + TUBE_CORAL_WALL_FAN, + TUFF, + TUFF_BRICKS, + TUFF_BRICK_SLAB, + TUFF_BRICK_STAIRS, + TUFF_BRICK_WALL, + TUFF_SLAB, + TUFF_STAIRS, + TUFF_WALL, + TURTLE_EGG, + TURTLE_HELMET, + TURTLE_SPAWN_EGG, + TWISTING_VINES, + TWISTING_VINES_PLANT, + VERDANT_FROGLIGHT, + VEX_ARMOR_TRIM_SMITHING_TEMPLATE, + VEX_SPAWN_EGG(35, "MONSTER_EGG"), + VILLAGER_SPAWN_EGG(120, "MONSTER_EGG"), + VINDICATOR_SPAWN_EGG(36, "MONSTER_EGG"), + VINE, + /** + * 1.13 tag is not added because it's the same thing as {@link #AIR} + * + * @see #CAVE_AIR + */ + VOID_AIR("AIR"), + WALL_TORCH("TORCH"), + WANDERING_TRADER_SPAWN_EGG, + WARDEN_SPAWN_EGG, + WARD_ARMOR_TRIM_SMITHING_TEMPLATE, + WARPED_BUTTON, + WARPED_DOOR, + WARPED_FENCE, + WARPED_FENCE_GATE, + WARPED_FUNGUS, + WARPED_FUNGUS_ON_A_STICK, + WARPED_HANGING_SIGN, + WARPED_HYPHAE, + WARPED_NYLIUM, + WARPED_PLANKS, + WARPED_PRESSURE_PLATE, + WARPED_ROOTS, + WARPED_SIGN("SIGN_POST"), + WARPED_SLAB, + WARPED_STAIRS, + WARPED_STEM, + WARPED_TRAPDOOR, + WARPED_WALL_HANGING_SIGN, + WARPED_WALL_SIGN("WALL_SIGN"), + WARPED_WART_BLOCK, + /** + * This is used for blocks only. + * In 1.13- WATER will turn into STATIONARY_WATER after it finished spreading. + * After 1.13+ this uses + * Levelled water flowing system. + */ + WATER("STATIONARY_WATER"), + WATER_BUCKET, + WATER_CAULDRON, + WAXED_CHISELED_COPPER, + WAXED_COPPER_BLOCK, + WAXED_COPPER_BULB, + WAXED_COPPER_DOOR, + WAXED_COPPER_GRATE, + WAXED_COPPER_TRAPDOOR, + WAXED_CUT_COPPER, + WAXED_CUT_COPPER_SLAB, + WAXED_CUT_COPPER_STAIRS, + WAXED_EXPOSED_CHISELED_COPPER, + WAXED_EXPOSED_COPPER, + WAXED_EXPOSED_COPPER_BULB, + WAXED_EXPOSED_COPPER_DOOR, + WAXED_EXPOSED_COPPER_GRATE, + WAXED_EXPOSED_COPPER_TRAPDOOR, + WAXED_EXPOSED_CUT_COPPER, + WAXED_EXPOSED_CUT_COPPER_SLAB, + WAXED_EXPOSED_CUT_COPPER_STAIRS, + WAXED_OXIDIZED_CHISELED_COPPER, + WAXED_OXIDIZED_COPPER, + WAXED_OXIDIZED_COPPER_BULB, + WAXED_OXIDIZED_COPPER_DOOR, + WAXED_OXIDIZED_COPPER_GRATE, + WAXED_OXIDIZED_COPPER_TRAPDOOR, + WAXED_OXIDIZED_CUT_COPPER, + WAXED_OXIDIZED_CUT_COPPER_SLAB, + WAXED_OXIDIZED_CUT_COPPER_STAIRS, + WAXED_WEATHERED_CHISELED_COPPER, + WAXED_WEATHERED_COPPER, + WAXED_WEATHERED_COPPER_BULB, + WAXED_WEATHERED_COPPER_DOOR, + WAXED_WEATHERED_COPPER_GRATE, + WAXED_WEATHERED_COPPER_TRAPDOOR, + WAXED_WEATHERED_CUT_COPPER, + WAXED_WEATHERED_CUT_COPPER_SLAB, + WAXED_WEATHERED_CUT_COPPER_STAIRS, + WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE, + WEATHERED_CHISELED_COPPER, + WEATHERED_COPPER, + WEATHERED_COPPER_BULB, + WEATHERED_COPPER_DOOR, + WEATHERED_COPPER_GRATE, + WEATHERED_COPPER_TRAPDOOR, + WEATHERED_CUT_COPPER, + WEATHERED_CUT_COPPER_SLAB, + WEATHERED_CUT_COPPER_STAIRS, + WEEPING_VINES, + WEEPING_VINES_PLANT, + WET_SPONGE(1, "SPONGE"), + /** + * Wheat is a known material in pre-1.13 + */ + WHEAT("CROPS"), + WHEAT_SEEDS("SEEDS"), + WHITE_BANNER(15, "STANDING_BANNER", "BANNER"), + WHITE_BED("BED_BLOCK", "BED"), + WHITE_CANDLE, + WHITE_CANDLE_CAKE, + WHITE_CARPET("CARPET"), + WHITE_CONCRETE("CONCRETE"), + WHITE_CONCRETE_POWDER("CONCRETE_POWDER"), + WHITE_DYE(15, "INK_SACK", "BONE_MEAL"), + WHITE_GLAZED_TERRACOTTA, + WHITE_SHULKER_BOX, + WHITE_STAINED_GLASS("STAINED_GLASS"), + WHITE_STAINED_GLASS_PANE("THIN_GLASS", "STAINED_GLASS_PANE"), + WHITE_TERRACOTTA("STAINED_CLAY"), + WHITE_TULIP(6, "RED_ROSE"), + WHITE_WALL_BANNER(15, "WALL_BANNER"), + WHITE_WOOL("WOOL"), + WILD_ARMOR_TRIM_SMITHING_TEMPLATE, + WITCH_SPAWN_EGG(66, "MONSTER_EGG"), + WITHER_ROSE, + WITHER_SKELETON_SKULL(1, "SKULL", "SKULL_ITEM"), + WITHER_SKELETON_SPAWN_EGG(5, "MONSTER_EGG"), + WITHER_SKELETON_WALL_SKULL(1, "SKULL", "SKULL_ITEM"), + WITHER_SPAWN_EGG, + WOLF_SPAWN_EGG(95, "MONSTER_EGG"), + WOODEN_AXE("WOOD_AXE"), + WOODEN_HOE("WOOD_HOE"), + WOODEN_PICKAXE("WOOD_PICKAXE"), + WOODEN_SHOVEL("WOOD_SPADE"), + WOODEN_SWORD("WOOD_SWORD"), + WRITABLE_BOOK("BOOK_AND_QUILL"), + WRITTEN_BOOK, + YELLOW_BANNER(11, "STANDING_BANNER", "BANNER"), + YELLOW_BED(supports(12) ? 4 : 0, "BED_BLOCK", "BED"), + YELLOW_CANDLE, + YELLOW_CANDLE_CAKE, + YELLOW_CARPET(4, "CARPET"), + YELLOW_CONCRETE(4, "CONCRETE"), + YELLOW_CONCRETE_POWDER(4, "CONCRETE_POWDER"), + /** + * 1.13 renamed to DANDELION_YELLOW + * 1.14 renamed to YELLOW_DYE + */ + YELLOW_DYE(11, "INK_SACK", "DANDELION_YELLOW"), + YELLOW_GLAZED_TERRACOTTA, + YELLOW_SHULKER_BOX, + YELLOW_STAINED_GLASS(4, "STAINED_GLASS"), + YELLOW_STAINED_GLASS_PANE(4, "THIN_GLASS", "STAINED_GLASS_PANE"), + YELLOW_TERRACOTTA(4, "STAINED_CLAY"), + YELLOW_WALL_BANNER(11, "WALL_BANNER"), + YELLOW_WOOL(4, "WOOL"), + ZOGLIN_SPAWN_EGG, + ZOMBIE_HEAD(2, "SKULL", "SKULL_ITEM"), + ZOMBIE_HORSE_SPAWN_EGG(29, "MONSTER_EGG"), + ZOMBIE_SPAWN_EGG(54, "MONSTER_EGG"), + ZOMBIE_VILLAGER_SPAWN_EGG(27, "MONSTER_EGG"), + ZOMBIE_WALL_HEAD(2, "SKULL", "SKULL_ITEM"), + ZOMBIFIED_PIGLIN_SPAWN_EGG(57, "MONSTER_EGG", "ZOMBIE_PIGMAN_SPAWN_EGG"); + + + /** + * Cached array of {@link XMaterial#values()} to avoid allocating memory for + * calling the method every time. + * + * @since 2.0.0 + */ + public static final XMaterial[] VALUES = values(); + + /** + * We don't want to use {@link Enums#getIfPresent(Class, String)} to avoid a few checks. + * + * @since 5.1.0 + */ + private static final Map NAMES = new HashMap<>(); + + /** + * Guava (Google Core Libraries for Java)'s cache for performance and timed caches. + * For strings that match a certain XMaterial. Mostly cached for configs. + * + * @since 1.0.0 + */ + private static final Cache NAME_CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .build(); + /** + * This is used for {@link #isOneOf(Collection)} + * + * @since 3.4.0 + */ + private static final Cache CACHED_REGEX = CacheBuilder.newBuilder() + .expireAfterAccess(3, TimeUnit.HOURS) + .build(); + /** + * The maximum data value in the pre-flattening update which belongs to {@link #VILLAGER_SPAWN_EGG}
+ * Spawn Eggs + * + * @see #matchXMaterialWithData(String) + * @since 8.0.0 + */ + private static final byte MAX_DATA_VALUE = 120; + /** + * Used to tell the system that the passed object's (name or material) data value + * is not provided or is invalid. + * + * @since 8.0.0 + */ + private static final byte UNKNOWN_DATA_VALUE = -1; + /** + * The maximum material ID before the pre-flattening update which belongs to {@link #MUSIC_DISC_WAIT} + * + * @since 8.1.0 + */ + private static final short MAX_ID = 2267; + /** + * XMaterial Paradox (Duplication Check) + *

+ * A set of duplicated material names in 1.13 and 1.12 that will conflict with the legacy names. + * Values are the new material names. This map also contains illegal elements. Check the static initializer for more info. + *

+ * Duplications are not useful at all in versions above the flattening update {@link Data#ISFLAT} + * This set is only used for matching materials, for parsing refer to {@link #isDuplicated()} + * + * @since 3.0.0 + */ + private static final Set DUPLICATED; + + static { + for (XMaterial material : VALUES) NAMES.put(material.name(), material); + } + + static { + if (Data.ISFLAT) { + // It's not needed at all if it's the newer version. We can save some memory. + DUPLICATED = null; + } else { + // MELON_SLICE, CARROTS, POTATOES, BEETROOTS, GRASS_BLOCK, BRICKS, NETHER_BRICKS, BROWN_MUSHROOM + // Using the constructor to add elements will decide to allocate more size which we don't need. + DUPLICATED = new HashSet<>(4); + DUPLICATED.add("GRASS"); + DUPLICATED.add(MELON.name()); + DUPLICATED.add(BRICK.name()); + DUPLICATED.add(NETHER_BRICK.name()); + } + } + + /** + * The data value of this material Pre-flattening + * It's never a negative number. + * + * @see #getData() + */ + private final byte data; + /** + * A list of material names that was being used for older verions. + * + * @see #getLegacy() + */ + @Nonnull + private final String[] legacy; + /** + * The cached Bukkit parsed material. + * + * @see #parseMaterial() + * @since 9.0.0 + */ + @Nullable + private final Material material; + + XMaterial(int data, @Nonnull String... legacy) { + this.data = (byte) data; + this.legacy = legacy; + + Material mat = null; + if ((!Data.ISFLAT && this.isDuplicated()) || (mat = Material.getMaterial(this.name())) == null) { + for (int i = legacy.length - 1; i >= 0; i--) { + mat = Material.getMaterial(legacy[i]); + if (mat != null) break; + } + } + this.material = mat; + } + + XMaterial(String... legacy) { + this(0, legacy); + } + + /** + * Gets the XMaterial with this name similar to {@link #valueOf(String)} + * without throwing an exception. + * + * @param name the name of the material. + * @return an optional that can be empty. + * @since 5.1.0 + */ + @Nonnull + private static Optional getIfPresent(@Nonnull String name) { + return Optional.ofNullable(NAMES.get(name)); + } + + /** + * The current version of the server. + * + * @return the current server version minor number. + * @see #supports(int) + * @since 2.0.0 + */ + public static int getVersion() { + return Data.VERSION; + } + + /** + * When using 1.13+, this helps to find the old material name + * with its data value using a cached search for optimization. + * + * @see #matchDefinedXMaterial(String, byte) + * @since 1.0.0 + */ + @Nullable + private static XMaterial requestOldXMaterial(@Nonnull String name, byte data) { + String holder = name + data; + XMaterial cache = NAME_CACHE.getIfPresent(holder); + if (cache != null) return cache; + + for (XMaterial material : VALUES) { + // Not using material.name().equals(name) check is intended. + if ((data == UNKNOWN_DATA_VALUE || data == material.data) && material.anyMatchLegacy(name)) { + NAME_CACHE.put(holder, material); + return material; + } + } + + return null; + } + + /** + * Parses material name and data value from the specified string. + * The separator for the material name and its data value is {@code :} + * Spaces are allowed. Mostly used when getting materials from config for old school minecrafters. + *

+ * Examples + *

+     *     {@code INK_SACK:1 -> RED_DYE}
+     *     {@code WOOL: 14  -> RED_WOOL}
+     * 
+ * + * @param name the material string that consists of the material name, data and separator character. + * @return the parsed XMaterial. + * @see #matchXMaterial(String) + * @since 3.0.0 + */ + @Nonnull + private static Optional matchXMaterialWithData(@Nonnull String name) { + int index = name.indexOf(':'); + if (index != -1) { + String mat = format(name.substring(0, index)); + try { + // We don't use Byte.parseByte because we have our own range check. + byte data = (byte) Integer.parseInt(name.substring(index + 1).replace(" ", "")); + return data >= 0 && data < MAX_DATA_VALUE ? matchDefinedXMaterial(mat, data) : matchDefinedXMaterial(mat, UNKNOWN_DATA_VALUE); + } catch (NumberFormatException ignored) { + return matchDefinedXMaterial(mat, UNKNOWN_DATA_VALUE); + } + } + + return Optional.empty(); + } + + /** + * Parses the given material name as an XMaterial with a given data + * value in the string if attached. Check {@link #matchXMaterialWithData(String)} for more info. + * + * @see #matchXMaterialWithData(String) + * @see #matchDefinedXMaterial(String, byte) + * @since 2.0.0 + */ + @Nonnull + public static Optional matchXMaterial(@Nonnull String name) { + if (name == null || name.isEmpty()) + throw new IllegalArgumentException("Cannot match a material with null or empty material name"); + Optional oldMatch = matchXMaterialWithData(name); + return oldMatch.isPresent() ? oldMatch : matchDefinedXMaterial(format(name), UNKNOWN_DATA_VALUE); + } + + /** + * Parses the given material as an XMaterial. + * + * @throws IllegalArgumentException may be thrown as an unexpected exception. + * @see #matchDefinedXMaterial(String, byte) + * @see #matchXMaterial(ItemStack) + * @since 2.0.0 + */ + @Nonnull + public static XMaterial matchXMaterial(@Nonnull Material material) { + Objects.requireNonNull(material, "Cannot match null material"); + return matchDefinedXMaterial(material.name(), UNKNOWN_DATA_VALUE) + .orElseThrow(() -> new IllegalArgumentException("Unsupported material with no data value: " + material.name())); + } + + /** + * Parses the given item as an XMaterial using its material and data value (durability) + * if not a damageable item {@link ItemStack#getDurability()}. + * + * @param item the ItemStack to match. + * @return an XMaterial if matched any. + * @throws IllegalArgumentException may be thrown as an unexpected exception. + * @see #matchXMaterial(Material) + * @since 2.0.0 + */ + @Nonnull + @SuppressWarnings("deprecation") + public static XMaterial matchXMaterial(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot match null ItemStack"); + String material = item.getType().name(); + + // 1.13+ doesn't use data values at all. + // Maps are given different data values for different parts of the map also some plugins use negative values for custom images. + // Items that have durability, such as armor and tools don't use the data value to distinguish their material. + byte data = (byte) (Data.ISFLAT || material.equals("MAP") || item.getType().getMaxDurability() > 0 ? 0 : item.getDurability()); + + // Versions 1.9-1.12 didn't really use the items data value. + if (supports(9) && !supports(13) && item.hasItemMeta() && material.equals("MONSTER_EGG")) { + ItemMeta meta = item.getItemMeta(); + if (meta instanceof SpawnEggMeta) { + SpawnEggMeta egg = (SpawnEggMeta) meta; + material = egg.getSpawnedType().name() + "_SPAWN_EGG"; + } + } + + // Potions used the items data value to store + // information about the type of potion in 1.8 + if (!supports(9) && material.endsWith("ION")) { + // There's also 16000+ data value technique, but this is more reliable. + return Potion.fromItemStack(item).isSplash() ? SPLASH_POTION : POTION; + } + + // Refer to the enum for info. + // Currently, these are the only materials with a non-zero data value + // that has been renamed after the flattening update. + // If this happens to more materials in the future, I might have to change the system. + if (supports(13) && !supports(14)) { + // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/diff/src/main/java/org/bukkit/Material.java?until=67d908a9830c71267ee740f5bddd728ce9c64cc7 + if (material.equals("CACTUS_GREEN")) return GREEN_DYE; + if (material.equals("ROSE_RED")) return RED_DYE; + if (material.equals("DANDELION_YELLOW")) return YELLOW_DYE; + } + + // Check FILLED_MAP enum for more info. + // if (!Data.ISFLAT && item.hasItemMeta() && item.getItemMeta() instanceof org.bukkit.inventory.meta.MapMeta) return FILLED_MAP; + + // No orElseThrow, I don't want to deal with Java's final variable bullshit. + Optional result = matchDefinedXMaterial(material, data); + if (result.isPresent()) return result.get(); + throw new IllegalArgumentException("Unsupported material from item: " + material + " (" + data + ')'); + } + + /** + * Gets the XMaterial based on the material's ID (Magic Value) and data value.
+ * You should avoid using this for performance issues. + * + * @param id the ID (Magic value) of the material. + * @param data the data value of the material. + * @return a parsed XMaterial with the same ID and data value. + * @see #matchXMaterial(ItemStack) + * @since 2.0.0 + * @deprecated this method loops through all the available materials and matches their ID using {@link #getId()} + * which takes a really long time. Plugins should no longer support IDs. If you want, you can make a {@link Map} cache yourself. + * This method obviously doesn't work for 1.13+ and will not be supported. This is only here for debugging purposes. + */ + @Nonnull + @Deprecated + public static Optional matchXMaterial(int id, byte data) { + if (id < 0 || id > MAX_ID || data < 0) return Optional.empty(); + for (XMaterial materials : VALUES) { + if (materials.data == data && materials.getId() == id) return Optional.of(materials); + } + return Optional.empty(); + } + + /** + * The main method that parses the given material name and data value as an XMaterial. + * All the values passed to this method will not be null or empty and are formatted correctly. + * + * @param name the formatted name of the material. + * @param data the data value of the material. Is always 0 or {@link #UNKNOWN_DATA_VALUE} when {@link Data#ISFLAT} + * @return an XMaterial (with the same data value if specified) + * @see #matchXMaterial(Material) + * @see #matchXMaterial(int, byte) + * @see #matchXMaterial(ItemStack) + * @since 3.0.0 + */ + @SuppressWarnings({"DanglingJavadoc", "JavadocBlankLines"}) + @Nonnull + protected static Optional matchDefinedXMaterial(@Nonnull String name, byte data) { + // if (!Boolean.valueOf(Boolean.getBoolean(Boolean.TRUE.toString())).equals(Boolean.FALSE.booleanValue())) return null; + Boolean duplicated = null; + boolean isAMap = name.equalsIgnoreCase("MAP"); + + // Do basic number and boolean checks before accessing more complex enum stuff. + if (Data.ISFLAT || (!isAMap && data <= 0 && !(duplicated = isDuplicated(name)))) { + Optional xMaterial = getIfPresent(name); + if (xMaterial.isPresent()) return xMaterial; + } + // Usually flat versions wouldn't pass this point, but some special materials do. + + XMaterial oldXMaterial = requestOldXMaterial(name, data); + if (oldXMaterial == null) { + // Special case. Refer to FILLED_MAP for more info. + return (data >= 0 && isAMap) ? Optional.of(FILLED_MAP) : Optional.empty(); + } + + /** + * XMaterial Paradox (Duplication Check) + * I've concluded that this is just an infinite loop that keeps + * going around the Singular Form and the Plural Form materials. A waste of brain cells and a waste of time. + * This solution works just fine anyway. + * + * A solution for XMaterial Paradox. + * Manually parses the duplicated materials to find the exact material based on the server version. + * If the name ends with "S" -> Plural Form Material. + * Plural methods are only plural if they're also {@link #DUPLICATED} + * + * The only special exceptions are {@link #BRICKS} (??) and {@link #NETHER_BRICKS} + * Note: BRICKS was added because + * {@code XMaterial.matchXMaterial("BRICK")} would match {@link #BRICKS} instead in 1.8. + */ + boolean isPlural = oldXMaterial == CARROTS || oldXMaterial == POTATOES || oldXMaterial == BRICKS; + + if (!Data.ISFLAT && isPlural && (duplicated == null ? isDuplicated(name) : duplicated)) + return getIfPresent(name); + return Optional.of(oldXMaterial); + } + + /** + * Attempts to build the string like an enum name. + * Removes all the spaces, and extra non-English characters. Also removes some config/in-game based strings. + * While this method is hard to maintain, it's extremely efficient. It's approximately more than x5 times faster than + * the normal RegEx + String Methods approach for both formatted and unformatted material names. + * + * @param name the material name to modify. + * @return an enum name. + * @since 2.0.0 + */ + @Nonnull + protected static String format(@Nonnull String name) { + int len = name.length(); + char[] chs = new char[len]; + int count = 0; + boolean appendUnderline = false; + + for (int i = 0; i < len; i++) { + char ch = name.charAt(i); + + if (!appendUnderline && count != 0 && (ch == '-' || ch == ' ' || ch == '_') && chs[count] != '_') + appendUnderline = true; + else { + boolean number = false; + // Old materials have numbers in them. + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (number = (ch >= '0' && ch <= '9'))) { + if (appendUnderline) { + chs[count++] = '_'; + appendUnderline = false; + } + + if (number) chs[count++] = ch; + else chs[count++] = (char) (ch & 0x5f); + } + } + } + + return new String(chs, 0, count); + } + + /** + * Checks if the specified version is the same version or higher than the current server version. + * + * @param version the major version to be checked. "1." is ignored. E.g. 1.12 = 12 | 1.9 = 9 + * @return true of the version is equal or higher than the current version. + * @since 2.0.0 + */ + public static boolean supports(int version) { + return Data.VERSION >= version; + } + + public String[] getLegacy() { + return this.legacy; + } + + /** + * Checks if the list of given material names matches the given base material. + * Mostly used for configs. + *

+ * Supports {@link String#contains} {@code CONTAINS:NAME} and Regular Expression {@code REGEX:PATTERN} formats. + *

+ * Example: + *

+     *     XMaterial material = {@link #matchXMaterial(ItemStack)};
+     *     if (material.isOneOf(plugin.getConfig().getStringList("disabled-items")) return;
+     * 
+ *
+ * {@code CONTAINS} Examples: + *
+     *     {@code "CONTAINS:CHEST" -> CHEST, ENDERCHEST, TRAPPED_CHEST -> true}
+     *     {@code "cOnTaINS:dYe" -> GREEN_DYE, YELLOW_DYE, BLUE_DYE, INK_SACK -> true}
+     * 
+ *

+ * {@code REGEX} Examples + *

+     *     {@code "REGEX:^.+_.+_.+$" -> Every Material with 3 underlines or more: SHULKER_SPAWN_EGG, SILVERFISH_SPAWN_EGG, SKELETON_HORSE_SPAWN_EGG}
+     *     {@code "REGEX:^.{1,3}$" -> Material names that have 3 letters only: BED, MAP, AIR}
+     * 
+ *

+ * The reason that there are tags for {@code CONTAINS} and {@code REGEX} is for the performance. + * Although RegEx patterns are cached in this method, + * please avoid using the {@code REGEX} tag if you can use the {@code CONTAINS} tag instead. + * It'll have a huge impact on performance. + * Please avoid using {@code (capturing groups)} there's no use for them in this case. + * If you want to use groups, use {@code (?: non-capturing groups)}. It's faster. + *

+ * Want to learn RegEx? You can mess around in RegExr website. + * + * @param materials the material names to check base material on. + * @return true if one of the given material names is similar to the base material. + * @since 3.1.1 + * @deprecated Use XTag.stringMatcher() instead. + */ + @Deprecated + public boolean isOneOf(@Nullable Collection materials) { + if (materials == null || materials.isEmpty()) return false; + String name = this.name(); + + for (String comp : materials) { + String checker = comp.toUpperCase(Locale.ENGLISH); + if (checker.startsWith("CONTAINS:")) { + comp = format(checker.substring(9)); + if (name.contains(comp)) return true; + continue; + } + if (checker.startsWith("REGEX:")) { + comp = comp.substring(6); + Pattern pattern = CACHED_REGEX.getIfPresent(comp); + if (pattern == null) { + try { + pattern = Pattern.compile(comp); + CACHED_REGEX.put(comp, pattern); + } catch (PatternSyntaxException ex) { + ex.printStackTrace(); + } + } + if (pattern != null && pattern.matcher(name).matches()) return true; + continue; + } + + // Direct Object Equals + Optional xMat = matchXMaterial(comp); + if (xMat.isPresent() && xMat.get() == this) return true; + } + return false; + } + + /** + * Sets the {@link Material} (and data value on older versions) of an item. + * Damageable materials will not have their durability changed. + *

+ * Use {@link #parseItem()} instead when creating new ItemStacks. + * + * @param item the item to change its type. + * @see #parseItem() + * @since 3.0.0 + */ + @Nonnull + @SuppressWarnings("deprecation") + public ItemStack setType(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot set material for null ItemStack"); + Material material = this.parseMaterial(); + Objects.requireNonNull(material, () -> "Unsupported material: " + this.name()); + + item.setType(material); + if (!Data.ISFLAT && material.getMaxDurability() <= 0) item.setDurability(this.data); + // Splash Potions weren't an official material pre-flattening. + if (!Data.ISFLAT && this == SPLASH_POTION) { + item.setDurability((short) 16384); // Hard-coded as 'data' is only a byte. + } + return item; + } + + /** + * Checks if the given material name matches any of this xmaterial's legacy material names. + * All the values passed to this method will not be null or empty and are formatted correctly. + * + * @param name the material name to check. + * @return true if it's one of the legacy names, otherwise false. + * @since 2.0.0 + */ + private boolean anyMatchLegacy(@Nonnull String name) { + for (int i = this.legacy.length - 1; i >= 0; i--) { + if (name.equals(this.legacy[i])) return true; + } + return false; + } + + /** + * Parses an enum name to a user-friendly name. + * These names will have underlines removed and with each word capitalized. + *

+ * Examples: + *

+     *     {@literal EMERALD                 -> Emerald}
+     *     {@literal EMERALD_BLOCK           -> Emerald Block}
+     *     {@literal ENCHANTED_GOLDEN_APPLE  -> Enchanted Golden Apple}
+     * 
+ * + * @return a more user-friendly enum name. + * @since 3.0.0 + */ + @Override + @Nonnull + public String toString() { + return Arrays.stream(name().split("_")) + .map(t -> t.charAt(0) + t.substring(1).toLowerCase()) + .collect(Collectors.joining(" ")); + } + + /** + * Gets the ID (Magic value) of the material. + * ID List + *

+ * Spigot added material ID support back in 1.16+ + * + * @return the ID of the material or -1 if it's not a legacy material or the server doesn't support the material. + * @see #matchXMaterial(int, byte) + * @since 2.2.0 + */ + @SuppressWarnings("deprecation") + public int getId() { + // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/diff/src/main/java/org/bukkit/Material.java?until=1cb03826ebde4ef887519ce37b0a2a341494a183 + // Should start working again in 1.16+ + Material material = this.parseMaterial(); + if (material == null) return -1; + try { + return material.getId(); + } catch (IllegalArgumentException ignored) { + return -1; + } + } + + /** + * The data value of this material pre-flattening. + *

+ * Can be accessed with {@link ItemStack#getData()} then {@code MaterialData#getData()} + * or {@link ItemStack#getDurability()} if not damageable. + * + * @return data of this material, or 0 if none. + * @since 1.0.0 + */ + @SuppressWarnings("deprecation") + public byte getData() { + return data; + } + + /** + * Parses an item from this XMaterial. + * Uses data values on older versions. + * + * @return an ItemStack with the same material (and data value if in older versions.) + * @see #setType(ItemStack) + * @since 2.0.0 + */ + @Nullable + @SuppressWarnings("deprecation") + public ItemStack parseItem() { + Material material = this.parseMaterial(); + if (material == null) return null; + ItemStack base = Data.ISFLAT ? new ItemStack(material) : new ItemStack(material, 1, this.data); + // Splash Potions weren't an official material pre-flattening. + if (!Data.ISFLAT && this == SPLASH_POTION) { + base.setDurability((short) 16384); // Hard-coded as 'data' is only a byte. + } + return base; + } + + /** + * Parses the material of this XMaterial. + * + * @return the material related to this XMaterial based on the server version. + * @since 1.0.0 + */ + @Nullable + public Material parseMaterial() { + return this.material; + } + + /** + * Checks if an item has the same material (and data value on older versions). + * + * @param item item to check. + * @return true if the material is the same as the item's material (and data value if on older versions), otherwise false. + * @since 1.0.0 + */ + @SuppressWarnings("deprecation") + public boolean isSimilar(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot compare with null ItemStack"); + if (item.getType() != this.parseMaterial()) return false; + // Special case for splash potions. + if (this == SPLASH_POTION) { + return Data.ISFLAT || item.getDurability() == (short) 16384; + } + return Data.ISFLAT || item.getDurability() == this.data || item.getType().getMaxDurability() > 0; + } + + /** + * Checks if this material is supported in the current version. + * Suggested materials will be ignored. + *

+ * Note that you should use {@link #parseMaterial()} or {@link #parseItem()} and check if it's null + * if you're going to parse and use the material/item later. + * + * @return true if the material exists in {@link Material} list. + * @since 2.0.0 + */ + public boolean isSupported() { + return this.material != null; + } + + /** + * Checks if this material is supported in the current version and + * returns itself if yes. + *

+ * In the other case, the alternate material will get returned, + * no matter if it is supported or not. + * + * @param alternateMaterial the material to get if this one is not supported. + * @return this material or the {@code alternateMaterial} if not supported. + */ + @Nullable + public XMaterial or(@Nullable XMaterial alternateMaterial) { + return isSupported() ? this : alternateMaterial; + } + + /** + * XMaterial Paradox (Duplication Check) + * Checks if the material has any duplicates. + *

+ * Example: + *

{@code MELON, CARROT, POTATO, BEETROOT -> true} + * + * @param name the name of the material to check. + * @return true if there's a duplicated material for this material, otherwise false. + * @since 2.0.0 + */ + private static boolean isDuplicated(@Nonnull String name) { + // Don't use matchXMaterial() since this method is being called from matchXMaterial() itself and will cause a StackOverflowError. + return DUPLICATED.contains(name); + } + + /** + * This method is needed due to Java enum initialization limitations. + * It's really inefficient yes, but it's only used for initialization. + *

+ * Yes there are many other ways like comparing the hardcoded ordinal or using a boolean in the enum constructor, + * but it's not really a big deal. + *

+ * This method should not be called if the version is after the flattening update {@link Data#ISFLAT} + * and is only used for parsing materials, not matching, for matching check {@link #DUPLICATED} + */ + private boolean isDuplicated() { + switch (this.name()) { + case "MELON": + case "CARROT": + case "POTATO": + case "GRASS": + case "BRICK": + case "NETHER_BRICK": + + // Illegal Elements + // Since both 1.12 and 1.13 have _DOOR XMaterial will use it + // for 1.12 to parse the material, but it needs _DOOR_ITEM. + // We'll trick XMaterial into thinking this needs to be parsed + // using the old methods. + // Some of these materials have their enum name added to the legacy list as well. + case "DARK_OAK_DOOR": + case "ACACIA_DOOR": + case "BIRCH_DOOR": + case "JUNGLE_DOOR": + case "SPRUCE_DOOR": + case "MAP": + case "CAULDRON": + case "BREWING_STAND": + case "FLOWER_POT": + return true; + default: + return false; + } + } + + /** + * Used for data that need to be accessed during enum initialization. + * + * @since 9.0.0 + */ + private static final class Data { + /** + * The current version of the server in the form of a major version. + * If the static initialization for this fails, you know something's wrong with the server software. + * + * @since 1.0.0 + */ + private static final int VERSION; + + static { // This needs to be right below VERSION because of initialization order. + String version = Bukkit.getVersion(); + Matcher matcher = Pattern.compile("MC: \\d\\.(\\d+)").matcher(version); + + if (matcher.find()) VERSION = Integer.parseInt(matcher.group(1)); + else throw new IllegalArgumentException("Failed to parse server version from: " + version); + } + + /** + * Cached result if the server version is after the v1.13 flattening update. + * + * @since 3.0.0 + */ + private static final boolean ISFLAT = supports(13); + } +} \ No newline at end of file From 89d4234a8cac80d8d91f2404f78b599df8c94be2 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Sun, 24 Dec 2023 15:39:25 +0100 Subject: [PATCH 34/52] Cleanup --- .../sbdevelopment/mapreflectionapi/MapReflectionAPI.java | 1 - .../sbdevelopment/mapreflectionapi/api/MapController.java | 1 - .../tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java | 4 +++- .../mapreflectionapi/api/MultiMapController.java | 4 ++-- .../api/events/CreativeInventoryMapUpdateEvent.java | 4 ---- .../mapreflectionapi/utils/ReflectionUtil.java | 6 ++++-- 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java index 7461be4..2049d7b 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java @@ -30,7 +30,6 @@ import tech.sbdevelopment.mapreflectionapi.listeners.MapListener; import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; import tech.sbdevelopment.mapreflectionapi.managers.Configuration; import tech.sbdevelopment.mapreflectionapi.utils.MainUtil; -import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager; import java.util.logging.Level; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java index 8618aa4..978e8ac 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java @@ -19,7 +19,6 @@ package tech.sbdevelopment.mapreflectionapi.api; import org.bukkit.OfflinePlayer; -import org.bukkit.World; import org.bukkit.entity.ItemFrame; import org.bukkit.entity.Player; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index a7eae14..d943fa5 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -19,7 +19,9 @@ package tech.sbdevelopment.mapreflectionapi.api; import lombok.Getter; -import org.bukkit.*; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.OfflinePlayer; import org.bukkit.entity.ItemFrame; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java index 537fd54..a027e09 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapController.java @@ -144,8 +144,8 @@ public interface MultiMapController extends IMapController { * Called to get debug information for a frame * * @param controller the {@link MapController} - * @param row Row of the current frame - * @param column Column of the current frame + * @param row Row of the current frame + * @param column Column of the current frame * @return {@link String} to show when a player looks at the map, or null * @see MapController#showInFrame(Player, int, String) */ diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java index 561ae96..e2f4334 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreativeInventoryMapUpdateEvent.java @@ -20,16 +20,12 @@ package tech.sbdevelopment.mapreflectionapi.api.events; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import org.bukkit.map.MapView; import org.jetbrains.annotations.Nullable; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; import tech.sbdevelopment.mapreflectionapi.api.events.types.CancellableEvent; -import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils; import tech.sbdevelopment.mapreflectionapi.utils.XMaterial; /** diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java index a1cbe13..ad81288 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java @@ -37,14 +37,16 @@ public class ReflectionUtil { * * @param The storage type */ - public static class ListParam extends ArrayList {} + public static class ListParam extends ArrayList { + } /** * Helper class converted to {@link Collection} * * @param The storage type */ - public static class CollectionParam extends ArrayList {} + public static class CollectionParam extends ArrayList { + } private static Class wrapperToPrimitive(Class clazz) { if (clazz == Boolean.class) return boolean.class; From 874b7d227c0deb4213c020e602d6268ec155367b Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Mon, 29 Apr 2024 16:43:23 +0200 Subject: [PATCH 35/52] Added 1.20.5 support in NMS branch --- .idea/encodings.xml | 2 ++ .idea/misc.xml | 9 ++++- .idea/ros.xml | 12 +++++++ Dist/pom.xml | 2 +- {NMS-v1_20_R1 => NMS-v1_20_R4}/pom.xml | 6 ++-- .../nms/MapSender_v1_20_R4.java | 15 +++++--- .../nms/MapWrapper_v1_20_R4.java | 36 ++++++++++--------- .../nms/PacketListener_v1_20_R4.java | 14 ++++---- pom.xml | 2 +- 9 files changed, 64 insertions(+), 34 deletions(-) create mode 100644 .idea/ros.xml rename {NMS-v1_20_R1 => NMS-v1_20_R4}/pom.xml (94%) rename NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java => NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R4.java (91%) rename NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java => NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R4.java (86%) rename NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java => NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R4.java (90%) diff --git a/.idea/encodings.xml b/.idea/encodings.xml index e45f72b..9d4e5cf 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -29,6 +29,8 @@ + + diff --git a/.idea/misc.xml b/.idea/misc.xml index fadb731..01cf610 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -12,6 +12,13 @@ + + + + - \ No newline at end of file diff --git a/.idea/ros.xml b/.idea/ros.xml new file mode 100644 index 0000000..29eeb8c --- /dev/null +++ b/.idea/ros.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/Dist/pom.xml b/Dist/pom.xml index 0af26ba..d9f9fc6 100644 --- a/Dist/pom.xml +++ b/Dist/pom.xml @@ -77,7 +77,7 @@ tech.sbdevelopment - MapReflectionAPI-NMS-v1_20_R1 + MapReflectionAPI-NMS-v1_20_R4 ${project.parent.version} diff --git a/NMS-v1_20_R1/pom.xml b/NMS-v1_20_R4/pom.xml similarity index 94% rename from NMS-v1_20_R1/pom.xml rename to NMS-v1_20_R4/pom.xml index 6f5cdde..ce1abe3 100644 --- a/NMS-v1_20_R1/pom.xml +++ b/NMS-v1_20_R4/pom.xml @@ -27,11 +27,11 @@ 4.0.0 - MapReflectionAPI-NMS-v1_20_R1 + MapReflectionAPI-NMS-v1_20_R4 - 1.20.1-R0.1-SNAPSHOT - 17 + 1.20.5-R0.1-SNAPSHOT + 21 diff --git a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R4.java similarity index 91% rename from NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java rename to NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R4.java index 3f523f7..c1c4956 100644 --- a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java +++ b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R4.java @@ -19,9 +19,10 @@ package tech.sbdevelopment.mapreflectionapi.nms; import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.MapId; import net.minecraft.world.level.saveddata.maps.WorldMap; import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer; import org.bukkit.entity.Player; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; @@ -29,14 +30,16 @@ import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; import java.util.ArrayList; import java.util.List; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.supports; + /** - * The {@link MapSender_v1_20_R1} sends the Map packets to players. + * The {@link MapSender_v1_20_R4} sends the Map packets to players. */ -public class MapSender_v1_20_R1 { +public class MapSender_v1_20_R4 { private static final List sendQueue = new ArrayList<>(); private static int senderID = -1; - private MapSender_v1_20_R1() { + private MapSender_v1_20_R4() { } /** @@ -118,8 +121,10 @@ public class MapSender_v1_20_R1 { content.array //Data ); + MapId mapId = new MapId(id); + PacketPlayOutMap packet = new PacketPlayOutMap( - id, //ID + mapId, //ID (byte) 0, //Scale false, //Show icons new ArrayList<>(), //Icons diff --git a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R4.java similarity index 86% rename from NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java rename to NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R4.java index fcdf607..9760612 100644 --- a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java +++ b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R4.java @@ -18,15 +18,18 @@ package tech.sbdevelopment.mapreflectionapi.nms; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.DataComponents; import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; import net.minecraft.network.syncher.DataWatcher; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.decoration.EntityItemFrame; +import net.minecraft.world.level.saveddata.maps.MapId; import org.bukkit.*; -import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_20_R4.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; import org.bukkit.entity.ItemFrame; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -40,7 +43,7 @@ import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededExcept import java.util.*; -public class MapWrapper_v1_20_R1 extends MapWrapper { +public class MapWrapper_v1_20_R4 extends MapWrapper { protected MapController controller = new MapController() { private final Map viewers = new HashMap<>(); @@ -81,11 +84,11 @@ public class MapWrapper_v1_20_R1 extends MapWrapper { public void update(ArrayImage content) { MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); if (duplicate != null) { - MapWrapper_v1_20_R1.this.content = duplicate.getContent(); + MapWrapper_v1_20_R4.this.content = duplicate.getContent(); return; } - MapWrapper_v1_20_R1.this.content = content; + MapWrapper_v1_20_R4.this.content = content; for (UUID id : viewers.keySet()) { sendContent(Bukkit.getPlayer(id)); @@ -103,16 +106,16 @@ public class MapWrapper_v1_20_R1 extends MapWrapper { int id = getMapId(player); if (withoutQueue) { - MapSender_v1_20_R1.sendMap(id, MapWrapper_v1_20_R1.this.content, player); + MapSender_v1_20_R4.sendMap(id, MapWrapper_v1_20_R4.this.content, player); } else { - MapSender_v1_20_R1.addToQueue(id, MapWrapper_v1_20_R1.this.content, player); + MapSender_v1_20_R4.addToQueue(id, MapWrapper_v1_20_R4.this.content, player); } } @Override public void cancelSend() { for (int s : viewers.values()) { - MapSender_v1_20_R1.cancelID(s); + MapSender_v1_20_R4.cancelID(s); } } @@ -129,8 +132,8 @@ public class MapWrapper_v1_20_R1 extends MapWrapper { } CraftPlayer craftPlayer = (CraftPlayer) player; - int windowId = craftPlayer.getHandle().bQ.j; //inventoryMenu containerId - int stateId = craftPlayer.getHandle().bQ.j(); //inventoryMenu getStateId() + int windowId = craftPlayer.getHandle().cb.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().cb.j(); //inventoryMenu getStateId() ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); @@ -186,7 +189,7 @@ public class MapWrapper_v1_20_R1 extends MapWrapper { ItemFrame frame = getItemFrameById(player.getWorld(), entityId); if (frame != null) { frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); - frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_20_R1.this)); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_20_R4.this)); } sendItemFramePacket(player, entityId, stack, getMapId(player)); @@ -217,10 +220,11 @@ public class MapWrapper_v1_20_R1 extends MapWrapper { private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); - nmsStack.w().a("map", mapId); //getOrCreateTag putInt + MapId mapId1 = new MapId(mapId); + nmsStack.b(DataComponents.B, mapId1); //set - List> list = new ArrayList<>(); - DataWatcher.b dataWatcherItem = DataWatcher.b.a(EntityItemFrame.g, nmsStack); + List> list = new ArrayList<>(); + DataWatcher.c dataWatcherItem = DataWatcher.c.a(EntityItemFrame.g, nmsStack); list.add(dataWatcherItem); PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); @@ -228,7 +232,7 @@ public class MapWrapper_v1_20_R1 extends MapWrapper { } }; - public MapWrapper_v1_20_R1(ArrayImage image) { + public MapWrapper_v1_20_R4(ArrayImage image) { super(image); } diff --git a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R4.java similarity index 90% rename from NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java rename to NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R4.java index a07cde1..a774625 100644 --- a/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java +++ b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R4.java @@ -27,8 +27,8 @@ import net.minecraft.world.EnumHand; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3D; import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; +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.util.Vector; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; @@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit; import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; -public class PacketListener_v1_20_R1 extends PacketListener { +public class PacketListener_v1_20_R4 extends PacketListener { @Override protected void injectPlayer(Player p) { ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { @@ -89,8 +89,8 @@ public class PacketListener_v1_20_R1 extends PacketListener { return false; }).get(1, TimeUnit.SECONDS)) return; } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { - int slot = packetPlayInSetCreativeSlot.a(); - ItemStack item = packetPlayInSetCreativeSlot.c(); + int slot = (int) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "b" : "a"); //slot, 1.20.5 = b, lower is a + ItemStack item = (ItemStack) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "e" : "d"); //item, 1.20.5 = e, lower is d boolean async = !plugin.getServer().isPrimaryThread(); CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); @@ -106,7 +106,7 @@ public class PacketListener_v1_20_R1 extends PacketListener { //The connection is private since 1.19.4 :| NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); - ChannelPipeline pipeline = networkManager.m.pipeline(); //connection channel + ChannelPipeline pipeline = networkManager.n.pipeline(); //connection channel pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); } @@ -114,7 +114,7 @@ public class PacketListener_v1_20_R1 extends PacketListener { public void removePlayer(Player p) { //The connection is private since 1.19.4 :| NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); - Channel channel = networkManager.m; //connection channel + Channel channel = networkManager.n; //connection channel channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); } diff --git a/pom.xml b/pom.xml index 1f55a66..7fb69ad 100644 --- a/pom.xml +++ b/pom.xml @@ -40,9 +40,9 @@ API Dist + NMS-v1_20_R4 NMS-v1_20_R3 NMS-v1_20_R2 - NMS-v1_20_R1 NMS-v1_19_R3 NMS-v1_18_R2 NMS-v1_17_R1 From ef49048ee169324c72bcf3b098b628d4c071a787 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Mon, 29 Apr 2024 16:50:10 +0200 Subject: [PATCH 36/52] Started with 1.20.5 support --- pom.xml | 2 +- .../mapreflectionapi/MapReflectionAPI.java | 2 +- .../mapreflectionapi/api/MapSender.java | 20 ++++++++++++++++++- .../listeners/PacketListener.java | 4 ++-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 8d7f65d..7ee27dc 100644 --- a/pom.xml +++ b/pom.xml @@ -161,7 +161,7 @@ org.spigotmc spigot-api - 1.20.4-R0.1-SNAPSHOT + 1.20.5-R0.1-SNAPSHOT provided diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java index 2049d7b..bc41d31 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java @@ -69,7 +69,7 @@ public class MapReflectionAPI extends JavaPlugin { getLogger().info("Made by © Copyright SBDevelopment 2023"); if (!supports(12)) { - getLogger().severe("MapReflectionAPI only supports Minecraft 1.12 - 1.19.4!"); + getLogger().severe("MapReflectionAPI only supports Minecraft 1.12 - 1.20.5!"); Bukkit.getPluginManager().disablePlugin(this); return; } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java index 2cb8844..cc35b8a 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java @@ -112,7 +112,25 @@ public class MapSender { final int id = -id0; Object packet; - if (supports(17)) { //1.17+ + if (supports(20, 4)) { //1.20.5+ + //TODO: Implement 1.20.5+ map sending + + Object updateData = ReflectionUtil.callConstructor(worldMapData, + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, + id, //ID + (byte) 0, //Scale, 0 = 1 block per pixel + false, //Show icons + new ReflectionUtil.CollectionParam<>(), //Icons + updateData + ); + } else if (supports(17)) { //1.17+ Object updateData = ReflectionUtil.callConstructor(worldMapData, content.minX, //X pos content.minY, //Y pos diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index b5c3f17..2804d44 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -140,8 +140,8 @@ public class PacketListener implements Listener { } else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) { Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet); - int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(19, 4) ? "a" : supports(13) ? "b" : "a"); //1.19.4 = a, 1.19.3 - 1.13 = b, 1.12 = a - Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(20, 2) ? "d" : supports(18) ? "c" : "getItemStack"); //1.20.2 = d, >= 1.18 = c, 1.17 = getItemStack + int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(20,4) ? "b" : supports(19, 4) ? "a" : supports(13) ? "b" : "a"); //1.20.4 - 1.19.4 = a, 1.19.3 - 1.13 and 1.20.5 = b, 1.12 = a + Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(20,4) ? "e" : supports(20, 2) ? "d" : supports(18) ? "c" : "getItemStack"); //1.20.5 = e, 1.20.2-1.20.4 = d, >= 1.18 = c, 1.17 = getItemStack ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack); boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); From f25c727a150180229c90f0f9bcd1642c1a459c79 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Mon, 29 Apr 2024 18:01:28 +0200 Subject: [PATCH 37/52] Added 1.20.5 support finished --- .../mapreflectionapi/api/MapSender.java | 6 +++--- .../mapreflectionapi/api/MapWrapper.java | 13 +++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java index cc35b8a..3d5ae64 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java @@ -113,8 +113,6 @@ public class MapSender { final int id = -id0; Object packet; if (supports(20, 4)) { //1.20.5+ - //TODO: Implement 1.20.5+ map sending - Object updateData = ReflectionUtil.callConstructor(worldMapData, content.minX, //X pos content.minY, //Y pos @@ -123,8 +121,10 @@ public class MapSender { content.array //Data ); + Object mapId = ReflectionUtil.callConstructor(getNMSClass("world.level.saveddata.maps", "MapId"), id); + packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, - id, //ID + mapId, //ID (byte) 0, //Scale, 0 = 1 block per pixel false, //Show icons new ReflectionUtil.CollectionParam<>(), //Icons diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index d943fa5..9c427e2 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -165,8 +165,8 @@ public class MapWrapper extends AbstractMapWrapper { String inventoryMenuName; if (supports(20)) { - //>= 1.20.2 = bR, 1.20(.1) = bQ - inventoryMenuName = supports(20, 2) ? "bR" : "bQ"; + //1.20.5 = cb, 1.20.2 - 1.20.4 = bR, 1.20(.1) = bQ + inventoryMenuName = supports(20, 4) ? "cb" : supports(20, 2) ? "bR" : "bQ"; } else if (supports(19)) { //1.19.4 = bO, >= 1.19.3 = bT inventoryMenuName = supports(19, 3) ? "bO" : "bT"; @@ -286,7 +286,12 @@ public class MapWrapper extends AbstractMapWrapper { Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack); - if (supports(13)) { + //1.20.5 uses new NBT compound system + if (supports(20, 4)) { + Object mapIdComponent = ReflectionUtil.getDeclaredField(getNMSClass("core.component", "DataComponents"), "B"); + Object mapId1 = ReflectionUtil.callConstructor(getNMSClass("world.level.saveddata.maps", "MapId"), mapId); + ReflectionUtil.callMethod(nmsStack, "b", mapIdComponent, mapId1); + } else if (supports(13)) { String nbtObjectName; if (supports(20)) { //1.20 nbtObjectName = "w"; @@ -328,7 +333,7 @@ public class MapWrapper extends AbstractMapWrapper { Object packet; if (supports(19, 3)) { //1.19.3 - Class dataWatcherRecordClass = getNMSClass("network.syncher", "DataWatcher$b"); + Class dataWatcherRecordClass = getNMSClass("network.syncher", supports(20, 4) ? "DataWatcher$c" : "DataWatcher$b"); //1.20.5 = c, lower is b // Sadly not possible to use ReflectionUtil (in its current state), because of the Object parameter Object dataWatcherItem; try { From cba7cbf6e50163bf5b7780db1a754fe48b944fd4 Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Mon, 29 Apr 2024 18:11:13 +0200 Subject: [PATCH 38/52] Fixed compilation --- .github/workflows/maven.yml | 4 ++-- .idea/misc.xml | 3 +-- pom.xml | 19 +++++++++++++------ .../mapreflectionapi/utils/XMaterial.java | 8 -------- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index c9caadf..d550d86 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -10,10 +10,10 @@ jobs: steps: - uses: actions/checkout@v1 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 21 - name: Build with Maven run: mvn -B package --file pom.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index f0021fd..5cacde7 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -29,9 +29,8 @@ - - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7ee27dc..de917ad 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ tech.sbdevelopment MapReflectionAPI - 1.6.3 + 1.6.4 jar MapReflectionAPI @@ -48,14 +48,14 @@ org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.13.0 11 org.projectlombok lombok - 1.18.30 + 1.18.32 @@ -63,7 +63,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.3.0 + 3.5.3 package @@ -95,6 +95,13 @@ ${maven.lombok.delombok-target} false + + + org.projectlombok + lombok + 1.18.32 + + generate-sources @@ -108,7 +115,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.4.0 + 3.6.3 11 ${maven.lombok.delombok-target} @@ -167,7 +174,7 @@ org.projectlombok lombok - 1.18.30 + 1.18.32 provided diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java index 95ecaa0..9a43afe 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/XMaterial.java @@ -30,7 +30,6 @@ import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.SpawnEggMeta; -import org.bukkit.potion.Potion; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -1908,13 +1907,6 @@ public enum XMaterial { } } - // Potions used the items data value to store - // information about the type of potion in 1.8 - if (!supports(9) && material.endsWith("ION")) { - // There's also 16000+ data value technique, but this is more reliable. - return Potion.fromItemStack(item).isSplash() ? SPLASH_POTION : POTION; - } - // Refer to the enum for info. // Currently, these are the only materials with a non-zero data value // that has been renamed after the flattening update. From 39dcfc8b31a2d6936ce9abca6d680fb863566058 Mon Sep 17 00:00:00 2001 From: SBDeveloper Date: Sun, 30 Jun 2024 20:25:39 +0200 Subject: [PATCH 39/52] Added 1.21 to legacy NMS branch (untested) --- .idea/encodings.xml | 2 + NMS-v1_21_R1/pom.xml | 72 ++++++ .../nms/MapSender_v1_21_R1.java | 139 ++++++++++ .../nms/MapWrapper_v1_21_R1.java | 243 ++++++++++++++++++ .../nms/PacketListener_v1_21_R1.java | 126 +++++++++ pom.xml | 1 + 6 files changed, 583 insertions(+) create mode 100644 NMS-v1_21_R1/pom.xml create mode 100644 NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java create mode 100644 NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java create mode 100644 NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java diff --git a/.idea/encodings.xml b/.idea/encodings.xml index e45f72b..387fa9e 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -29,6 +29,8 @@ + + diff --git a/NMS-v1_21_R1/pom.xml b/NMS-v1_21_R1/pom.xml new file mode 100644 index 0000000..598c369 --- /dev/null +++ b/NMS-v1_21_R1/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_21_R1 + + + 1.21-R0.1-SNAPSHOT + 21 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java new file mode 100644 index 0000000..c349201 --- /dev/null +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java @@ -0,0 +1,139 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.MapId; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_21_R1} sends the Map packets to players. + */ +public class MapSender_v1_21_R1 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_21_R1() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final MapId id = new MapId(-id0); + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.b updateData = new WorldMap.b( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + PacketPlayOutMap packet = new PacketPlayOutMap( + id, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().c.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java new file mode 100644 index 0000000..bae1bea --- /dev/null +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java @@ -0,0 +1,243 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.resources.MinecraftKey; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import net.minecraft.world.item.Items; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_21_R1 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_21_R1.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_21_R1.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_21_R1.sendMap(id, MapWrapper_v1_21_R1.this.content, player); + } else { + MapSender_v1_21_R1.addToQueue(id, MapWrapper_v1_21_R1.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_21_R1.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().cc.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().cc.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().c.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_21_R1.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + nmsStack.a().a(BuiltInRegistries.aq.a(MinecraftKey.a("minecraft:map_id")), mapId); //getOrCreateTag putInt + + List> list = new ArrayList<>(); + DataWatcher.c dataWatcherItem = DataWatcher.c.a(EntityItemFrame.f, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().c.a(packet); + } + }; + + public MapWrapper_v1_21_R1(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java new file mode 100644 index 0000000..e722e1c --- /dev/null +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_21_R1 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = packetPlayInSetCreativeSlot.b(); + ItemStack item = packetPlayInSetCreativeSlot.e(); + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + ChannelPipeline pipeline = networkManager.n.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + Channel channel = networkManager.n; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.c, vec3dObj.d, vec3dObj.e); //x, y, z + } +} diff --git a/pom.xml b/pom.xml index 1f55a66..1c36662 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ API Dist + NMS-v1_21_R1 NMS-v1_20_R3 NMS-v1_20_R2 NMS-v1_20_R1 From 445dc1d2e9f3b7241b61747038eea2bc4aca8971 Mon Sep 17 00:00:00 2001 From: SBDeveloper Date: Sun, 30 Jun 2024 20:26:53 +0200 Subject: [PATCH 40/52] Added 1.21 support (untested) --- .idea/misc.xml | 3 +- .idea/vcs.xml | 2 +- pom.xml | 27 +-- .../mapreflectionapi/api/MapSender.java | 30 ++-- .../mapreflectionapi/api/MapWrapper.java | 22 ++- .../listeners/PacketListener.java | 4 +- .../mapreflectionapi/utils/MainUtil.java | 3 + .../mapreflectionapi/utils/XMaterial.java | 159 +++++------------- 8 files changed, 104 insertions(+), 146 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index f0021fd..285bc57 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -29,9 +29,8 @@ -

- * Supports {@link String#contains} {@code CONTAINS:NAME} and Regular Expression {@code REGEX:PATTERN} formats. - *

- * Example: - *

-     *     XMaterial material = {@link #matchXMaterial(ItemStack)};
-     *     if (material.isOneOf(plugin.getConfig().getStringList("disabled-items")) return;
-     * 
- *
- * {@code CONTAINS} Examples: - *
-     *     {@code "CONTAINS:CHEST" -> CHEST, ENDERCHEST, TRAPPED_CHEST -> true}
-     *     {@code "cOnTaINS:dYe" -> GREEN_DYE, YELLOW_DYE, BLUE_DYE, INK_SACK -> true}
-     * 
- *

- * {@code REGEX} Examples - *

-     *     {@code "REGEX:^.+_.+_.+$" -> Every Material with 3 underlines or more: SHULKER_SPAWN_EGG, SILVERFISH_SPAWN_EGG, SKELETON_HORSE_SPAWN_EGG}
-     *     {@code "REGEX:^.{1,3}$" -> Material names that have 3 letters only: BED, MAP, AIR}
-     * 
- *

- * The reason that there are tags for {@code CONTAINS} and {@code REGEX} is for the performance. - * Although RegEx patterns are cached in this method, - * please avoid using the {@code REGEX} tag if you can use the {@code CONTAINS} tag instead. - * It'll have a huge impact on performance. - * Please avoid using {@code (capturing groups)} there's no use for them in this case. - * If you want to use groups, use {@code (?: non-capturing groups)}. It's faster. - *

- * Want to learn RegEx? You can mess around in RegExr website. - * - * @param materials the material names to check base material on. - * @return true if one of the given material names is similar to the base material. - * @since 3.1.1 - * @deprecated Use XTag.stringMatcher() instead. - */ - @Deprecated - public boolean isOneOf(@Nullable Collection materials) { - if (materials == null || materials.isEmpty()) return false; - String name = this.name(); - - for (String comp : materials) { - String checker = comp.toUpperCase(Locale.ENGLISH); - if (checker.startsWith("CONTAINS:")) { - comp = format(checker.substring(9)); - if (name.contains(comp)) return true; - continue; - } - if (checker.startsWith("REGEX:")) { - comp = comp.substring(6); - Pattern pattern = CACHED_REGEX.getIfPresent(comp); - if (pattern == null) { - try { - pattern = Pattern.compile(comp); - CACHED_REGEX.put(comp, pattern); - } catch (PatternSyntaxException ex) { - ex.printStackTrace(); - } - } - if (pattern != null && pattern.matcher(name).matches()) return true; - continue; - } - - // Direct Object Equals - Optional xMat = matchXMaterial(comp); - if (xMat.isPresent() && xMat.get() == this) return true; - } - return false; - } - /** * Sets the {@link Material} (and data value on older versions) of an item. * Damageable materials will not have their durability changed. @@ -2209,7 +2134,6 @@ public enum XMaterial { * Spigot added material ID support back in 1.16+ * * @return the ID of the material or -1 if it's not a legacy material or the server doesn't support the material. - * @see #matchXMaterial(int, byte) * @since 2.2.0 */ @SuppressWarnings("deprecation") @@ -2379,6 +2303,7 @@ public enum XMaterial { * * @since 9.0.0 */ + @ApiStatus.Internal private static final class Data { /** * The current version of the server in the form of a major version. From 5f9cbe742576c3753ccf15ac6bc40a8352b82b55 Mon Sep 17 00:00:00 2001 From: SBDeveloper Date: Sun, 30 Jun 2024 20:33:07 +0200 Subject: [PATCH 41/52] Re-update based on missing commit --- .../mapreflectionapi/nms/MapSender_v1_20_R4.java | 2 -- .../mapreflectionapi/nms/MapSender_v1_21_R1.java | 6 ++++-- .../mapreflectionapi/nms/MapWrapper_v1_21_R1.java | 8 ++++---- .../mapreflectionapi/nms/PacketListener_v1_21_R1.java | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R4.java b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R4.java index c1c4956..dec1ac9 100644 --- a/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R4.java +++ b/NMS-v1_20_R4/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R4.java @@ -30,8 +30,6 @@ import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; import java.util.ArrayList; import java.util.List; -import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.supports; - /** * The {@link MapSender_v1_20_R4} sends the Map packets to players. */ diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java index c349201..8e4ba08 100644 --- a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R1.java @@ -108,7 +108,7 @@ public class MapSender_v1_21_R1 { return; } - final MapId id = new MapId(-id0); + final int id = -id0; Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { try { WorldMap.b updateData = new WorldMap.b( @@ -119,8 +119,10 @@ public class MapSender_v1_21_R1 { content.array //Data ); + MapId mapId = new MapId(id); + PacketPlayOutMap packet = new PacketPlayOutMap( - id, //ID + mapId, //ID (byte) 0, //Scale false, //Show icons new ArrayList<>(), //Icons diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java index bae1bea..453343d 100644 --- a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R1.java @@ -19,14 +19,13 @@ package tech.sbdevelopment.mapreflectionapi.nms; import net.minecraft.core.component.DataComponentType; -import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.component.DataComponents; import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; import net.minecraft.network.syncher.DataWatcher; -import net.minecraft.resources.MinecraftKey; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.decoration.EntityItemFrame; -import net.minecraft.world.item.Items; +import net.minecraft.world.level.saveddata.maps.MapId; import org.bukkit.*; import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer; @@ -221,7 +220,8 @@ public class MapWrapper_v1_21_R1 extends MapWrapper { private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); - nmsStack.a().a(BuiltInRegistries.aq.a(MinecraftKey.a("minecraft:map_id")), mapId); //getOrCreateTag putInt + MapId mapId1 = new MapId(mapId); + nmsStack.b(DataComponents.B, mapId1); //set List> list = new ArrayList<>(); DataWatcher.c dataWatcherItem = DataWatcher.c.a(EntityItemFrame.f, nmsStack); diff --git a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java index e722e1c..21411c5 100644 --- a/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java +++ b/NMS-v1_21_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R1.java @@ -89,8 +89,8 @@ public class PacketListener_v1_21_R1 extends PacketListener { return false; }).get(1, TimeUnit.SECONDS)) return; } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { - int slot = packetPlayInSetCreativeSlot.b(); - ItemStack item = packetPlayInSetCreativeSlot.e(); + int slot = (int) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "b" : "a"); //slot, 1.20.5 = b, lower is a + ItemStack item = (ItemStack) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "e" : "d"); //item, 1.20.5 = e, lower is d boolean async = !plugin.getServer().isPrimaryThread(); CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); From bd6a24a2426501ff1ed0b085a8dd9b6503642854 Mon Sep 17 00:00:00 2001 From: SBDeveloper Date: Sun, 30 Jun 2024 20:49:09 +0200 Subject: [PATCH 42/52] Bumped XSeries, moved to new reflection API of XSeries --- .idea/misc.xml | 2 +- pom.xml | 10 +- .../bukkit/common/map/MapColorPalette.java | 2 +- .../mapreflectionapi/MapReflectionAPI.java | 2 +- .../mapreflectionapi/api/MapManager.java | 5 +- .../mapreflectionapi/api/MapSender.java | 33 +- .../mapreflectionapi/api/MapWrapper.java | 8 +- .../listeners/PacketListener.java | 3 +- .../utils/ReflectionUtil.java | 18 + .../utils/ReflectionUtils.java | 566 ------------------ 10 files changed, 53 insertions(+), 596 deletions(-) delete mode 100644 src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 5cacde7..285bc57 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -30,7 +30,7 @@ - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index fe21f7f..005712b 100644 --- a/pom.xml +++ b/pom.xml @@ -81,6 +81,10 @@ org.bstats tech.sbdevelopment.mapreflectionapi.libs.bstats + + com.cryptomorin.xseries + tech.sbdevelopment.mapreflectionapi.libs.xseries + @@ -177,13 +181,17 @@ 1.18.34 provided - org.bstats bstats-bukkit 3.0.2 compile + + com.github.cryptomorin + XSeries + 11.2.0 + diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java b/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java index 3963b7b..f3ea19a 100644 --- a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java +++ b/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java @@ -27,7 +27,7 @@ import java.awt.*; import java.io.InputStream; import java.util.Arrays; -import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports; +import static com.cryptomorin.xseries.reflection.XReflection.supports; /** * Additional functionality on top of Bukkit's MapPalette diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java index bc41d31..0d76789 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java @@ -34,7 +34,7 @@ import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager; import java.util.logging.Level; -import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports; +import static com.cryptomorin.xseries.reflection.XReflection.supports; public class MapReflectionAPI extends JavaPlugin { private static MapReflectionAPI instance; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java index 60fbc15..c3c5c60 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java @@ -33,7 +33,8 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; -import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; +import static com.cryptomorin.xseries.reflection.XReflection.*; +import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle; /** * The {@link MapManager} manages all the maps. It also contains functions for wrapping. @@ -185,7 +186,7 @@ public class MapManager { * @return The found {@link ItemFrame}, or null */ public ItemFrame getItemFrameById(World world, int entityId) { - Object worldHandle = getHandle(world); + Object worldHandle = ReflectionUtil.getHandle(world); Object nmsEntity = ReflectionUtil.callMethod(worldHandle, supports(18) ? "a" : "getEntity", entityId); if (nmsEntity == null) return null; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java index 0caefd0..9e46c98 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java @@ -27,7 +27,8 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import java.util.ArrayList; import java.util.List; -import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; +import static com.cryptomorin.xseries.reflection.XReflection.*; +import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.sendPacket; /** * The {@link MapSender} sends the Map packets to players. @@ -111,6 +112,8 @@ public class MapSender { return; } + int id = -id0; + Object packet; if (supports(20, 4)) { //1.20.5+ Object updateData = ReflectionUtil.callConstructor(worldMapData, @@ -139,26 +142,16 @@ public class MapSender { content.array //Data ); - if (supports(21)) { //1.21+ - packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, - ReflectionUtil.callConstructor(mapId, -id0), //ID - (byte) 0, //Scale, 0 = 1 block per pixel - false, //Show icons - new ReflectionUtil.CollectionParam<>(), //Icons - updateData - ); - } else { - packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, - -id0, //ID - (byte) 0, //Scale, 0 = 1 block per pixel - false, //Show icons - new ReflectionUtil.CollectionParam<>(), //Icons - updateData - ); - } + packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, + id, //ID + (byte) 0, //Scale, 0 = 1 block per pixel + false, //Show icons + new ReflectionUtil.CollectionParam<>(), //Icons + updateData + ); } else if (supports(14)) { //1.16-1.14 packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, - -id0, //ID + id, //ID (byte) 0, //Scale, 0 = 1 block per pixel false, //Tracking position false, //Locked @@ -171,7 +164,7 @@ public class MapSender { ); } else { //1.13- packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, - -id0, //ID + id, //ID (byte) 0, //Scale, 0 = 1 block per pixel false, //??? new ReflectionUtil.CollectionParam<>(), //Icons diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index def2c14..32396b9 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -41,7 +41,9 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; -import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; +import static com.cryptomorin.xseries.reflection.XReflection.*; +import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle; +import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.sendPacket; /** * A {@link MapWrapper} wraps one image. @@ -207,7 +209,7 @@ public class MapWrapper extends AbstractMapWrapper { ); } - sendPacketSync(player, packet); + sendPacket(player, packet); } @Override @@ -371,7 +373,7 @@ public class MapWrapper extends AbstractMapWrapper { ReflectionUtil.setDeclaredField(packet, "b", list); } - sendPacketSync(player, packet); + sendPacket(player, packet); } }; } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index e7914fb..5cf1957 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -38,8 +38,9 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import java.util.concurrent.TimeUnit; +import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle; import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; -import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; +import static com.cryptomorin.xseries.reflection.XReflection.*; public class PacketListener implements Listener { private static final Class packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap"); diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java index ad81288..87f7e57 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java @@ -18,6 +18,7 @@ package tech.sbdevelopment.mapreflectionapi.utils; +import org.bukkit.World; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,6 +28,9 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; +import static com.cryptomorin.xseries.reflection.XReflection.getCraftClass; +import static com.cryptomorin.xseries.reflection.XReflection.getNMSClass; + public class ReflectionUtil { private static final Map> constructorCache = new HashMap<>(); private static final Map methodCache = new HashMap<>(); @@ -71,6 +75,20 @@ public class ReflectionUtil { .toArray(Class[]::new); } + @Nullable + public static Object getHandle(@NotNull World world) { + Class worldServer = getNMSClass("server.level", "WorldServer"); + Class craftWorld = getCraftClass("CraftWorld"); + try { + Method m = craftWorld.getMethod("getHandle", worldServer); + m.setAccessible(true); + return m.invoke(null, world); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { + ex.printStackTrace(); + return null; + } + } + @Nullable public static Class getClass(@NotNull String name) { try { diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java deleted file mode 100644 index 912a2c4..0000000 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java +++ /dev/null @@ -1,566 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2023 Crypto Morin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR - * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE - * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package tech.sbdevelopment.mapreflectionapi.utils; - -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.entity.Player; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.util.Arrays; -import java.util.Objects; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * ReflectionUtils - Reflection handler for NMS and CraftBukkit.
- * Caches the packet related methods and is asynchronous. - *

- * This class does not handle null checks as most of the requests are from the - * other utility classes that already handle null checks. - *

- * Clientbound Packets are considered fake - * updates to the client without changing the actual data. Since all the data is handled - * by the server. - *

- * A useful resource used to compare mappings is Mini's Mapping Viewer - * - * @author Crypto Morin - * @version 7.1.0.0.1 - */ -public final class ReflectionUtils { - /** - * We use reflection mainly to avoid writing a new class for version barrier. - * The version barrier is for NMS that uses the Minecraft version as the main package name. - *

- * E.g. EntityPlayer in 1.15 is in the class {@code net.minecraft.server.v1_15_R1} - * but in 1.14 it's in {@code net.minecraft.server.v1_14_R1} - * In order to maintain cross-version compatibility we cannot import these classes. - *

- * Performance is not a concern for these specific statically initialized values. - *

- * Versions Legacy - */ - public static final String NMS_VERSION; - - static { // This needs to be right below VERSION because of initialization order. - // This package loop is used to avoid implementation-dependant strings like Bukkit.getVersion() or Bukkit.getBukkitVersion() - // which allows easier testing as well. - String found = null; - for (Package pack : Package.getPackages()) { - String name = pack.getName(); - - // .v because there are other packages. - if (name.startsWith("org.bukkit.craftbukkit.v")) { - found = pack.getName().split("\\.")[3]; - - // Just a final guard to make sure it finds this important class. - // As a protection for forge+bukkit implementation that tend to mix versions. - // The real CraftPlayer should exist in the package. - // Note: Doesn't seem to function properly. Will need to separate the version - // handler for NMS and CraftBukkit for softwares like catmc. - try { - Class.forName("org.bukkit.craftbukkit." + found + ".entity.CraftPlayer"); - break; - } catch (ClassNotFoundException e) { - found = null; - } - } - } - if (found == null) - throw new IllegalArgumentException("Failed to parse server version. Could not find any package starting with name: 'org.bukkit.craftbukkit.v'"); - NMS_VERSION = found; - } - - /** - * The raw minor version number. - * E.g. {@code v1_17_R1} to {@code 17} - * - * @see #supports(int) - * @since 4.0.0 - */ - public static final int MINOR_NUMBER; - /** - * The raw patch version number. Refers to the major.minor.patch version scheme. - * E.g. - *

    - *
  • {@code v1.20.4} to {@code 4}
  • - *
  • {@code v1.18.2} to {@code 2}
  • - *
  • {@code v1.19.1} to {@code 1}
  • - *
- *

- * I'd not recommend developers to support individual patches at all. You should always support the latest patch. - * For example, between v1.14.0, v1.14.1, v1.14.2, v1.14.3 and v1.14.4 you should only support v1.14.4 - *

- * This can be used to warn server owners when your plugin will break on older patches. - * - * @see #supportsPatch(int) - * @since 7.0.0 - */ - public static final int PATCH_NUMBER; - - static { - String[] split = NMS_VERSION.substring(1).split("_"); - if (split.length < 1) { - throw new IllegalStateException("Version number division error: " + Arrays.toString(split) + ' ' + getVersionInformation()); - } - - String minorVer = split[1]; - try { - MINOR_NUMBER = Integer.parseInt(minorVer); - if (MINOR_NUMBER < 0) - throw new IllegalStateException("Negative minor number? " + minorVer + ' ' + getVersionInformation()); - } catch (Throwable ex) { - throw new RuntimeException("Failed to parse minor number: " + minorVer + ' ' + getVersionInformation(), ex); - } - - // Bukkit.getBukkitVersion() = "1.12.2-R0.1-SNAPSHOT" - Matcher bukkitVer = Pattern.compile("^\\d+\\.\\d+\\.(\\d+)").matcher(Bukkit.getBukkitVersion()); - if (bukkitVer.find()) { // matches() won't work, we just want to match the start using "^" - try { - // group(0) gives the whole matched string, we just want the captured group. - PATCH_NUMBER = Integer.parseInt(bukkitVer.group(1)); - } catch (Throwable ex) { - throw new RuntimeException("Failed to parse minor number: " + bukkitVer + ' ' + getVersionInformation(), ex); - } - } else { - // 1.8-R0.1-SNAPSHOT - PATCH_NUMBER = 0; - } - } - - /** - * Gets the full version information of the server. Useful for including in errors. - * - * @since 7.0.0 - */ - public static String getVersionInformation() { - return "(NMS: " + NMS_VERSION + " | " + - "Minecraft: " + Bukkit.getVersion() + " | " + - "Bukkit: " + Bukkit.getBukkitVersion() + ')'; - } - - /** - * Gets the latest known patch number of the given minor version. - * For example: 1.14 -> 4, 1.17 -> 10 - * The latest version is expected to get newer patches, so make sure to account for unexpected results. - * - * @param minorVersion the minor version to get the patch number of. - * @return the patch number of the given minor version if recognized, otherwise null. - * @since 7.0.0 - */ - public static Integer getLatestPatchNumberOf(int minorVersion) { - if (minorVersion <= 0) throw new IllegalArgumentException("Minor version must be positive: " + minorVersion); - - // https://minecraft.wiki/w/Java_Edition_version_history - // There are many ways to do this, but this is more visually appealing. - int[] patches = { - /* 1 */ 1, - /* 2 */ 5, - /* 3 */ 2, - /* 4 */ 7, - /* 5 */ 2, - /* 6 */ 4, - /* 7 */ 10, - /* 8 */ 8, // I don't think they released a server version for 1.8.9 - /* 9 */ 4, - - /* 10 */ 2,// ,_ _ _, - /* 11 */ 2,// \o-o/ - /* 12 */ 2,// ,(.-.), - /* 13 */ 2,// _/ |) (| \_ - /* 14 */ 4,// /\=-=/\ - /* 15 */ 2,// ,| \=/ |, - /* 16 */ 5,// _/ \ | / \_ - /* 17 */ 1,// \_!_/ - /* 18 */ 2, - /* 19 */ 4, - /* 20 */ 4, - }; - - if (minorVersion > patches.length) return null; - return patches[minorVersion - 1]; - } - - /** - * Mojang remapped their NMS in 1.17: Spigot Thread - */ - public static final String - CRAFTBUKKIT_PACKAGE = "org.bukkit.craftbukkit." + NMS_VERSION + '.', - NMS_PACKAGE = v(17, "net.minecraft.").orElse("net.minecraft.server." + NMS_VERSION + '.'); - /** - * A nullable public accessible field only available in {@code EntityPlayer}. - * This can be null if the player is offline. - */ - private static final MethodHandle PLAYER_CONNECTION; - /** - * Responsible for getting the NMS handler {@code EntityPlayer} object for the player. - * {@code CraftPlayer} is simply a wrapper for {@code EntityPlayer}. - * Used mainly for handling packet related operations. - *

- * This is also where the famous player {@code ping} field comes from! - */ - private static final MethodHandle GET_HANDLE; - /** - * Responsible for getting the NMS handler {@code WorldServer} object for the world. - * {@code CraftWorld} is simply a wrapper for {@code WorldServer}. - */ - private static final MethodHandle GET_HANDLE_WORLD; - /** - * Sends a packet to the player's client through a {@code NetworkManager} which - * is where {@code ProtocolLib} controls packets by injecting channels! - */ - private static final MethodHandle SEND_PACKET; - - static { - Class entityPlayer = getNMSClass("server.level", "EntityPlayer"); - Class worldServer = getNMSClass("server.level", "WorldServer"); - Class craftPlayer = getCraftClass("entity.CraftPlayer"); - Class craftWorld = getCraftClass("CraftWorld"); - Class playerConnection = getNMSClass("server.network", "PlayerConnection"); - Class playerCommonConnection; - if (supports(20) && supportsPatch(2)) { - // The packet send method has been abstracted from ServerGamePacketListenerImpl to ServerCommonPacketListenerImpl in 1.20.2 - playerCommonConnection = getNMSClass("server.network", "ServerCommonPacketListenerImpl"); - } else { - playerCommonConnection = playerConnection; - } - - MethodHandles.Lookup lookup = MethodHandles.lookup(); - MethodHandle sendPacket = null, getHandle = null, getHandleWorld = null, connection = null; - - try { - connection = lookup.findGetter(entityPlayer, - v(20, "c").v(17, "b").orElse("playerConnection"), playerConnection); - getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer)); - getHandleWorld = lookup.findVirtual(craftWorld, "getHandle", MethodType.methodType(worldServer)); - sendPacket = lookup.findVirtual(playerCommonConnection, - v(20, 2, "b").v(18, "a").orElse("sendPacket"), - MethodType.methodType(void.class, getNMSClass("network.protocol", "Packet"))); - } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); - } - - PLAYER_CONNECTION = connection; - SEND_PACKET = sendPacket; - GET_HANDLE = getHandle; - GET_HANDLE_WORLD = getHandleWorld; - } - - private ReflectionUtils() { - } - - /** - * Gives the {@code handle} object if the server version is equal or greater than the given version. - * This method is purely for readability and should be always used with {@link VersionHandler#orElse(Object)}. - * - * @see #v(int, int, Object) - * @see VersionHandler#orElse(Object) - * @since 5.0.0 - */ - public static VersionHandler v(int version, T handle) { - return new VersionHandler<>(version, handle); - } - - /** - * Overload for {@link #v(int, T)} that supports patch versions - * - * @since 9.5.0 - */ - public static VersionHandler v(int version, int patch, T handle) { - return new VersionHandler<>(version, patch, handle); - } - - public static CallableVersionHandler v(int version, Callable handle) { - return new CallableVersionHandler<>(version, handle); - } - - /** - * Checks whether the server version is equal or greater than the given version. - * - * @param minorNumber the version to compare the server version with. - * @return true if the version is equal or newer, otherwise false. - * @see #MINOR_NUMBER - * @since 4.0.0 - */ - public static boolean supports(int minorNumber) { - return MINOR_NUMBER >= minorNumber; - } - - /** - * Checks whether the server version is equal or greater than the given version. - * - * @param minorNumber the minor version to compare the server version with. - * @param patchNumber the patch number to compare the server version with. - * @return true if the version is equal or newer, otherwise false. - * @see #MINOR_NUMBER - * @see #PATCH_NUMBER - * @since 7.1.0 - */ - public static boolean supports(int minorNumber, int patchNumber) { - return (MINOR_NUMBER == minorNumber && supportsPatch(patchNumber)) || MINOR_NUMBER > minorNumber; - } - - /** - * Checks whether the server version is equal or greater than the given version. - * - * @param patchNumber the version to compare the server version with. - * @return true if the version is equal or newer, otherwise false. - * @see #PATCH_NUMBER - * @since 7.0.0 - */ - public static boolean supportsPatch(int patchNumber) { - return PATCH_NUMBER >= patchNumber; - } - - /** - * Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility. - * - * @param packageName the 1.17+ package name of this class. - * @param name the name of the class. - * @return the NMS class or null if not found. - * @since 4.0.0 - */ - @Nullable - public static Class getNMSClass(@Nullable String packageName, @Nonnull String name) { - if (packageName != null && supports(17)) name = packageName + '.' + name; - - try { - return Class.forName(NMS_PACKAGE + name); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - return null; - } - } - - /** - * Get a NMS {@link #NMS_PACKAGE} class. - * - * @param name the name of the class. - * @return the NMS class or null if not found. - * @since 1.0.0 - */ - @Nullable - public static Class getNMSClass(@Nonnull String name) { - return getNMSClass(null, name); - } - - /** - * Sends a packet to the player asynchronously if they're online. - * Packets are thread-safe. - * - * @param player the player to send the packet to. - * @param packets the packets to send. - * @return the async thread handling the packet. - * @see #sendPacketSync(Player, Object...) - * @since 1.0.0 - */ - @Nonnull - public static CompletableFuture sendPacket(@Nonnull Player player, @Nonnull Object... packets) { - return CompletableFuture.runAsync(() -> sendPacketSync(player, packets)) - .exceptionally(ex -> { - ex.printStackTrace(); - return null; - }); - } - - /** - * Sends a packet to the player synchronously if they're online. - * - * @param player the player to send the packet to. - * @param packets the packets to send. - * @see #sendPacket(Player, Object...) - * @since 2.0.0 - */ - public static void sendPacketSync(@Nonnull Player player, @Nonnull Object... packets) { - try { - Object handle = GET_HANDLE.invoke(player); - Object connection = PLAYER_CONNECTION.invoke(handle); - - // Checking if the connection is not null is enough. There is no need to check if the player is online. - if (connection != null) { - for (Object packet : packets) SEND_PACKET.invoke(connection, packet); - } - } catch (Throwable throwable) { - throwable.printStackTrace(); - } - } - - @Nullable - public static Object getHandle(@Nonnull Player player) { - Objects.requireNonNull(player, "Cannot get handle of null player"); - try { - return GET_HANDLE.invoke(player); - } catch (Throwable throwable) { - throwable.printStackTrace(); - return null; - } - } - - @Nullable - public static Object getHandle(@Nonnull World world) { - Objects.requireNonNull(world, "Cannot get handle of null world"); - try { - return GET_HANDLE_WORLD.invoke(world); - } catch (Throwable throwable) { - throwable.printStackTrace(); - return null; - } - } - - @Nullable - public static Object getConnection(@Nonnull Player player) { - Objects.requireNonNull(player, "Cannot get connection of null player"); - try { - Object handle = GET_HANDLE.invoke(player); - return PLAYER_CONNECTION.invoke(handle); - } catch (Throwable throwable) { - throwable.printStackTrace(); - return null; - } - } - - /** - * Get a CraftBukkit (org.bukkit.craftbukkit) class. - * - * @param name the name of the class to load. - * @return the CraftBukkit class or null if not found. - * @since 1.0.0 - */ - @Nullable - public static Class getCraftClass(@Nonnull String name) { - try { - return Class.forName(CRAFTBUKKIT_PACKAGE + name); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - return null; - } - } - - /** - * @deprecated Use {@link #toArrayClass(Class)} instead. - */ - @Deprecated - public static Class getArrayClass(String clazz, boolean nms) { - clazz = "[L" + (nms ? NMS_PACKAGE : CRAFTBUKKIT_PACKAGE) + clazz + ';'; - try { - return Class.forName(clazz); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - return null; - } - } - - /** - * Gives an array version of a class. For example if you wanted {@code EntityPlayer[]} you'd use: - *

{@code
-     *     Class EntityPlayer = ReflectionUtils.getNMSClass("...", "EntityPlayer");
-     *     Class EntityPlayerArray = ReflectionUtils.toArrayClass(EntityPlayer);
-     * }
- * - * @param clazz the class to get the array version of. You could use for multi-dimensions arrays too. - */ - public static Class toArrayClass(Class clazz) { - try { - return Class.forName("[L" + clazz.getName() + ';'); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - return null; - } - } - - public static final class VersionHandler { - private int version, patch; - private T handle; - - private VersionHandler(int version, T handle) { - this(version, 0, handle); - } - - private VersionHandler(int version, int patch, T handle) { - if (supports(version) && supportsPatch(patch)) { - this.version = version; - this.patch = patch; - this.handle = handle; - } - } - - public VersionHandler v(int version, T handle) { - return v(version, 0, handle); - } - - public VersionHandler v(int version, int patch, T handle) { - if (version == this.version && patch == this.patch) - throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version + '.' + patch); - if (version > this.version && supports(version) && patch >= this.patch && supportsPatch(patch)) { - this.version = version; - this.patch = patch; - this.handle = handle; - } - return this; - } - - /** - * If none of the previous version checks matched, it'll return this object. - */ - public T orElse(T handle) { - return this.version == 0 ? handle : this.handle; - } - } - - public static final class CallableVersionHandler { - private int version; - private Callable handle; - - private CallableVersionHandler(int version, Callable handle) { - if (supports(version)) { - this.version = version; - this.handle = handle; - } - } - - public CallableVersionHandler v(int version, Callable handle) { - if (version == this.version) - throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version); - if (version > this.version && supports(version)) { - this.version = version; - this.handle = handle; - } - return this; - } - - public T orElse(Callable handle) { - try { - return (this.version == 0 ? handle : this.handle).call(); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - } -} \ No newline at end of file From 3f382583a796e069822387386347d940c8de180d Mon Sep 17 00:00:00 2001 From: SBDeveloper Date: Sun, 30 Jun 2024 22:37:24 +0200 Subject: [PATCH 43/52] Fixed all stupid NMS changes for 1.20.6+ --- .../mapreflectionapi/api/MapManager.java | 1 - .../mapreflectionapi/api/MapWrapper.java | 12 ++++- .../listeners/PacketListener.java | 52 +++++++++++++++---- .../utils/ReflectionUtil.java | 34 ++++++++---- 4 files changed, 75 insertions(+), 24 deletions(-) diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java index c3c5c60..8b05f72 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java @@ -34,7 +34,6 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import static com.cryptomorin.xseries.reflection.XReflection.*; -import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle; /** * The {@link MapManager} manages all the maps. It also contains functions for wrapping. diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index 32396b9..534728c 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -297,7 +297,17 @@ public class MapWrapper extends AbstractMapWrapper { if (supports(20, 4)) { Object mapIdComponent = ReflectionUtil.getDeclaredField(getNMSClass("core.component", "DataComponents"), "B"); Object mapId1 = ReflectionUtil.callConstructor(getNMSClass("world.level.saveddata.maps", "MapId"), mapId); - ReflectionUtil.callMethod(nmsStack, "b", mapIdComponent, mapId1); + + // Use generic reflection because of generics + // T ItemStack#b(DataComponentType dataComponentType, T t) + try { + Method m = nmsStack.getClass().getMethod("b", getNMSClass("core.component", "DataComponentType"), Object.class); + m.setAccessible(true); + m.invoke(nmsStack, mapIdComponent, mapId1); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { + ex.printStackTrace(); + return null; + } } else if (supports(13)) { String nbtObjectName; if (supports(20)) { //1.20 diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index 5cf1957..ec3daf2 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -30,14 +30,18 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; +import sun.misc.Unsafe; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.events.CreativeInventoryMapUpdateEvent; import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.concurrent.TimeUnit; +import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getConnection; import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle; import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; import static com.cryptomorin.xseries.reflection.XReflection.*; @@ -51,7 +55,7 @@ public class PacketListener implements Listener { private static final Class playerCommonConnection; static { - if (supports(20) && supportsPatch(2)) { + if (supports(20, 2)) { // The packet send method has been abstracted from ServerGamePacketListenerImpl to ServerCommonPacketListenerImpl in 1.20.2 playerCommonConnection = getNMSClass("server.network", "ServerCommonPacketListenerImpl"); } else { @@ -78,11 +82,34 @@ public class PacketListener implements Listener { if (packet.getClass().isAssignableFrom(packetPlayOutMapClass)) { Object packetPlayOutMap = packetPlayOutMapClass.cast(packet); - int id = (int) getDeclaredField(packetPlayOutMap, "a"); - if (id < 0) { - int newId = -id; - setDeclaredField(packetPlayOutMap, "a", newId); + int id; + boolean inv = false; + if (supports(20, 4)) { //1.20.4 uses MapId class and record classes (final fields...) + Object mapId = getDeclaredField(packetPlayOutMap, "b"); + id = (int) getDeclaredField(mapId, "c"); + + if (id < 0) { + Object newMapid = callConstructor(mapId.getClass(), -id); + Object c = getDeclaredField(packetPlayOutMap, "c"); + Object d = getDeclaredField(packetPlayOutMap, "d"); + Object e = getDeclaredField(packetPlayOutMap, "e"); + Object f = getDeclaredField(packetPlayOutMap, "f"); + + packetPlayOutMap = callConstructor(packetPlayOutMapClass, newMapid, c, d, e, f); + packet = packetPlayOutMap; + + inv = true; + } } else { + id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + setDeclaredField(packetPlayOutMap, "a", -id); + inv = true; + } + } + + if (!inv) { boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); MapCancelEvent event = new MapCancelEvent(player, id, async); if (MapReflectionAPI.getMapManager().isIdUsedBy(player, id)) event.setCancelled(true); @@ -102,13 +129,13 @@ public class PacketListener implements Listener { if (packet.getClass().isAssignableFrom(packetPlayInUseEntityClass)) { Object packetPlayInEntity = packetPlayInUseEntityClass.cast(packet); - int entityId = (int) getDeclaredField(packetPlayInEntity, "a"); + int entityId = (int) getDeclaredField(packetPlayInEntity, supports(20, 4) ? "b" : "a"); Enum actionEnum; Enum hand; Object pos; if (supports(17)) { - Object action = getDeclaredField(packetPlayInEntity, "b"); + Object action = getDeclaredField(packetPlayInEntity, supports(20, 4) ? "c" : "b"); actionEnum = (Enum) callDeclaredMethod(action, "a"); Class d = getNMSClass("network.protocol.game", "PacketPlayInUseEntity$d"); Class e = getNMSClass("network.protocol.game", "PacketPlayInUseEntity$e"); @@ -141,7 +168,12 @@ public class PacketListener implements Listener { } else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) { Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet); - int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(20, 4) ? "b" : supports(19, 4) ? "a" : supports(13) ? "b" : "a"); //1.20.4 - 1.19.4 = a, 1.19.3 - 1.13 and 1.20.5 = b, 1.12 = a + int slot; + if (supports(20, 4)) { //1.20.4+ uses short + slot = (short) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, "b"); + } else { //1.20.3 and lower uses int + slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(19, 4) ? "a" : supports(13) ? "b" : "a"); //1.20.4 - 1.19.4 = a, 1.19.3 - 1.13 and 1.20.5 = b, 1.12 = a + } Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(20, 4) ? "e" : supports(20, 2) ? "d" : supports(18) ? "c" : "getItemStack"); //1.20.5 = e, 1.20.2-1.20.4 = d, >= 1.18 = c, 1.17 = getItemStack ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack); @@ -167,9 +199,7 @@ public class PacketListener implements Listener { } private Channel getChannel(Player player) { - Object playerHandle = getHandle(player); - Object playerConnection = getDeclaredField(playerHandle, supports(20) ? "c" : supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection - Object networkManager = getDeclaredField(playerCommonConnection, playerConnection, supports(20, 2) ? "c" : supports(19, 4) ? "h" : supports(19) ? "b" : supports(17) ? "a" : "networkManager"); //1.20.2 = ServerCommonPacketListenerImpl#c, 1.20(.1) & 1.19.4 = h, >= 1.19.3 = b, 1.18 - 1.17 = a, 1.16 = networkManager + Object networkManager = getDeclaredField(playerCommonConnection, getConnection(player), supports(21) ? "e" : supports(20, 2) ? "c" : supports(19, 4) ? "h" : supports(19) ? "b" : supports(17) ? "a" : "networkManager"); //1.20.2 = ServerCommonPacketListenerImpl#c, 1.20(.1) & 1.19.4 = h, >= 1.19.3 = b, 1.18 - 1.17 = a, 1.16 = networkManager return (Channel) getDeclaredField(networkManager, supports(20, 2) ? "n" : supports(18) ? "m" : supports(17) ? "k" : "channel"); //1.20.2 = n, 1.20(.1), 1.19 & 1.18 = m, 1.17 = k, 1.16 = channel } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java index 87f7e57..a723ca9 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java @@ -35,6 +35,7 @@ public class ReflectionUtil { private static final Map> constructorCache = new HashMap<>(); private static final Map methodCache = new HashMap<>(); private static final Map fieldCache = new HashMap<>(); + private static final Class craftWorld = getCraftClass("CraftWorld"); /** * Helper class converted to {@link List} @@ -76,17 +77,8 @@ public class ReflectionUtil { } @Nullable - public static Object getHandle(@NotNull World world) { - Class worldServer = getNMSClass("server.level", "WorldServer"); - Class craftWorld = getCraftClass("CraftWorld"); - try { - Method m = craftWorld.getMethod("getHandle", worldServer); - m.setAccessible(true); - return m.invoke(null, world); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { - ex.printStackTrace(); - return null; - } + public static Object getHandle(@NotNull World world) {; + return callDeclaredMethod(craftWorld, world, "getHandle"); } @Nullable @@ -243,6 +235,26 @@ public class ReflectionUtil { } } + @Nullable + public static Object callDeclaredMethod(Class clazz, Object obj, String method, Object... params) { + try { + String cacheKey = "DeclaredMethod:" + clazz.getName() + ":" + method + ":" + Arrays.hashCode(params); + + if (methodCache.containsKey(cacheKey)) { + Method cachedMethod = methodCache.get(cacheKey); + return cachedMethod.invoke(obj, params); + } else { + Method m = clazz.getDeclaredMethod(method, toParamTypes(params)); + m.setAccessible(true); + methodCache.put(cacheKey, m); + return m.invoke(obj, params); + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { + ex.printStackTrace(); + return null; + } + } + public static boolean hasField(Object packet, String field) { try { String cacheKey = "HasField:" + packet.getClass().getName() + ":" + field; From c2074e450491ec8810aadfc3e253fe8e2282444d Mon Sep 17 00:00:00 2001 From: Stijn Bannink Date: Sun, 30 Jun 2024 22:56:04 +0200 Subject: [PATCH 44/52] Update README.md --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 98f254a..1f20539 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MapReflectionAPI -This plugin helps developer with displaying images on maps. It supports Spigot 1.12 - 1.20. +This plugin helps developer with displaying images on maps. It supports Spigot 1.12 - 1.21. ## Usage: @@ -18,7 +18,7 @@ First, include the API using Maven: tech.sbdevelopment MapReflectionAPI - 1.6 + 1.6.4 provided ``` @@ -162,7 +162,6 @@ public class MapRenderDistanceListener implements Listener { ## Credits: -This is a fork of [MapManager](https://github.com/InventivetalentDev/MapManager). It updates the API to 1.19 and uses -other dependencies. +This is a fork of [MapManager](https://github.com/InventivetalentDev/MapManager). It updates the API to the latest version of Minecraft and uses other dependencies. This plugin includes classes from BKCommonLib. Please checkout the README in that package for more information. From 59d4f84625c07a7c3b0ebb1c1598f0d2ab985a02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:54:17 +0000 Subject: [PATCH 45/52] Bump org.bstats:bstats-bukkit from 3.0.2 to 3.1.0 Bumps [org.bstats:bstats-bukkit](https://github.com/Bastian/bStats-Metrics) from 3.0.2 to 3.1.0. - [Release notes](https://github.com/Bastian/bStats-Metrics/releases) - [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.2...v3.1.0) --- updated-dependencies: - dependency-name: org.bstats:bstats-bukkit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 005712b..df0609f 100644 --- a/pom.xml +++ b/pom.xml @@ -184,7 +184,7 @@ org.bstats bstats-bukkit - 3.0.2 + 3.1.0 compile From 48762f97032704ee6657450184c0f9fb9dbd8b92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:50:30 +0000 Subject: [PATCH 46/52] Bump io.netty:netty-transport from 4.1.97.Final to 4.1.114.Final Bumps [io.netty:netty-transport](https://github.com/netty/netty) from 4.1.97.Final to 4.1.114.Final. - [Commits](https://github.com/netty/netty/compare/netty-4.1.97.Final...netty-4.1.114.Final) --- updated-dependencies: - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 005712b..0f90fa2 100644 --- a/pom.xml +++ b/pom.xml @@ -203,7 +203,7 @@ io.netty netty-transport - 4.1.97.Final + 4.1.114.Final provided From 7e392eb0ecc8a58b67584c15a629cce29300c751 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:50:37 +0000 Subject: [PATCH 47/52] Bump com.github.cryptomorin:XSeries from 11.2.0 to 11.3.0 Bumps [com.github.cryptomorin:XSeries](https://github.com/CryptoMorin/XSeries) from 11.2.0 to 11.3.0. - [Release notes](https://github.com/CryptoMorin/XSeries/releases) - [Commits](https://github.com/CryptoMorin/XSeries/compare/v11.2.0...v11.3.0) --- updated-dependencies: - dependency-name: com.github.cryptomorin:XSeries dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 005712b..2375634 100644 --- a/pom.xml +++ b/pom.xml @@ -190,7 +190,7 @@ com.github.cryptomorin XSeries - 11.2.0 + 11.3.0 From fa5e26ffd836c737d10839c901294ad5f3f02205 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:29:25 +0000 Subject: [PATCH 48/52] Bump org.apache.maven.plugins:maven-javadoc-plugin from 3.7.0 to 3.11.1 Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.7.0 to 3.11.1. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.7.0...maven-javadoc-plugin-3.11.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 005712b..9f3b9d9 100644 --- a/pom.xml +++ b/pom.xml @@ -119,7 +119,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.7.0 + 3.11.1 11 ${maven.lombok.delombok-target} From ef91b97d1f4b42b480cf3d9bcac2b03d214dd6c1 Mon Sep 17 00:00:00 2001 From: SBDeveloper Date: Thu, 7 Nov 2024 19:30:20 +0100 Subject: [PATCH 49/52] Legacy NMS to 1.21.2/3 --- .idea/encodings.xml | 2 + NMS-v1_20_R4/pom.xml | 2 +- NMS-v1_21_R2/pom.xml | 72 ++++++ .../nms/MapSender_v1_21_R2.java | 141 ++++++++++ .../nms/MapWrapper_v1_21_R2.java | 242 ++++++++++++++++++ .../nms/PacketListener_v1_21_R2.java | 126 +++++++++ pom.xml | 2 +- 7 files changed, 585 insertions(+), 2 deletions(-) create mode 100644 NMS-v1_21_R2/pom.xml create mode 100644 NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R2.java create mode 100644 NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R2.java create mode 100644 NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R2.java diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 7c0341c..242311a 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -33,6 +33,8 @@ + + diff --git a/NMS-v1_20_R4/pom.xml b/NMS-v1_20_R4/pom.xml index ce1abe3..f3f63e7 100644 --- a/NMS-v1_20_R4/pom.xml +++ b/NMS-v1_20_R4/pom.xml @@ -30,7 +30,7 @@ MapReflectionAPI-NMS-v1_20_R4 - 1.20.5-R0.1-SNAPSHOT + 1.20.6-R0.1-SNAPSHOT 21 diff --git a/NMS-v1_21_R2/pom.xml b/NMS-v1_21_R2/pom.xml new file mode 100644 index 0000000..005a5df --- /dev/null +++ b/NMS-v1_21_R2/pom.xml @@ -0,0 +1,72 @@ + + + + + + MapReflectionAPI + tech.sbdevelopment + ${revision} + + 4.0.0 + + MapReflectionAPI-NMS-v1_21_R2 + + + 1.21.3-R0.1-SNAPSHOT + 21 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${jdk.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + true + + + + + + + + org.bukkit + craftbukkit + ${NMSVersion} + provided + + + tech.sbdevelopment + MapReflectionAPI-API + ${revision} + provided + + + \ No newline at end of file diff --git a/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R2.java b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R2.java new file mode 100644 index 0000000..3620bc8 --- /dev/null +++ b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_21_R2.java @@ -0,0 +1,141 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.level.saveddata.maps.MapId; +import net.minecraft.world.level.saveddata.maps.WorldMap; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link MapSender_v1_21_R2} sends the Map packets to players. + */ +public class MapSender_v1_21_R2 { + private static final List sendQueue = new ArrayList<>(); + private static int senderID = -1; + + private MapSender_v1_21_R2() { + } + + /** + * Add a map to the send queue + * + * @param id The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void addToQueue(final int id, final ArrayImage content, final Player player) { + QueuedMap toSend = new QueuedMap(id, content, player); + if (sendQueue.contains(toSend)) return; + sendQueue.add(toSend); + + runSender(); + } + + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + + /** + * Run the sender task + */ + private static void runSender() { + if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty()) + return; + + senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> { + if (sendQueue.isEmpty()) return; + + for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) { + QueuedMap current = sendQueue.get(0); + if (current == null) return; + + sendMap(current.id, current.image, current.player); + + if (!sendQueue.isEmpty()) sendQueue.remove(0); + } + }, 0, 2); + } + + /** + * Send a map to a player + * + * @param id0 The ID of the map + * @param content The {@link ArrayImage} to view on the map + * @param player The {@link Player} to view for + */ + public static void sendMap(final int id0, final ArrayImage content, final Player player) { + if (player == null || !player.isOnline()) { + List toRemove = new ArrayList<>(); + for (QueuedMap qMap : sendQueue) { + if (qMap == null) continue; + + if (qMap.player == null || !qMap.player.isOnline()) { + toRemove.add(qMap); + } + } + Bukkit.getScheduler().cancelTask(senderID); + sendQueue.removeAll(toRemove); + + return; + } + + final int id = -id0; + Bukkit.getScheduler().runTaskAsynchronously(MapReflectionAPI.getInstance(), () -> { + try { + WorldMap.c updateData = new WorldMap.c( + content.minX, //X pos + content.minY, //Y pos + content.maxX, //X size (2nd X pos) + content.maxY, //Y size (2nd Y pos) + content.array //Data + ); + + MapId mapId = new MapId(id); + + PacketPlayOutMap packet = new PacketPlayOutMap( + mapId, //ID + (byte) 0, //Scale + false, //Show icons + new ArrayList<>(), //Icons + updateData + ); + + ((CraftPlayer) player).getHandle().f.a(packet); //connection send() + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + + record QueuedMap(int id, ArrayImage image, Player player) { + } +} diff --git a/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R2.java b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R2.java new file mode 100644 index 0000000..0dbc9ac --- /dev/null +++ b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_21_R2.java @@ -0,0 +1,242 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata; +import net.minecraft.network.protocol.game.PacketPlayOutSetSlot; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.decoration.EntityItemFrame; +import net.minecraft.world.level.saveddata.maps.MapId; +import org.bukkit.*; +import org.bukkit.craftbukkit.v1_21_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapController; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; +import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; + +import java.util.*; + +public class MapWrapper_v1_21_R2 extends MapWrapper { + protected MapController controller = new MapController() { + private final Map viewers = new HashMap<>(); + + @Override + public void addViewer(Player player) throws MapLimitExceededException { + if (!isViewing(player)) { + viewers.put(player.getUniqueId(), MapReflectionAPI.getMapManager().getNextFreeIdFor(player)); + } + } + + @Override + public void removeViewer(OfflinePlayer player) { + viewers.remove(player.getUniqueId()); + } + + @Override + public void clearViewers() { + for (UUID uuid : viewers.keySet()) { + viewers.remove(uuid); + } + } + + @Override + public boolean isViewing(OfflinePlayer player) { + if (player == null) return false; + return viewers.containsKey(player.getUniqueId()); + } + + @Override + public int getMapId(OfflinePlayer player) { + if (isViewing(player)) { + return viewers.get(player.getUniqueId()); + } + return -1; + } + + @Override + public void update(ArrayImage content) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper_v1_21_R2.this.content = duplicate.getContent(); + return; + } + + MapWrapper_v1_21_R2.this.content = content; + + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } + } + + @Override + public void sendContent(Player player) { + sendContent(player, false); + } + + @Override + public void sendContent(Player player, boolean withoutQueue) { + if (!isViewing(player)) return; + + int id = getMapId(player); + if (withoutQueue) { + MapSender_v1_21_R2.sendMap(id, MapWrapper_v1_21_R2.this.content, player); + } else { + MapSender_v1_21_R2.addToQueue(id, MapWrapper_v1_21_R2.this.content, player); + } + } + + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender_v1_21_R2.cancelID(s); + } + } + + @Override + public void showInInventory(Player player, int slot, boolean force) { + if (!isViewing(player)) return; + + if (player.getGameMode() == GameMode.CREATIVE && !force) return; + + if (slot < 9) { + slot += 36; + } else if (slot > 35 && slot != 45) { + slot = 8 - (slot - 36); + } + + CraftPlayer craftPlayer = (CraftPlayer) player; + int windowId = craftPlayer.getHandle().cc.j; //inventoryMenu containerId + int stateId = craftPlayer.getHandle().cc.j(); //inventoryMenu getStateId() + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + + PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(windowId, stateId, slot, nmsStack); + ((CraftPlayer) player).getHandle().f.a(packet); + } + + @Override + public void showInInventory(Player player, int slot) { + showInInventory(player, slot, false); + } + + @Override + public void showInHand(Player player, boolean force) { + if (player.getInventory().getItemInMainHand().getType() != Material.FILLED_MAP && !force) return; + showInInventory(player, player.getInventory().getHeldItemSlot(), force); + } + + @Override + public void showInHand(Player player) { + showInHand(player, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame) { + showInFrame(player, frame, false); + } + + @Override + public void showInFrame(Player player, ItemFrame frame, boolean force) { + if (frame.getItem().getType() != Material.FILLED_MAP && !force) return; + showInFrame(player, frame.getEntityId()); + } + + @Override + public void showInFrame(Player player, int entityId) { + showInFrame(player, entityId, null); + } + + @Override + public void showInFrame(Player player, int entityId, String debugInfo) { + if (!isViewing(player)) return; + + ItemStack stack = new ItemStack(Material.FILLED_MAP, 1); + if (debugInfo != null) { + ItemMeta itemMeta = stack.getItemMeta(); + itemMeta.setDisplayName(debugInfo); + stack.setItemMeta(itemMeta); + } + + Bukkit.getScheduler().runTask(MapReflectionAPI.getInstance(), () -> { + ItemFrame frame = getItemFrameById(player.getWorld(), entityId); + if (frame != null) { + frame.removeMetadata("MAP_WRAPPER_REF", MapReflectionAPI.getInstance()); + frame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapReflectionAPI.getInstance(), MapWrapper_v1_21_R2.this)); + } + + sendItemFramePacket(player, entityId, stack, getMapId(player)); + }); + } + + @Override + public void clearFrame(Player player, int entityId) { + + } + + @Override + public void clearFrame(Player player, ItemFrame frame) { + + } + + @Override + public ItemFrame getItemFrameById(World world, int entityId) { + CraftWorld craftWorld = (CraftWorld) world; + + Entity entity = craftWorld.getHandle().a(entityId); + if (entity == null) return null; + + if (entity instanceof ItemFrame) return (ItemFrame) entity; + + return null; + } + + private void sendItemFramePacket(Player player, int entityId, ItemStack stack, int mapId) { + net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(stack); + MapId mapId1 = new MapId(mapId); + nmsStack.b(DataComponents.L, mapId1); //set + + List> list = new ArrayList<>(); + DataWatcher.c dataWatcherItem = DataWatcher.c.a(EntityItemFrame.e, nmsStack); + list.add(dataWatcherItem); + PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata(entityId, list); + + ((CraftPlayer) player).getHandle().f.a(packet); + } + }; + + public MapWrapper_v1_21_R2(ArrayImage image) { + super(image); + } + + @Override + public MapController getController() { + return controller; + } +} diff --git a/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R2.java b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R2.java new file mode 100644 index 0000000..487ed32 --- /dev/null +++ b/NMS-v1_21_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_21_R2.java @@ -0,0 +1,126 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package tech.sbdevelopment.mapreflectionapi.nms; + +import io.netty.channel.*; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot; +import net.minecraft.network.protocol.game.PacketPlayInUseEntity; +import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.world.EnumHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3D; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.CreateInventoryMapUpdateEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; +import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; +import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; + +import java.util.concurrent.TimeUnit; + +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; + +public class PacketListener_v1_21_R2 extends PacketListener { + @Override + protected void injectPlayer(Player p) { + ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() { + @Override + //On send packet + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + if (packet instanceof PacketPlayOutMap packetPlayOutMap) { + int id = (int) getDeclaredField(packetPlayOutMap, "a"); + + if (id < 0) { + //It's one of our maps, invert ID and let through! + int newId = -id; + setDeclaredField(packetPlayOutMap, "a", newId); //mapId + } else { + boolean async = !plugin.getServer().isPrimaryThread(); + MapCancelEvent event = new MapCancelEvent(p, id, async); + if (MapReflectionAPI.getMapManager().isIdUsedBy(p, id)) event.setCancelled(true); + if (event.getHandlers().getRegisteredListeners().length > 0) + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) return; + } + } + + super.write(ctx, packet, promise); + } + + @Override + //On receive packet + public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception { + if (packet instanceof PacketPlayInUseEntity packetPlayInUseEntity) { + int entityId = (int) getDeclaredField(packetPlayInUseEntity, "a"); //entityId + Object action = getDeclaredField(packetPlayInUseEntity, "b"); //action + Enum actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type + EnumHand hand = hasField(action, "a") ? (EnumHand) getDeclaredField(action, "a") : null; //hand + Vec3D pos = hasField(action, "b") ? (Vec3D) getDeclaredField(action, "b") : null; //pos + + if (Bukkit.getScheduler().callSyncMethod(plugin, () -> { + boolean async = !plugin.getServer().isPrimaryThread(); + MapInteractEvent event = new MapInteractEvent(p, entityId, actionEnum.ordinal(), pos != null ? vec3DToVector(pos) : null, hand != null ? hand.ordinal() : 0, async); + if (event.getFrame() != null && event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + return event.isCancelled(); + } + return false; + }).get(1, TimeUnit.SECONDS)) return; + } else if (packet instanceof PacketPlayInSetCreativeSlot packetPlayInSetCreativeSlot) { + int slot = (int) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "b" : "a"); //slot, 1.20.5 = b, lower is a + ItemStack item = (ItemStack) getDeclaredField(packetPlayInSetCreativeSlot, supports(20, 4) ? "e" : "d"); //item, 1.20.5 = e, lower is d + + boolean async = !plugin.getServer().isPrimaryThread(); + CreateInventoryMapUpdateEvent event = new CreateInventoryMapUpdateEvent(p, slot, CraftItemStack.asBukkitCopy(item), async); + if (event.getMapWrapper() != null) { + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) return; + } + } + + super.channelRead(ctx, packet); + } + }; + + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + ChannelPipeline pipeline = networkManager.n.pipeline(); //connection channel + pipeline.addBefore("packet_handler", p.getName(), channelDuplexHandler); + } + + @Override + public void removePlayer(Player p) { + //The connection is private since 1.19.4 :| + NetworkManager networkManager = (NetworkManager) getField(((CraftPlayer) p).getHandle().c, "h"); + Channel channel = networkManager.n; //connection channel + channel.eventLoop().submit(() -> channel.pipeline().remove(p.getName())); + } + + @Override + protected Vector vec3DToVector(Object vec3d) { + if (!(vec3d instanceof Vec3D vec3dObj)) return new Vector(0, 0, 0); + return new Vector(vec3dObj.d, vec3dObj.e, vec3dObj.f); //x, y, z + } +} diff --git a/pom.xml b/pom.xml index 7ef8cf4..933e673 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ NMS-v1_20_R4 NMS-v1_20_R3 NMS-v1_20_R2 - NMS-v1_20_R1 + NMS-v1_21_R2 NMS-v1_19_R3 NMS-v1_18_R2 NMS-v1_17_R1 From e142050b7afa07afa2694a96ed394d9a710d082a Mon Sep 17 00:00:00 2001 From: SBDeveloper Date: Thu, 27 Mar 2025 19:54:31 +0100 Subject: [PATCH 50/52] Delete .github/workflows/maven.yml --- .github/workflows/maven.yml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .github/workflows/maven.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index d550d86..0000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Java CI - -on: [push] - -jobs: - build: - name: Build - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - - name: Set up JDK 21 - uses: actions/setup-java@v1 - with: - java-version: 21 - - - name: Build with Maven - run: mvn -B package --file pom.xml - - - run: mkdir -p target - - - uses: actions/upload-artifact@master - with: - name: MapReflectionAPI - path: target From aaecbf447dc37c7c16e006977e5f5827d9898a92 Mon Sep 17 00:00:00 2001 From: SBDeveloper Date: Thu, 27 Mar 2025 19:54:36 +0100 Subject: [PATCH 51/52] Delete .github/dependabot.yml --- .github/dependabot.yml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 4cae9d2..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: "maven" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "daily" \ No newline at end of file From 14a6c330d29f541138c72a55032042a600f36284 Mon Sep 17 00:00:00 2001 From: SBDeveloper Date: Thu, 27 Mar 2025 19:55:31 +0100 Subject: [PATCH 52/52] v1.6.6: 1.21.5 support (#50) Reviewed-on: https://git.sbdevelopment.tech/SBDevelopment/MapReflectionAPI/pulls/50 Co-authored-by: SBDeveloper Co-committed-by: SBDeveloper --- pom.xml | 16 ++++++++++------ .../mapreflectionapi/api/MapSender.java | 6 +++--- .../mapreflectionapi/api/MapWrapper.java | 10 ++++------ .../mapreflectionapi/api/MultiMapWrapper.java | 8 ++------ .../listeners/PacketListener.java | 10 +++------- 5 files changed, 22 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index 3bb09ab..d830bee 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ tech.sbdevelopment MapReflectionAPI - 1.6.4 + 1.6.6 jar MapReflectionAPI @@ -48,7 +48,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + 3.14.0 11 @@ -119,7 +119,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.11.1 + 3.11.2 11 ${maven.lombok.delombok-target} @@ -166,13 +166,17 @@ dmulloy2-repo https://repo.dmulloy2.net/repository/public/ + + jitpack.io + https://jitpack.io + org.spigotmc spigot-api - 1.21-R0.1-SNAPSHOT + 1.21.5-R0.1-SNAPSHOT provided @@ -190,7 +194,7 @@ com.github.cryptomorin XSeries - 11.3.0 + 13.1.0 @@ -203,7 +207,7 @@ io.netty netty-transport - 4.1.114.Final + 4.1.118.Final provided diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java index 9e46c98..b43f148 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java @@ -86,8 +86,8 @@ public class MapSender { } private static final Class packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap"); - private static final Class worldMapData = supports(17) ? getNMSClass("world.level.saveddata.maps", "WorldMap$b") : null; - private static final Class mapId = supports(21) ? getNMSClass("world.level.saveddata.maps", "MapId") : null; + private static final Class worldMapData = supports(17) ? getNMSClass("world.level.saveddata.maps", supports(21, 2) ? "WorldMap$c" : "WorldMap$b") : null; //1.21.2+ uses WorldMap$c, 1.17+ uses WorldMap$b + private static final Class mapIdClazz = supports(21) ? getNMSClass("world.level.saveddata.maps", "MapId") : null; /** * Send a map to a player @@ -124,7 +124,7 @@ public class MapSender { content.array //Data ); - Object mapId = ReflectionUtil.callConstructor(getNMSClass("world.level.saveddata.maps", "MapId"), id); + Object mapId = ReflectionUtil.callConstructor(mapIdClazz, id); packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, mapId, //ID diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index 534728c..c2b6edd 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -69,8 +69,6 @@ public class MapWrapper extends AbstractMapWrapper { private static final Class entityMetadataPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata"); private static final Class entityItemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame"); private static final Class dataWatcherItemClass = getNMSClass("network.syncher", "DataWatcher$Item"); - private static final Class minecraftKeyClass = getNMSClass("resources", "MinecraftKey"); - private static final Class builtInRegistriesClass = getNMSClass("core.registries", "BuiltInRegistries"); protected MapController controller = new MapController() { private final Map viewers = new HashMap<>(); @@ -169,8 +167,8 @@ public class MapWrapper extends AbstractMapWrapper { String inventoryMenuName; if (supports(21)) { - //1.21 = cc - inventoryMenuName = "cc"; + //1.21.5 = bQ, 1.21 - 1.21.4 = cc + inventoryMenuName = supports(21, 4) ? "bQ" : "cc"; } else if (supports(20)) { //1.20.5 = cb, 1.20.2 - 1.20.4 = bR, 1.20(.1) = bQ inventoryMenuName = supports(20, 4) ? "cb" : supports(20, 2) ? "bR" : "bQ"; @@ -295,7 +293,7 @@ public class MapWrapper extends AbstractMapWrapper { //1.20.5 uses new NBT compound system if (supports(20, 4)) { - Object mapIdComponent = ReflectionUtil.getDeclaredField(getNMSClass("core.component", "DataComponents"), "B"); + Object mapIdComponent = ReflectionUtil.getDeclaredField(getNMSClass("core.component", "DataComponents"), supports(21, 4) ? "M" : supports(21, 2) ? "L" : "B"); //1.21.2+ uses L, otherwise B Object mapId1 = ReflectionUtil.callConstructor(getNMSClass("world.level.saveddata.maps", "MapId"), mapId); // Use generic reflection because of generics @@ -330,7 +328,7 @@ public class MapWrapper extends AbstractMapWrapper { String dataWatcherObjectName; if (supports(21)) { //1.21 - dataWatcherObjectName = "f"; + dataWatcherObjectName = supports(21, 2) ? "e" : "f"; //1.21.2+ = e, 1.21(.1) = f } else if (supports(19, 3)) { //1.19.3 and 1.20(.1) dataWatcherObjectName = "g"; } else if (supports(19)) { //1.19-1.19.2 diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java index 795a2be..be6efd7 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MultiMapWrapper.java @@ -64,10 +64,8 @@ public class MultiMapWrapper extends AbstractMapWrapper { * Creates a new {@link MultiMapWrapper} from the given image. * * @param imageMatrix The image matrix to wrap - * @deprecated Use {@link #MultiMapWrapper(ArrayImage, int, int)} instead, this method is meant for internal use only. */ - @Deprecated(since = "1.6", forRemoval = true) - public MultiMapWrapper(ArrayImage[][] imageMatrix) { + protected MultiMapWrapper(ArrayImage[][] imageMatrix) { wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length]; for (int row = 0; row < imageMatrix.length; row++) { @@ -85,10 +83,8 @@ public class MultiMapWrapper extends AbstractMapWrapper { * Creates a new {@link MultiMapWrapper} from the given image. * * @param imageMatrix The image matrix to wrap - * @deprecated Use {@link #MultiMapWrapper(BufferedImage, int, int)} instead, this method is meant for internal use only. */ - @Deprecated(since = "1.6", forRemoval = true) - public MultiMapWrapper(BufferedImage[][] imageMatrix) { + protected MultiMapWrapper(BufferedImage[][] imageMatrix) { wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length]; for (int row = 0; row < imageMatrix.length; row++) { diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index ec3daf2..eef5b61 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -30,19 +30,15 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; -import sun.misc.Unsafe; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; import tech.sbdevelopment.mapreflectionapi.api.events.CreativeInventoryMapUpdateEvent; import tech.sbdevelopment.mapreflectionapi.api.events.MapCancelEvent; import tech.sbdevelopment.mapreflectionapi.api.events.MapInteractEvent; import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.util.concurrent.TimeUnit; import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getConnection; -import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle; import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; import static com.cryptomorin.xseries.reflection.XReflection.*; @@ -207,9 +203,9 @@ public class PacketListener implements Listener { if (!(vec3d.getClass().isAssignableFrom(vec3DClass))) return new Vector(0, 0, 0); Object vec3dNMS = vec3DClass.cast(vec3d); - double x = (double) getDeclaredField(vec3dNMS, supports(19) ? "c" : supports(17) ? "b" : "x"); //1.19 = c, 1.18 = b, 1.16 = x - double y = (double) getDeclaredField(vec3dNMS, supports(19) ? "d" : supports(17) ? "c" : "y"); //1.19 = d, 1.18 = c, 1.16 = y - double z = (double) getDeclaredField(vec3dNMS, supports(19) ? "e" : supports(17) ? "d" : "z"); //1.19 = e, 1.18 = d, 1.16 = z + double x = (double) getDeclaredField(vec3dNMS, supports(21, 2) ? "d" : supports(19) ? "c" : supports(17) ? "b" : "x"); //1.21.2+ = d, 1.19 = c, 1.18 = b, 1.16 = x + double y = (double) getDeclaredField(vec3dNMS, supports(21, 2) ? "e" : supports(19) ? "d" : supports(17) ? "c" : "y"); //1.21.2+ = e, 1.19 = d, 1.18 = c, 1.16 = y + double z = (double) getDeclaredField(vec3dNMS, supports(21, 2) ? "f" : supports(19) ? "e" : supports(17) ? "d" : "z"); //1.21.2+ = f, 1.19 = e, 1.18 = d, 1.16 = z return new Vector(x, y, z); }