diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
deleted file mode 100644
index c9caadf..0000000
--- a/.github/workflows/maven.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Java CI
-
-on: [push]
-
-jobs:
-  build:
-    name: Build
-    runs-on: ubuntu-latest
-
-    steps:
-      - uses: actions/checkout@v1
-
-      - name: Set up JDK 11
-        uses: actions/setup-java@v1
-        with:
-          java-version: 11
-
-      - name: Build with Maven
-        run: mvn -B package --file pom.xml
-
-      - run: mkdir -p target
-
-      - uses: actions/upload-artifact@master
-        with:
-          name: MapReflectionAPI
-          path: target
diff --git a/.idea/.gitignore b/.idea/.gitignore
index 13566b8..ecca007 100644
--- a/.idea/.gitignore
+++ b/.idea/.gitignore
@@ -5,4 +5,4 @@
 /httpRequests/
 # Datasource local storage ignored files
 /dataSources/
-/dataSources.local.xml
+/dataSources.local.xml
\ No newline at end of file
diff --git a/.idea/copyright/SBDevelopment.xml b/.idea/copyright/SBDevelopment.xml
index 7471535..9a257e5 100644
--- a/.idea/copyright/SBDevelopment.xml
+++ b/.idea/copyright/SBDevelopment.xml
@@ -1,6 +1,6 @@
 <component name="CopyrightManager">
-  <copyright>
-    <option name="notice" value="This file is part of &amp;#36;project.name.&#10;Copyright (c) &amp;#36;originalComment.match(&quot;Copyright \(c\) (\d+)&quot;, 1, &quot;-&quot;, &quot;&amp;#36;today.year&quot;)&amp;#36;today.year inventivetalent / SBDevelopment - All Rights Reserved&#10;&#10;This program is free software: you can redistribute it and/or modify&#10;it under the terms of the GNU General Public License as published by&#10;the Free Software Foundation, either version 3 of the License, or&#10;(at your option) any later version.&#10;&#10;This program is distributed in the hope that it will be useful,&#10;but WITHOUT ANY WARRANTY; without even the implied warranty of&#10;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&#10;GNU General Public License for more details.&#10;&#10;You should have received a copy of the GNU General Public License&#10;along with this program.  If not, see &lt;https://www.gnu.org/licenses/&gt;." />
-    <option name="myName" value="SBDevelopment" />
-  </copyright>
+    <copyright>
+        <option name="notice" value="This file is part of &amp;#36;project.name.&#10;Copyright (c) &amp;#36;originalComment.match(&quot;Copyright \(c\) (\d+)&quot;, 1, &quot;-&quot;, &quot;&amp;#36;today.year&quot;)&amp;#36;today.year inventivetalent / SBDevelopment - All Rights Reserved&#10;&#10;This program is free software: you can redistribute it and/or modify&#10;it under the terms of the GNU General Public License as published by&#10;the Free Software Foundation, either version 3 of the License, or&#10;(at your option) any later version.&#10;&#10;This program is distributed in the hope that it will be useful,&#10;but WITHOUT ANY WARRANTY; without even the implied warranty of&#10;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&#10;GNU General Public License for more details.&#10;&#10;You should have received a copy of the GNU General Public License&#10;along with this program.  If not, see &lt;https://www.gnu.org/licenses/&gt;." />
+        <option name="myName" value="SBDevelopment" />
+    </copyright>
 </component>
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..26f52f6
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,7 @@
+<component name="CopyrightManager">
+  <settings default="SBDevelopment">
+    <module2copyright>
+      <element module="Project Files" copyright="SBDevelopment" />
+    </module2copyright>
+  </settings>
+</component>
\ No newline at end of file
diff --git a/.idea/discord.xml b/.idea/discord.xml
index d8e9561..a672138 100644
--- a/.idea/discord.xml
+++ b/.idea/discord.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
-  <component name="DiscordProjectSettings">
-    <option name="show" value="PROJECT_FILES" />
-    <option name="description" value="" />
-  </component>
+    <component name="DiscordProjectSettings">
+        <option name="show" value="PROJECT_FILES" />
+        <option name="description" value="" />
+    </component>
 </project>
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index 337d139..67b333b 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,6 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/API/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/API/src/main/resources" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/Dist/src/main/java" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/Dist/src/main/resources" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/NMS-v1_12_R1/src/main/java" charset="UTF-8" />
@@ -17,9 +19,9 @@
     <file url="file://$PROJECT_DIR$/NMS-v1_17_R1/src/main/resources" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/NMS-v1_18_R2/src/main/java" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/NMS-v1_18_R2/src/main/resources" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/NMS-v1_19_R1/src/main/java" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/NMS-v1_19_R1/src/main/resources" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
-    <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/NMS-v1_19_R3/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/NMS-v1_19_R3/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/NMS-v1_20_R1/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/NMS-v1_20_R1/src/main/resources" charset="UTF-8" />
   </component>
 </project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 200af21..fadb731 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,8 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
-  <component name="ComposerSettings">
-    <execution />
-  </component>
   <component name="EntryPointsManager">
     <list size="1">
       <item index="0" class="java.lang.String" itemvalue="org.bukkit.event.EventHandler" />
@@ -15,22 +12,6 @@
         <option value="$PROJECT_DIR$/pom.xml" />
       </list>
     </option>
-    <option name="ignoredFiles">
-      <set>
-        <option value="$PROJECT_DIR$/API/pom.xml" />
-        <option value="$PROJECT_DIR$/Dist/pom.xml" />
-        <option value="$PROJECT_DIR$/NMS-v1_12_R1/pom.xml" />
-        <option value="$PROJECT_DIR$/NMS-v1_13_R2/pom.xml" />
-        <option value="$PROJECT_DIR$/NMS-v1_14_R1/pom.xml" />
-        <option value="$PROJECT_DIR$/NMS-v1_15_R1/pom.xml" />
-        <option value="$PROJECT_DIR$/NMS-v1_16_R3/pom.xml" />
-        <option value="$PROJECT_DIR$/NMS-v1_17_R1/pom.xml" />
-        <option value="$PROJECT_DIR$/NMS-v1_18_R2/pom.xml" />
-        <option value="$PROJECT_DIR$/NMS-v1_19_R1/pom.xml" />
-      </set>
-    </option>
-  </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
-    <output url="file://$PROJECT_DIR$/out" />
   </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="temurin-11" project-jdk-type="JavaSDK" />
 </project>
\ 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 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="VcsDirectoryMappings">
-    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+    <mapping directory="" vcs="Git" />
   </component>
 </project>
\ No newline at end of file
diff --git a/API/pom.xml b/API/pom.xml
new file mode 100644
index 0000000..a276161
--- /dev/null
+++ b/API/pom.xml
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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 <https://www.gnu.org/licenses/>.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>MapReflectionAPI</artifactId>
+        <groupId>tech.sbdevelopment</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>MapReflectionAPI-API</artifactId>
+
+    <properties>
+        <jdk.version>11</jdk.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <release>${jdk.version}</release>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>1.18.28</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>3.5.0</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <createDependencyReducedPom>false</createDependencyReducedPom>
+                            <relocations>
+                                <relocation>
+                                    <pattern>com.bergerkiller.bukkit.common</pattern>
+                                    <shadedPattern>tech.sbdevelopment.mapreflectionapi.libs.bkcommonlib</shadedPattern>
+                                </relocation>
+                                <relocation>
+                                    <pattern>org.bstats</pattern>
+                                    <shadedPattern>tech.sbdevelopment.mapreflectionapi.libs.bstats</shadedPattern>
+                                </relocation>
+                            </relocations>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok-maven-plugin</artifactId>
+                <version>1.18.20.0</version>
+                <configuration>
+                    <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
+                    <outputDirectory>${maven.lombok.delombok-target}</outputDirectory>
+                    <addOutputDirectory>false</addOutputDirectory>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>delombok</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>3.5.0</version>
+                <configuration>
+                    <release>${jdk.version}</release>
+                    <sourcepath>${maven.lombok.delombok-target}</sourcepath>
+                    <sourceFileExcludes>
+                        <sourceFileExclude>**/com/bergerkiller/bukkit/common/io/*.java</sourceFileExclude>
+                        <sourceFileExclude>**/com/bergerkiller/bukkit/common/map/*.java</sourceFileExclude>
+                        <sourceFileExclude>**/com/bergerkiller/bukkit/common/map/color/*.java</sourceFileExclude>
+                        <sourceFileExclude>**/tech/sbdevelopment/mapreflectionapi/*.java</sourceFileExclude>
+                        <sourceFileExclude>**/tech/sbdevelopment/mapreflectionapi/cmd/*.java</sourceFileExclude>
+                        <sourceFileExclude>**/tech/sbdevelopment/mapreflectionapi/managers/*.java</sourceFileExclude>
+                        <sourceFileExclude>**/tech/sbdevelopment/mapreflectionapi/utils/*.java</sourceFileExclude>
+                        <sourceFileExclude>**/tech/sbdevelopment/mapreflectionapi/listeners/*.java</sourceFileExclude>
+                    </sourceFileExcludes>
+                </configuration>
+            </plugin>
+        </plugins>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>plugin.yml</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+                <excludes>
+                    <exclude>plugin.yml</exclude>
+                </excludes>
+            </resource>
+        </resources>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>spigot-repo</id>
+            <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
+        </repository>
+        <repository>
+            <id>MG-Dev Jenkins CI Maven Repository</id>
+            <url>https://ci.mg-dev.eu/plugin/repository/everything</url>
+        </repository>
+        <repository>
+            <id>dmulloy2-repo</id>
+            <url>https://repo.dmulloy2.net/repository/public/</url>
+        </repository>
+    </repositories>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.28</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.bstats</groupId>
+            <artifactId>bstats-bukkit</artifactId>
+            <version>3.0.2</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <!-- Libraries below are provided by Spigot -->
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations-java5</artifactId>
+            <version>23.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-transport</artifactId>
+            <version>4.1.77.Final</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
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 97%
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
index 4b89417..90631c8 100644
--- a/src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java
+++ b/API/src/main/java/com/bergerkiller/bukkit/common/io/BitInputStream.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java b/API/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java
similarity index 93%
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
index f841f39..fcc5516 100644
--- a/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java
+++ b/API/src/main/java/com/bergerkiller/bukkit/common/io/BitPacket.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java b/API/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java
similarity index 98%
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
index f0777e9..affdef6 100644
--- a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java
+++ b/API/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/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 94%
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
index b4a4e22..ea06d57 100644
--- 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
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/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 98%
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
index 57f1710..1331937 100644
--- 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
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/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 98%
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
index 1e09f4d..c6185d6 100644
--- 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
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java
similarity index 88%
rename from src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java
rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java
index 70b7368..ac18791 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,11 +90,26 @@ public class MapReflectionAPI extends JavaPlugin {
         getLogger().info("Loading the commands...");
         getCommand("mapmanager").setExecutor(new MapManagerCMD());
 
-        getLogger().info("Loading the map manager...");
-        mapManager = new MapManager();
+        try {
+            packetListener = PacketListener.construct(this);
+        } catch (IllegalStateException e) {
+            getLogger().log(Level.SEVERE, e.getMessage(), e);
+            Bukkit.getPluginManager().disablePlugin(this);
+            return;
+        }
+        packetListener.init(this);
+
+        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...");
+            getLogger().info("Vanilla Maps are allowed!");
+            getLogger().info("Discovering occupied Map IDs...");
             int occupiedIDs = 0;
             for (int s = 0; s < Short.MAX_VALUE; s++) {
                 try {
@@ -113,9 +129,8 @@ 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...");
+        getLogger().info("Enabling metrics...");
         Metrics metrics = new Metrics(this, 16033);
         metrics.addCustomChart(new SingleLineChart("managed_maps", () -> mapManager.getManagedMapsCount()));
 
@@ -167,6 +182,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 93%
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..b6e56c8 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/AbstractMapWrapper.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -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 97%
rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java
rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java
index d58cfb2..c32abdf 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/ArrayImage.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java
similarity index 96%
rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java
rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java
index ca0927f..773c4a9 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/IMapController.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/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..00b906d 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -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<Integer> occupiedIds = new HashSet<>();
     protected final List<MapWrapper> 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);
+
+        plugin.getLogger().info("Enabling MapManager for " + 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..2d7f9cb
--- /dev/null
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java
@@ -0,0 +1,42 @@
+/*
+ * This file is part of MapReflectionAPI.
+ * Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package tech.sbdevelopment.mapreflectionapi.api;
+
+/**
+ * A {@link MapWrapper} wraps one image.
+ */
+public abstract class MapWrapper extends AbstractMapWrapper {
+    protected ArrayImage content;
+
+    /**
+     * Construct a new {@link MapWrapper}
+     *
+     * @param image The {@link ArrayImage} to wrap
+     */
+    public MapWrapper(ArrayImage image) {
+        this.content = image;
+    }
+
+    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 97%
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
index 6629266..a003ddc 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/CreateInventoryMapUpdateEvent.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java
similarity index 95%
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
index 4cc282f..b7aa6c9 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapCancelEvent.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java
similarity index 96%
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..65b9a6e 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
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 97%
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
index 6e2dbb4..6bbcb4a 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapInteractEvent.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/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 91%
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
index 96733cc..56c07cd 100644
--- 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
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java
similarity index 92%
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
index 55a2cc7..9aa6672 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/exceptions/MapLimitExceededException.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/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 91%
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
index bff336f..7fb11d0 100644
--- 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
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java
similarity index 91%
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
index b3d3b45..6c697b6 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/api/package-info.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java
similarity index 96%
rename from src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java
rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java
index 4284c2a..5fe9c42 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java
similarity index 95%
rename from src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java
rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java
index 33ee0b3..4c8729e 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java
new file mode 100644
index 0000000..d56a89e
--- /dev/null
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java
@@ -0,0 +1,80 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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("Enabling PacketListener for " + 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 95%
rename from src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java
rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java
index f5acc72..ab55bef 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/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 99%
rename from src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java
rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java
index 7acc5f3..631c663 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/UpdateManager.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java
similarity index 97%
rename from src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java
rename to API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java
index 50154a5..bb2db31 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java
+++ b/API/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/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..0af26ba
--- /dev/null
+++ b/Dist/pom.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ This file is part of MapReflectionAPI.
+  ~ Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>MapReflectionAPI</artifactId>
+        <groupId>tech.sbdevelopment</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+
+    <artifactId>MapReflectionAPI-Dist</artifactId>
+
+    <build>
+        <directory>../target</directory>
+        <finalName>${project.parent.name}-${project.version}</finalName>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>3.0.0-M2</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>3.3.0</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <createDependencyReducedPom>false</createDependencyReducedPom>
+
+                            <!-- Include all the dependencies required by the plugin -->
+                            <artifactSet>
+                                <includes>
+                                    <include>tech.sbdevelopment:MapReflectionAPI*</include>
+                                </includes>
+                            </artifactSet>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-API</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-NMS-v1_20_R1</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-NMS-v1_19_R3</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-NMS-v1_18_R2</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-NMS-v1_16_R3</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-NMS-v1_17_R1</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-NMS-v1_15_R1</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-NMS-v1_14_R1</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-NMS-v1_13_R2</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-NMS-v1_12_R1</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+    </dependencies>
+</project>
\ 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..adaa593
--- /dev/null
+++ b/NMS-v1_12_R1/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ This file is part of MapReflectionAPI.
+  ~ Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>MapReflectionAPI</artifactId>
+        <groupId>tech.sbdevelopment</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>MapReflectionAPI-NMS-v1_12_R1</artifactId>
+
+    <properties>
+        <NMSVersion>1.12.2-R0.1-SNAPSHOT</NMSVersion>
+        <jdk.version>11</jdk.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <release>${jdk.version}</release>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>3.1.1</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bukkit</groupId>
+            <artifactId>craftbukkit</artifactId>
+            <version>${NMSVersion}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-API</artifactId>
+            <version>${revision}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java
similarity index 57%
rename from src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java
rename to NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java
index 56fcaed..f24528d 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java
+++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_12_R1.java
@@ -1,6 +1,6 @@
 /*
  * This file is part of MapReflectionAPI.
- * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -16,25 +16,27 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-package tech.sbdevelopment.mapreflectionapi.api;
+package tech.sbdevelopment.mapreflectionapi.nms;
 
-import lombok.Data;
+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.utils.ReflectionUtil;
+import tech.sbdevelopment.mapreflectionapi.api.ArrayImage;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
- * The {@link MapSender} sends the Map packets to players.
+ * The {@link MapSender_v1_12_R1} sends the Map packets to players.
  */
-public class MapSender {
+public class MapSender_v1_12_R1 {
     private static final List<QueuedMap> sendQueue = new ArrayList<>();
     private static int senderID = -1;
 
-    private MapSender() {
+    private MapSender_v1_12_R1() {
     }
 
     /**
@@ -82,9 +84,6 @@ public class MapSender {
         }, 0, 2);
     }
 
-    private static final Class<?> packetPlayOutMapClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutMap");
-    private static final Class<?> worldMapData = ReflectionUtil.supports(17) ? ReflectionUtil.getNMSClass("world.level.saveddata.maps", "WorldMap$b") : null;
-
     /**
      * Send a map to a player
      *
@@ -109,57 +108,59 @@ public class MapSender {
         }
 
         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
-            );
+        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)
+                );
 
-            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);
+                ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        });
     }
 
-    @Data
     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..4d710ab
--- /dev/null
+++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_12_R1.java
@@ -0,0 +1,249 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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<UUID, Integer> 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<DataWatcher.Item<?>> list = new ArrayList<>();
+                DataWatcherObject<net.minecraft.server.v1_12_R1.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.server.v1_12_R1.ItemStack>) 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..f2a6924
--- /dev/null
+++ b/NMS-v1_12_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_12_R1.java
@@ -0,0 +1,123 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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..17b0098
--- /dev/null
+++ b/NMS-v1_13_R2/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ This file is part of MapReflectionAPI.
+  ~ Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>MapReflectionAPI</artifactId>
+        <groupId>tech.sbdevelopment</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>MapReflectionAPI-NMS-v1_13_R2</artifactId>
+
+    <properties>
+        <NMSVersion>1.13.2-R0.1-SNAPSHOT</NMSVersion>
+        <jdk.version>11</jdk.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <release>${jdk.version}</release>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>3.1.1</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bukkit</groupId>
+            <artifactId>craftbukkit</artifactId>
+            <version>${NMSVersion}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-API</artifactId>
+            <version>${revision}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
\ 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..d82d5fc
--- /dev/null
+++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_13_R2.java
@@ -0,0 +1,166 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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;
+
+/**
+ * The {@link MapSender_v1_13_R2} sends the Map packets to players.
+ */
+public class MapSender_v1_13_R2 {
+    private static final List<QueuedMap> sendQueue = new ArrayList<>();
+    private static int senderID = -1;
+
+    private MapSender_v1_13_R2() {
+    }
+
+    /**
+     * Add a map to the send queue
+     *
+     * @param id      The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void addToQueue(final int id, final ArrayImage content, final Player player) {
+        QueuedMap toSend = new QueuedMap(id, content, player);
+        if (sendQueue.contains(toSend)) return;
+        sendQueue.add(toSend);
+
+        runSender();
+    }
+
+    /**
+     * Cancels a senderID in the sender queue
+     *
+     * @param s The senderID to cancel
+     */
+    public static void cancelID(int s) {
+        sendQueue.removeIf(queuedMap -> queuedMap.id == s);
+    }
+
+    /**
+     * Run the sender task
+     */
+    private static void runSender() {
+        if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty())
+            return;
+
+        senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> {
+            if (sendQueue.isEmpty()) return;
+
+            for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) {
+                QueuedMap current = sendQueue.get(0);
+                if (current == null) return;
+
+                sendMap(current.id, current.image, current.player);
+
+                if (!sendQueue.isEmpty()) sendQueue.remove(0);
+            }
+        }, 0, 2);
+    }
+
+    /**
+     * Send a map to a player
+     *
+     * @param id0     The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void sendMap(final int id0, final ArrayImage content, final Player player) {
+        if (player == null || !player.isOnline()) {
+            List<QueuedMap> 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..145dc66
--- /dev/null
+++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_13_R2.java
@@ -0,0 +1,248 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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<UUID, Integer> 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<DataWatcher.Item<?>> list = new ArrayList<>();
+                DataWatcherObject<net.minecraft.server.v1_13_R2.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.server.v1_13_R2.ItemStack>) 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..2180ab0
--- /dev/null
+++ b/NMS-v1_13_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_13_R2.java
@@ -0,0 +1,123 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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..9e67e14
--- /dev/null
+++ b/NMS-v1_14_R1/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ This file is part of MapReflectionAPI.
+  ~ Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>MapReflectionAPI</artifactId>
+        <groupId>tech.sbdevelopment</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>MapReflectionAPI-NMS-v1_14_R1</artifactId>
+
+    <properties>
+        <NMSVersion>1.14.4-R0.1-SNAPSHOT</NMSVersion>
+        <jdk.version>11</jdk.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <release>${jdk.version}</release>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>3.1.1</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bukkit</groupId>
+            <artifactId>craftbukkit</artifactId>
+            <version>${NMSVersion}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-API</artifactId>
+            <version>${revision}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
\ 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..49ebfae
--- /dev/null
+++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_14_R1.java
@@ -0,0 +1,167 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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;
+
+/**
+ * The {@link MapSender_v1_14_R1} sends the Map packets to players.
+ */
+public class MapSender_v1_14_R1 {
+    private static final List<QueuedMap> sendQueue = new ArrayList<>();
+    private static int senderID = -1;
+
+    private MapSender_v1_14_R1() {
+    }
+
+    /**
+     * Add a map to the send queue
+     *
+     * @param id      The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void addToQueue(final int id, final ArrayImage content, final Player player) {
+        QueuedMap toSend = new QueuedMap(id, content, player);
+        if (sendQueue.contains(toSend)) return;
+        sendQueue.add(toSend);
+
+        runSender();
+    }
+
+    /**
+     * Cancels a senderID in the sender queue
+     *
+     * @param s The senderID to cancel
+     */
+    public static void cancelID(int s) {
+        sendQueue.removeIf(queuedMap -> queuedMap.id == s);
+    }
+
+    /**
+     * Run the sender task
+     */
+    private static void runSender() {
+        if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty())
+            return;
+
+        senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> {
+            if (sendQueue.isEmpty()) return;
+
+            for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) {
+                QueuedMap current = sendQueue.get(0);
+                if (current == null) return;
+
+                sendMap(current.id, current.image, current.player);
+
+                if (!sendQueue.isEmpty()) sendQueue.remove(0);
+            }
+        }, 0, 2);
+    }
+
+    /**
+     * Send a map to a player
+     *
+     * @param id0     The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void sendMap(final int id0, final ArrayImage content, final Player player) {
+        if (player == null || !player.isOnline()) {
+            List<QueuedMap> 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..4a73b9f
--- /dev/null
+++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_14_R1.java
@@ -0,0 +1,248 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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<UUID, Integer> 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<DataWatcher.Item<?>> list = new ArrayList<>();
+                DataWatcherObject<net.minecraft.server.v1_14_R1.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.server.v1_14_R1.ItemStack>) 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..551a54b
--- /dev/null
+++ b/NMS-v1_14_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_14_R1.java
@@ -0,0 +1,123 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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..4f6f9df
--- /dev/null
+++ b/NMS-v1_15_R1/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ This file is part of MapReflectionAPI.
+  ~ Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>MapReflectionAPI</artifactId>
+        <groupId>tech.sbdevelopment</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>MapReflectionAPI-NMS-v1_15_R1</artifactId>
+
+    <properties>
+        <NMSVersion>1.15.2-R0.1-SNAPSHOT</NMSVersion>
+        <jdk.version>11</jdk.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <release>${jdk.version}</release>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>3.1.1</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bukkit</groupId>
+            <artifactId>craftbukkit</artifactId>
+            <version>${NMSVersion}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-API</artifactId>
+            <version>${revision}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
\ 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..a7b83d9
--- /dev/null
+++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_15_R1.java
@@ -0,0 +1,167 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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;
+
+/**
+ * The {@link MapSender_v1_15_R1} sends the Map packets to players.
+ */
+public class MapSender_v1_15_R1 {
+    private static final List<QueuedMap> sendQueue = new ArrayList<>();
+    private static int senderID = -1;
+
+    private MapSender_v1_15_R1() {
+    }
+
+    /**
+     * Add a map to the send queue
+     *
+     * @param id      The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void addToQueue(final int id, final ArrayImage content, final Player player) {
+        QueuedMap toSend = new QueuedMap(id, content, player);
+        if (sendQueue.contains(toSend)) return;
+        sendQueue.add(toSend);
+
+        runSender();
+    }
+
+    /**
+     * Cancels a senderID in the sender queue
+     *
+     * @param s The senderID to cancel
+     */
+    public static void cancelID(int s) {
+        sendQueue.removeIf(queuedMap -> queuedMap.id == s);
+    }
+
+    /**
+     * Run the sender task
+     */
+    private static void runSender() {
+        if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty())
+            return;
+
+        senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> {
+            if (sendQueue.isEmpty()) return;
+
+            for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) {
+                QueuedMap current = sendQueue.get(0);
+                if (current == null) return;
+
+                sendMap(current.id, current.image, current.player);
+
+                if (!sendQueue.isEmpty()) sendQueue.remove(0);
+            }
+        }, 0, 2);
+    }
+
+    /**
+     * Send a map to a player
+     *
+     * @param id0      The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void sendMap(final int id0, final ArrayImage content, final Player player) {
+        if (player == null || !player.isOnline()) {
+            List<QueuedMap> 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..7308ace
--- /dev/null
+++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_15_R1.java
@@ -0,0 +1,249 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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<UUID, Integer> 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<DataWatcher.Item<?>> list = new ArrayList<>();
+                DataWatcherObject<net.minecraft.server.v1_15_R1.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.server.v1_15_R1.ItemStack>) 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..a73dd04
--- /dev/null
+++ b/NMS-v1_15_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_15_R1.java
@@ -0,0 +1,123 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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..46edaa9
--- /dev/null
+++ b/NMS-v1_16_R3/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ This file is part of MapReflectionAPI.
+  ~ Copyright (c) 2023 inventivetalent / SBDevelopment - All Rights Reserved
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>MapReflectionAPI</artifactId>
+        <groupId>tech.sbdevelopment</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>MapReflectionAPI-NMS-v1_16_R3</artifactId>
+
+    <properties>
+        <NMSVersion>1.16.4-R0.1-SNAPSHOT</NMSVersion>
+        <jdk.version>11</jdk.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <release>${jdk.version}</release>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>3.1.1</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bukkit</groupId>
+            <artifactId>craftbukkit</artifactId>
+            <version>${NMSVersion}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-API</artifactId>
+            <version>${revision}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
\ 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..e4ed5cf
--- /dev/null
+++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_16_R3.java
@@ -0,0 +1,167 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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;
+
+/**
+ * The {@link MapSender_v1_16_R3} sends the Map packets to players.
+ */
+public class MapSender_v1_16_R3 {
+    private static final List<QueuedMap> sendQueue = new ArrayList<>();
+    private static int senderID = -1;
+
+    private MapSender_v1_16_R3() {
+    }
+
+    /**
+     * Add a map to the send queue
+     *
+     * @param id      The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void addToQueue(final int id, final ArrayImage content, final Player player) {
+        QueuedMap toSend = new QueuedMap(id, content, player);
+        if (sendQueue.contains(toSend)) return;
+        sendQueue.add(toSend);
+
+        runSender();
+    }
+
+    /**
+     * Cancels a senderID in the sender queue
+     *
+     * @param s The senderID to cancel
+     */
+    public static void cancelID(int s) {
+        sendQueue.removeIf(queuedMap -> queuedMap.id == s);
+    }
+
+    /**
+     * Run the sender task
+     */
+    private static void runSender() {
+        if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty())
+            return;
+
+        senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> {
+            if (sendQueue.isEmpty()) return;
+
+            for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) {
+                QueuedMap current = sendQueue.get(0);
+                if (current == null) return;
+
+                sendMap(current.id, current.image, current.player);
+
+                if (!sendQueue.isEmpty()) sendQueue.remove(0);
+            }
+        }, 0, 2);
+    }
+
+    /**
+     * Send a map to a player
+     *
+     * @param id0      The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void sendMap(final int id0, final ArrayImage content, final Player player) {
+        if (player == null || !player.isOnline()) {
+            List<QueuedMap> 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..492fd29
--- /dev/null
+++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_16_R3.java
@@ -0,0 +1,248 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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<UUID, Integer> 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<DataWatcher.Item<?>> list = new ArrayList<>();
+                DataWatcherObject<net.minecraft.server.v1_16_R3.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.server.v1_16_R3.ItemStack>) 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..5f6fa98
--- /dev/null
+++ b/NMS-v1_16_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_16_R3.java
@@ -0,0 +1,123 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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..2608809
--- /dev/null
+++ b/NMS-v1_17_R1/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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 <https://www.gnu.org/licenses/>.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>MapReflectionAPI</artifactId>
+        <groupId>tech.sbdevelopment</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>MapReflectionAPI-NMS-v1_17_R1</artifactId>
+
+    <properties>
+        <NMSVersion>1.17.1-R0.1-SNAPSHOT</NMSVersion>
+        <jdk.version>17</jdk.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <release>${jdk.version}</release>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>3.1.1</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bukkit</groupId>
+            <artifactId>craftbukkit</artifactId>
+            <version>${NMSVersion}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-API</artifactId>
+            <version>${revision}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
\ 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..050df5b
--- /dev/null
+++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_17_R1.java
@@ -0,0 +1,138 @@
+/*
+ * This file is part of MapReflectionAPI.
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+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;
+
+/**
+ * The {@link MapSender_v1_17_R1} sends the Map packets to players.
+ */
+public class MapSender_v1_17_R1 {
+    private static final List<QueuedMap> sendQueue = new ArrayList<>();
+    private static int senderID = -1;
+
+    private MapSender_v1_17_R1() {
+    }
+
+    /**
+     * Add a map to the send queue
+     *
+     * @param id      The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void addToQueue(final int id, final ArrayImage content, final Player player) {
+        QueuedMap toSend = new QueuedMap(id, content, player);
+        if (sendQueue.contains(toSend)) return;
+        sendQueue.add(toSend);
+
+        runSender();
+    }
+
+    /**
+     * Cancels a senderID in the sender queue
+     *
+     * @param s The senderID to cancel
+     */
+    public static void cancelID(int s) {
+        sendQueue.removeIf(queuedMap -> queuedMap.id == s);
+    }
+
+    /**
+     * Run the sender task
+     */
+    private static void runSender() {
+        if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty())
+            return;
+
+        senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> {
+            if (sendQueue.isEmpty()) return;
+
+            for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) {
+                QueuedMap current = sendQueue.get(0);
+                if (current == null) return;
+
+                sendMap(current.id, current.image, current.player);
+
+                if (!sendQueue.isEmpty()) sendQueue.remove(0);
+            }
+        }, 0, 2);
+    }
+
+    /**
+     * Send a map to a player
+     *
+     * @param id0     The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void sendMap(final int id0, final ArrayImage content, final Player player) {
+        if (player == null || !player.isOnline()) {
+            List<QueuedMap> 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..8ba9201
--- /dev/null
+++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_17_R1.java
@@ -0,0 +1,251 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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<UUID, Integer> 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<DataWatcher.Item<?>> list = new ArrayList<>();
+                DataWatcherObject<net.minecraft.world.item.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.world.item.ItemStack>) 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..f06f3f5
--- /dev/null
+++ b/NMS-v1_17_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_17_R1.java
@@ -0,0 +1,121 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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..5dfc581
--- /dev/null
+++ b/NMS-v1_18_R2/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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 <https://www.gnu.org/licenses/>.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>MapReflectionAPI</artifactId>
+        <groupId>tech.sbdevelopment</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>MapReflectionAPI-NMS-v1_18_R2</artifactId>
+
+    <properties>
+        <NMSVersion>1.18.2-R0.1-SNAPSHOT</NMSVersion>
+        <jdk.version>17</jdk.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <release>${jdk.version}</release>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>3.1.1</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bukkit</groupId>
+            <artifactId>craftbukkit</artifactId>
+            <version>${NMSVersion}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-API</artifactId>
+            <version>${revision}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
\ 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..ebe34fe
--- /dev/null
+++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_18_R2.java
@@ -0,0 +1,138 @@
+/*
+ * This file is part of MapReflectionAPI.
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+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;
+
+/**
+ * The {@link MapSender_v1_18_R2} sends the Map packets to players.
+ */
+public class MapSender_v1_18_R2 {
+    private static final List<QueuedMap> sendQueue = new ArrayList<>();
+    private static int senderID = -1;
+
+    private MapSender_v1_18_R2() {
+    }
+
+    /**
+     * Add a map to the send queue
+     *
+     * @param id      The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void addToQueue(final int id, final ArrayImage content, final Player player) {
+        QueuedMap toSend = new QueuedMap(id, content, player);
+        if (sendQueue.contains(toSend)) return;
+        sendQueue.add(toSend);
+
+        runSender();
+    }
+
+    /**
+     * Cancels a senderID in the sender queue
+     *
+     * @param s The senderID to cancel
+     */
+    public static void cancelID(int s) {
+        sendQueue.removeIf(queuedMap -> queuedMap.id == s);
+    }
+
+    /**
+     * Run the sender task
+     */
+    private static void runSender() {
+        if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty())
+            return;
+
+        senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> {
+            if (sendQueue.isEmpty()) return;
+
+            for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) {
+                QueuedMap current = sendQueue.get(0);
+                if (current == null) return;
+
+                sendMap(current.id, current.image, current.player);
+
+                if (!sendQueue.isEmpty()) sendQueue.remove(0);
+            }
+        }, 0, 2);
+    }
+
+    /**
+     * Send a map to a player
+     *
+     * @param id0     The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void sendMap(final int id0, final ArrayImage content, final Player player) {
+        if (player == null || !player.isOnline()) {
+            List<QueuedMap> 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..1ab2a22
--- /dev/null
+++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_18_R2.java
@@ -0,0 +1,251 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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<UUID, Integer> 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<DataWatcher.Item<?>> list = new ArrayList<>();
+                DataWatcherObject<net.minecraft.world.item.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.world.item.ItemStack>) 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..7524a29
--- /dev/null
+++ b/NMS-v1_18_R2/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_18_R2.java
@@ -0,0 +1,121 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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..08f0828
--- /dev/null
+++ b/NMS-v1_19_R3/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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 <https://www.gnu.org/licenses/>.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>MapReflectionAPI</artifactId>
+        <groupId>tech.sbdevelopment</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>MapReflectionAPI-NMS-v1_19_R3</artifactId>
+
+    <properties>
+        <NMSVersion>1.19.4-R0.1-SNAPSHOT</NMSVersion>
+        <jdk.version>17</jdk.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <release>${jdk.version}</release>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>3.1.1</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bukkit</groupId>
+            <artifactId>craftbukkit</artifactId>
+            <version>${NMSVersion}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-API</artifactId>
+            <version>${revision}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
\ 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..50d5bbd
--- /dev/null
+++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_19_R3.java
@@ -0,0 +1,138 @@
+/*
+ * This file is part of MapReflectionAPI.
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+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;
+
+/**
+ * The {@link MapSender_v1_19_R3} sends the Map packets to players.
+ */
+public class MapSender_v1_19_R3 {
+    private static final List<QueuedMap> sendQueue = new ArrayList<>();
+    private static int senderID = -1;
+
+    private MapSender_v1_19_R3() {
+    }
+
+    /**
+     * Add a map to the send queue
+     *
+     * @param id      The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void addToQueue(final int id, final ArrayImage content, final Player player) {
+        QueuedMap toSend = new QueuedMap(id, content, player);
+        if (sendQueue.contains(toSend)) return;
+        sendQueue.add(toSend);
+
+        runSender();
+    }
+
+    /**
+     * Cancels a senderID in the sender queue
+     *
+     * @param s The senderID to cancel
+     */
+    public static void cancelID(int s) {
+        sendQueue.removeIf(queuedMap -> queuedMap.id == s);
+    }
+
+    /**
+     * Run the sender task
+     */
+    private static void runSender() {
+        if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty())
+            return;
+
+        senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> {
+            if (sendQueue.isEmpty()) return;
+
+            for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) {
+                QueuedMap current = sendQueue.get(0);
+                if (current == null) return;
+
+                sendMap(current.id, current.image, current.player);
+
+                if (!sendQueue.isEmpty()) sendQueue.remove(0);
+            }
+        }, 0, 2);
+    }
+
+    /**
+     * Send a map to a player
+     *
+     * @param id0     The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void sendMap(final int id0, final ArrayImage content, final Player player) {
+        if (player == null || !player.isOnline()) {
+            List<QueuedMap> 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..e0e0615
--- /dev/null
+++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_19_R3.java
@@ -0,0 +1,239 @@
+/*
+ * This file is part of MapReflectionAPI.
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+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<UUID, Integer> 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<DataWatcher.b<?>> 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..c437542
--- /dev/null
+++ b/NMS-v1_19_R3/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_19_R3.java
@@ -0,0 +1,126 @@
+/*
+ * This file is part of MapReflectionAPI.
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+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..6f5cdde
--- /dev/null
+++ b/NMS-v1_20_R1/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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 <https://www.gnu.org/licenses/>.
+  -->
+
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>MapReflectionAPI</artifactId>
+        <groupId>tech.sbdevelopment</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>MapReflectionAPI-NMS-v1_20_R1</artifactId>
+
+    <properties>
+        <NMSVersion>1.20.1-R0.1-SNAPSHOT</NMSVersion>
+        <jdk.version>17</jdk.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <release>${jdk.version}</release>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>3.1.1</version>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.bukkit</groupId>
+            <artifactId>craftbukkit</artifactId>
+            <version>${NMSVersion}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>tech.sbdevelopment</groupId>
+            <artifactId>MapReflectionAPI-API</artifactId>
+            <version>${revision}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
\ 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..3f523f7
--- /dev/null
+++ b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapSender_v1_20_R1.java
@@ -0,0 +1,138 @@
+/*
+ * This file is part of MapReflectionAPI.
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+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;
+
+/**
+ * The {@link MapSender_v1_20_R1} sends the Map packets to players.
+ */
+public class MapSender_v1_20_R1 {
+    private static final List<QueuedMap> sendQueue = new ArrayList<>();
+    private static int senderID = -1;
+
+    private MapSender_v1_20_R1() {
+    }
+
+    /**
+     * Add a map to the send queue
+     *
+     * @param id      The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void addToQueue(final int id, final ArrayImage content, final Player player) {
+        QueuedMap toSend = new QueuedMap(id, content, player);
+        if (sendQueue.contains(toSend)) return;
+        sendQueue.add(toSend);
+
+        runSender();
+    }
+
+    /**
+     * Cancels a senderID in the sender queue
+     *
+     * @param s The senderID to cancel
+     */
+    public static void cancelID(int s) {
+        sendQueue.removeIf(queuedMap -> queuedMap.id == s);
+    }
+
+    /**
+     * Run the sender task
+     */
+    private static void runSender() {
+        if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.isEmpty())
+            return;
+
+        senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapReflectionAPI.getInstance(), () -> {
+            if (sendQueue.isEmpty()) return;
+
+            for (int i = 0; i < Math.min(sendQueue.size(), 10 + 1); i++) {
+                QueuedMap current = sendQueue.get(0);
+                if (current == null) return;
+
+                sendMap(current.id, current.image, current.player);
+
+                if (!sendQueue.isEmpty()) sendQueue.remove(0);
+            }
+        }, 0, 2);
+    }
+
+    /**
+     * Send a map to a player
+     *
+     * @param id0     The ID of the map
+     * @param content The {@link ArrayImage} to view on the map
+     * @param player  The {@link Player} to view for
+     */
+    public static void sendMap(final int id0, final ArrayImage content, final Player player) {
+        if (player == null || !player.isOnline()) {
+            List<QueuedMap> 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..fcdf607
--- /dev/null
+++ b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/MapWrapper_v1_20_R1.java
@@ -0,0 +1,239 @@
+/*
+ * This file is part of MapReflectionAPI.
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+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<UUID, Integer> 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.w().a("map", mapId); //getOrCreateTag putInt
+
+            List<DataWatcher.b<?>> 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..a07cde1
--- /dev/null
+++ b/NMS-v1_20_R1/src/main/java/tech/sbdevelopment/mapreflectionapi/nms/PacketListener_v1_20_R1.java
@@ -0,0 +1,126 @@
+/*
+ * This file is part of MapReflectionAPI.
+ * Copyright (c) 2022-2023 inventivetalent / SBDevelopment - All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+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/pom.xml b/pom.xml
index 361046c..aa639dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,18 +24,33 @@
 
     <groupId>tech.sbdevelopment</groupId>
     <artifactId>MapReflectionAPI</artifactId>
-    <version>1.4.4</version>
-    <packaging>jar</packaging>
+    <packaging>pom</packaging>
+    <version>${revision}</version>
 
     <name>MapReflectionAPI</name>
     <description>This API helps developer with viewing images on maps.</description>
     <url>https://sbdplugins.nl</url>
 
     <properties>
+        <revision>1.5</revision>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        <maven.lombok.delombok-target>${project.build.directory}/javadoc-delombok</maven.lombok.delombok-target>
+        <jdk.version>11</jdk.version>
     </properties>
 
+    <modules>
+        <module>API</module>
+        <module>Dist</module>
+        <module>NMS-v1_20_R1</module>
+        <module>NMS-v1_19_R3</module>
+        <module>NMS-v1_18_R2</module>
+        <module>NMS-v1_17_R1</module>
+        <module>NMS-v1_16_R3</module>
+        <module>NMS-v1_15_R1</module>
+        <module>NMS-v1_14_R1</module>
+        <module>NMS-v1_13_R2</module>
+        <module>NMS-v1_12_R1</module>
+    </modules>
+
     <distributionManagement>
         <repository>
             <id>sbdevelopment-repo</id>
@@ -44,102 +59,46 @@
     </distributionManagement>
 
     <build>
+        <defaultGoal>clean package</defaultGoal>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.10.1</version>
-                <configuration>
-                    <release>11</release>
-                    <annotationProcessorPaths>
-                        <path>
-                            <groupId>org.projectlombok</groupId>
-                            <artifactId>lombok</artifactId>
-                            <version>1.18.24</version>
-                        </path>
-                    </annotationProcessorPaths>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-shade-plugin</artifactId>
-                <version>3.3.0</version>
+                <artifactId>maven-toolchains-plugin</artifactId>
+                <version>3.1.0</version>
                 <executions>
                     <execution>
-                        <phase>package</phase>
                         <goals>
-                            <goal>shade</goal>
-                        </goals>
-                        <configuration>
-                            <createDependencyReducedPom>false</createDependencyReducedPom>
-                            <relocations>
-                                <relocation>
-                                    <pattern>com.bergerkiller.bukkit.common</pattern>
-                                    <shadedPattern>tech.sbdevelopment.mapreflectionapi.libs.bkcommonlib</shadedPattern>
-                                </relocation>
-                                <relocation>
-                                    <pattern>org.bstats</pattern>
-                                    <shadedPattern>tech.sbdevelopment.mapreflectionapi.libs.bstats</shadedPattern>
-                                </relocation>
-                            </relocations>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
-                <groupId>org.projectlombok</groupId>
-                <artifactId>lombok-maven-plugin</artifactId>
-                <version>1.18.20.0</version>
-                <configuration>
-                    <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
-                    <outputDirectory>${maven.lombok.delombok-target}</outputDirectory>
-                    <addOutputDirectory>false</addOutputDirectory>
-                </configuration>
-                <executions>
-                    <execution>
-                        <phase>generate-sources</phase>
-                        <goals>
-                            <goal>delombok</goal>
+                            <goal>toolchain</goal>
                         </goals>
                     </execution>
                 </executions>
+                <configuration>
+                    <toolchains>
+                        <jdk>
+                            <version>${jdk.version}</version>
+                        </jdk>
+                    </toolchains>
+                </configuration>
             </plugin>
 
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-javadoc-plugin</artifactId>
-                <version>3.4.0</version>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
                 <configuration>
-                    <release>11</release>
-                    <sourcepath>${maven.lombok.delombok-target}</sourcepath>
-                    <sourceFileExcludes>
-                        <sourceFileExclude>**/com/bergerkiller/bukkit/common/io/*.java</sourceFileExclude>
-                        <sourceFileExclude>**/com/bergerkiller/bukkit/common/map/*.java</sourceFileExclude>
-                        <sourceFileExclude>**/com/bergerkiller/bukkit/common/map/color/*.java</sourceFileExclude>
-                        <sourceFileExclude>**/tech/sbdevelopment/mapreflectionapi/*.java</sourceFileExclude>
-                        <sourceFileExclude>**/tech/sbdevelopment/mapreflectionapi/cmd/*.java</sourceFileExclude>
-                        <sourceFileExclude>**/tech/sbdevelopment/mapreflectionapi/managers/*.java</sourceFileExclude>
-                        <sourceFileExclude>**/tech/sbdevelopment/mapreflectionapi/utils/*.java</sourceFileExclude>
-                        <sourceFileExclude>**/tech/sbdevelopment/mapreflectionapi/listeners/*.java</sourceFileExclude>
-                    </sourceFileExcludes>
+                    <release>${jdk.version}</release>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>3.1.1</version>
+                <configuration>
+                    <skip>true</skip>
                 </configuration>
             </plugin>
         </plugins>
-        <resources>
-            <resource>
-                <directory>src/main/resources</directory>
-                <filtering>true</filtering>
-                <includes>
-                    <include>plugin.yml</include>
-                </includes>
-            </resource>
-            <resource>
-                <directory>src/main/resources</directory>
-                <excludes>
-                    <exclude>plugin.yml</exclude>
-                </excludes>
-            </resource>
-        </resources>
     </build>
 
     <repositories>
@@ -147,49 +106,13 @@
             <id>spigot-repo</id>
             <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
         </repository>
-        <repository>
-            <id>MG-Dev Jenkins CI Maven Repository</id>
-            <url>https://ci.mg-dev.eu/plugin/repository/everything</url>
-        </repository>
-        <repository>
-            <id>dmulloy2-repo</id>
-            <url>https://repo.dmulloy2.net/repository/public/</url>
-        </repository>
     </repositories>
 
     <dependencies>
         <dependency>
             <groupId>org.spigotmc</groupId>
             <artifactId>spigot-api</artifactId>
-            <version>1.19.4-R0.1-SNAPSHOT</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.projectlombok</groupId>
-            <artifactId>lombok</artifactId>
-            <version>1.18.24</version>
-            <scope>provided</scope>
-        </dependency>
-
-        <dependency>
-            <groupId>org.bstats</groupId>
-            <artifactId>bstats-bukkit</artifactId>
-            <version>3.0.0</version>
-            <scope>compile</scope>
-        </dependency>
-
-        <!-- Libraries below are provided by Spigot -->
-        <dependency>
-            <groupId>org.jetbrains</groupId>
-            <artifactId>annotations-java5</artifactId>
-            <version>23.0.0</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>io.netty</groupId>
-            <artifactId>netty-transport</artifactId>
-            <version>4.1.77.Final</version>
-            <scope>provided</scope>
+            <version>1.20.1-R0.1-SNAPSHOT</version>
         </dependency>
     </dependencies>
-</project>
\ No newline at end of file
+</project>
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 <https://www.gnu.org/licenses/>.
- */
-
-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<UUID, Integer> 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 <https://www.gnu.org/licenses/>.
- */
-
-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);
-    }
-}