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