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); - } -}