浏览代码

Merge branch 'nbt' of github.com:mcMMO-Dev/mcmmo into configurable

nossr50 5 年之前
父节点
当前提交
3b276b59cf
共有 32 个文件被更改,包括 1207 次插入75 次删除
  1. 0 11
      1
  2. 2 0
      build.gradle.kts
  3. 28 4
      mcmmo-core/build.gradle.kts
  4. 15 0
      mcmmo-core/src/main/java/com/gmail/nossr50/commands/admin/NBTToolsCommand.java
  5. 0 20
      mcmmo-core/src/main/java/com/gmail/nossr50/commands/admin/PlayerDebug.java
  6. 10 11
      mcmmo-core/src/main/java/com/gmail/nossr50/commands/admin/PlayerDebugCommand.java
  7. 34 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/PlatformManager.java
  8. 13 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/adapters/NBTAdapter.java
  9. 158 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/adapters/NMS_114/BukkitNBTAdapter.java
  10. 11 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/adapters/NMS_114/BukkitPlatformAdapter.java
  11. 19 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/adapters/PlatformAdapter.java
  12. 11 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTBase.java
  13. 45 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTByte.java
  14. 49 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTByteArray.java
  15. 65 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTCompound.java
  16. 45 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTDouble.java
  17. 8 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTEnd.java
  18. 45 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTFloat.java
  19. 45 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTInt.java
  20. 49 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTIntArray.java
  21. 53 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTList.java
  22. 45 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTLong.java
  23. 49 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTLongArray.java
  24. 45 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTShort.java
  25. 50 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTString.java
  26. 22 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTType.java
  27. 14 0
      mcmmo-core/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java
  28. 16 2
      mcmmo-core/src/main/java/com/gmail/nossr50/mcMMO.java
  29. 41 10
      mcmmo-core/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java
  30. 71 0
      mcmmo-core/src/main/java/com/gmail/nossr50/util/nbt/NBTFactory.java
  31. 141 17
      mcmmo-core/src/main/java/com/gmail/nossr50/util/nbt/NBTManager.java
  32. 8 0
      mcmmo-core/src/main/resources/plugin.yml

+ 0 - 11
1

@@ -1,11 +0,0 @@
-SkillShot tweaks
-# Please enter the commit message for your changes. Lines starting
-# with '#' will be ignored, and an empty message aborts the commit.
-#
-# On branch master
-# Your branch is up to date with 'origin/master'.
-#
-# Changes to be committed:
-#	modified:   src/main/java/com/gmail/nossr50/skills/archery/Archery.java
-#	modified:   src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java
-#

+ 2 - 0
build.gradle.kts

@@ -11,6 +11,8 @@ subprojects {
         maven("https://repo.codemc.org/repository/maven-public")
         maven("https://maven.sk89q.com/repo")
         maven("https://mvnrepository.com/artifact/org.jetbrains/annotations")
+        maven("https://repo.aikar.co/content/groups/aikar/")
+        maven("https://hub.spigotmc.org/nexus/content/groups/public/")
     }
 
     tasks {

+ 28 - 4
mcmmo-core/build.gradle.kts

@@ -1,3 +1,4 @@
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
 import org.apache.tools.ant.filters.ReplaceTokens
 
 plugins {
@@ -7,10 +8,6 @@ plugins {
 
 tasks {
 
-    build {
-        dependsOn(shadowJar)
-    }
-
     shadowJar {
         dependencies {
             include(dependency("org.spongepowered:configurate-yaml"))
@@ -20,12 +17,24 @@ tasks {
             include(dependency("org.apache.tomcat:tomcat-jdbc"))
             include(dependency("org.apache.tomcat:tomcat-juli"))
             include(dependency("com.typesafe:config"))
+            include(dependency("co.aikar:acf-core"))
+            include(dependency("co.aikar:acf-bukkit"))
+//            include(dependency("co.aikar:locales"))
+//            include(dependency("co.aikar:table"))
+//            include(dependency("net.jodah:expiring-map"))
             exclude(dependency("org.spigotmc:spigot"))
         }
         relocate("org.apache.commons.logging", "com.gmail.nossr50.commons.logging")
         relocate("org.apache.juli", "com.gmail.nossr50.database.tomcat.juli")
         relocate("org.apache.tomcat", "com.gmail.nossr50.database.tomcat")
         relocate("org.bstats", "com.gmail.nossr50.metrics.bstat")
+        relocate("co.aikar.commands", "com.gmail.nossr50.aikar.commands")
+        relocate("co.aikar.locales", "com.gmail.nossr50.aikar.locales")
+        relocate("co.aikar.table", "com.gmail.nossr50.aikar.table")
+        relocate("net.jodah.expiringmap", "com.gmail.nossr50.expiringmap")
+
+//        archiveBaseName.set("mcMMO")
+        mergeServiceFiles()
     }
 
     processResources {
@@ -34,6 +43,16 @@ tasks {
 
         }
     }
+
+    build {
+        dependsOn(shadowJar)
+    }
+}
+
+tasks.named<ShadowJar>("shadowJar") {
+    dependencies{
+        include { true }
+    }
 }
 
 
@@ -43,6 +62,11 @@ dependencies {
     api("org.spongepowered:configurate-core:3.7-SNAPSHOT")
     api("org.spongepowered:configurate-yaml:3.7-SNAPSHOT")
     api("org.spongepowered:configurate-hocon:3.7-SNAPSHOT")
+    api("co.aikar:acf-core:0.5.0-SNAPSHOT") //Don't change without updating the artifacts for its dependencies (see the other comments)
+    api("co.aikar:acf-bukkit:0.5.0-SNAPSHOT") //Don't change without updating the artifacts for its dependencies (see the other comments)
+//    api("co.aikar:locales:1.0-SNAPSHOT") //ACF 0.5.0-SNAPSHOT is dependent on this version of locales
+//    api("co.aikar:table:1.0.0-SNAPSHOT") //ACF 0.5.0-SNAPSHOT is dependent on this version of table
+//    api("net.jodah:expiring-map:0.5.8") //ACF 0.5.0-SNAPSHOT is dependent on this version of expiring map
     implementation("org.jetbrains:annotations:17.0.0")
     implementation("org.apache.maven.scm:maven-scm-provider-gitexe:1.8.1")
     implementation("org.bstats:bstats-bukkit:1.4")

+ 15 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/commands/admin/NBTToolsCommand.java

@@ -0,0 +1,15 @@
+package com.gmail.nossr50.commands.admin;
+
+import co.aikar.commands.BaseCommand;
+import co.aikar.commands.annotation.Dependency;
+import co.aikar.commands.annotation.Description;
+import com.gmail.nossr50.mcMMO;
+
+@Description("Read or Modify values of NBT on an item in-hand")
+public class NBTToolsCommand extends BaseCommand {
+
+    @Dependency
+    private mcMMO pluginRef;
+
+
+}

+ 0 - 20
mcmmo-core/src/main/java/com/gmail/nossr50/commands/admin/PlayerDebug.java

@@ -1,20 +0,0 @@
-package com.gmail.nossr50.commands.admin;
-
-import com.gmail.nossr50.mcMMO;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
-import org.bukkit.command.CommandSender;
-
-public class PlayerDebug implements CommandExecutor {
-
-    private final mcMMO pluginRef;
-
-    public PlayerDebug(mcMMO pluginRef) {
-        this.pluginRef = pluginRef;
-    }
-
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-        return false;
-    }
-}

+ 10 - 11
mcmmo-core/src/main/java/com/gmail/nossr50/commands/admin/PlayerDebugCommand.java

@@ -1,29 +1,28 @@
 package com.gmail.nossr50.commands.admin;
 
+import co.aikar.commands.BaseCommand;
+import co.aikar.commands.annotation.Dependency;
+import co.aikar.commands.annotation.Description;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.mcMMO;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandExecutor;
 import org.bukkit.command.CommandSender;
 import org.bukkit.entity.Player;
 
-public class PlayerDebugCommand implements CommandExecutor {
 
-    private final mcMMO pluginRef;
+@Description("Puts the player into debug mode, which helps problem solve bugs in mcMMO.")
+public class PlayerDebugCommand extends BaseCommand {
 
-    public PlayerDebugCommand(mcMMO pluginRef) {
-        this.pluginRef = pluginRef;
-    }
+    @Dependency
+    private mcMMO pluginRef;
 
-    @Override
-    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+    public void onCommand(CommandSender sender) {
         if(sender instanceof Player) {
             McMMOPlayer mcMMOPlayer = pluginRef.getUserManager().getPlayer((Player) sender);
             mcMMOPlayer.toggleDebugMode(); //Toggle debug mode
             pluginRef.getNotificationManager().sendPlayerInformationChatOnlyPrefixed(mcMMOPlayer.getPlayer(), "Commands.Mmodebug.Toggle", String.valueOf(mcMMOPlayer.isDebugMode()));
-            return true;
         } else {
-            return false;
+            //TODO: Localize
+            sender.sendMessage("Players only");
         }
     }
 

+ 34 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/PlatformManager.java

@@ -0,0 +1,34 @@
+package com.gmail.nossr50.core;
+
+import com.gmail.nossr50.core.adapters.NMS_114.BukkitPlatformAdapter;
+import com.gmail.nossr50.core.adapters.PlatformAdapter;
+import com.gmail.nossr50.mcMMO;
+
+public class PlatformManager {
+    private PlatformAdapter platformAdapter;
+    private mcMMO pluginRef;
+
+    public PlatformManager(mcMMO pluginRef) {
+        this.pluginRef = pluginRef;
+        initAdapters();
+    }
+
+    /**
+     * Initialize the adapters based on the current platform
+     */
+    private void initAdapters() {
+        pluginRef.getLogger().info("Initializing platform adapters...");
+        //Determine which platform we are on and load the correct adapter
+        //For now this will be hardcoded for testing purposes
+        platformAdapter = new BukkitPlatformAdapter();
+    }
+
+    /**
+     * Get the current platform adapter implementation
+     * @return the current platform adapter
+     */
+    public PlatformAdapter getPlatformAdapter() {
+        return platformAdapter;
+    }
+
+}

+ 13 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/adapters/NBTAdapter.java

@@ -0,0 +1,13 @@
+package com.gmail.nossr50.core.adapters;
+
+import com.gmail.nossr50.core.nbt.NBTBase;
+
+public interface NBTAdapter {
+
+    /**
+     * Transform our NBT type representation to its implementation on the target platform
+     * @param nbtBase target NBT type representation
+     * @return platform specific implementation of our NBT Type
+     */
+    Object asNative(NBTBase nbtBase);
+}

+ 158 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/adapters/NMS_114/BukkitNBTAdapter.java

@@ -0,0 +1,158 @@
+package com.gmail.nossr50.core.adapters.NMS_114;
+
+import com.gmail.nossr50.core.adapters.NBTAdapter;
+import com.gmail.nossr50.core.nbt.NBTBase;
+import com.gmail.nossr50.core.nbt.NBTList;
+import com.gmail.nossr50.core.nbt.*;
+import net.minecraft.server.v1_14_R1.*;
+
+public class BukkitNBTAdapter implements NBTAdapter {
+
+    @Override
+    public Object asNative(NBTBase nbtBase) {
+        switch(nbtBase.getNBTType()) {
+            case END:
+                return new NBTTagEnd();
+            case BYTE:
+                return asNativeNBTByte((NBTByte) nbtBase);
+            case SHORT:
+                return asNativeNBTShort((NBTShort) nbtBase);
+            case INT:
+                return asNativeNBTInt((NBTInt) nbtBase);
+            case LONG:
+                return asNativeNBTLong((NBTLong) nbtBase);
+            case FLOAT:
+                return asNativeNBTFloat((NBTFloat) nbtBase);
+            case DOUBLE:
+                return asNativeNBTDouble((NBTDouble) nbtBase);
+            case BYTE_ARRAY:
+                return asNativeNBTByteArray((NBTByteArray) nbtBase);
+            case STRING:
+                return asNativeNBTString((NBTString) nbtBase);
+            case LIST:
+                return asNativeNBTList((NBTList) nbtBase);
+            case COMPOUND:
+                return asNativeNBTCompound((NBTCompound) nbtBase);
+            case INT_ARRAY:
+                return asNativeNBTIntArray((NBTIntArray) nbtBase);
+            case LONG_ARRAY:
+                return asNativeNBTLongArray((NBTLongArray) nbtBase);
+        }
+
+        return null;
+    }
+
+    /**
+     * Create a NBTTagByte (NMS Type) from our NBTByte representation
+     * @param nbtByte target NBTByte
+     * @return NBTTagByte copy of our NBTByte representation
+     */
+    private NBTTagByte asNativeNBTByte(NBTByte nbtByte) {
+        return new NBTTagByte(nbtByte.getValue());
+    }
+
+    /**
+     * Create a NBTTagShort (NMS Type) from our NBTShort representation
+     * @param nbtShort target NBTShort
+     * @return NBTTagShort copy of our NBTShort representation
+     */
+    private NBTTagShort asNativeNBTShort(NBTShort nbtShort) {
+        return new NBTTagShort(nbtShort.getValue());
+    }
+
+    /**
+     * Create a NBTTagInt (NMS Type) from our NBTInt representation
+     * @param nbtInt target NBTInt
+     * @return NBTTagInt copy of our NBTInt representation
+     */
+    private NBTTagInt asNativeNBTInt(NBTInt nbtInt) {
+        return new NBTTagInt(nbtInt.getValue());
+    }
+
+    /**
+     * Create a NBTTagLong (NMS Type) from our NBTLong representation
+     * @param nbtLong target NBTLong
+     * @return NBTTagLong copy of our NBTLong representation
+     */
+    private NBTTagLong asNativeNBTLong(NBTLong nbtLong) {
+        return new NBTTagLong(nbtLong.getValue());
+    }
+
+    /**
+     * Create a NBTTagFloat (NMS Type) from our NBTFloat representation
+     * @param nbtFloat target NBTFloat
+     * @return NBTTagFloat copy of our NBTFloat representation
+     */
+    private NBTTagFloat asNativeNBTFloat(NBTFloat nbtFloat) {
+        return new NBTTagFloat(nbtFloat.getValue());
+    }
+
+    /**
+     * Create a NBTTagDouble (NMS Type) from our NBTDouble representation
+     * @param nbtDouble target NBTDouble
+     * @return NBTTagDouble copy of our NBTDouble representation
+     */
+    private NBTTagDouble asNativeNBTDouble(NBTDouble nbtDouble) {
+        return new NBTTagDouble(nbtDouble.getValue());
+    }
+
+    /**
+     * Create a NBTTagByteArray (NMS Type) from our NBTByteArray representation
+     * @param nbtByteArray target NBTByteArray
+     * @return NBTTagByteArray copy of our NBTByteArray representation
+     */
+    private NBTTagByteArray asNativeNBTByteArray(NBTByteArray nbtByteArray) {
+        return new NBTTagByteArray(nbtByteArray.getValues());
+    }
+
+    /**
+     * Create a NBTTagString (NMS Type) from our NBTString representation
+     * @param nbtString target NBTString
+     * @return NBTTagString copy of our NBTString representation
+     */
+    private NBTTagString asNativeNBTString(NBTString nbtString) {
+        return new NBTTagString(nbtString.getValue());
+    }
+
+    /**
+     * Create a NBTTagList (NMS Type) from our NBTList representation
+     * @param nbtList target NBTList
+     * @return NBTTagList copy of our NBTList representation
+     */
+    private NBTTagList asNativeNBTList(NBTList nbtList) {
+        NBTTagList nbtTagList = new NBTTagList();
+        nbtList.setValues(nbtList.getValues());
+        return nbtTagList;
+    }
+
+    /**
+     * Create a NBTTagCompound (NMS Type) from our NBTCompound representation
+     * @param nbtCompound target NBTCompound
+     * @return NBTTagCompound copy of our NBTCompound representation
+     */
+    //TODO: Finish
+    private NBTTagCompound asNativeNBTCompound(NBTCompound nbtCompound) {
+        System.out.println("FINISH asNativeNBTCompound()");
+        NBTTagCompound nbtTagCompound = new NBTTagCompound();
+
+        return nbtTagCompound;
+    }
+
+    /**
+     * Create a NBTTagIntArray (NMS Type) from our NBTIntArray representation
+     * @param nbtIntArray target NBTIntArray
+     * @return NBTTagIntArray copy of our NBTIntArray representation
+     */
+    private NBTTagIntArray asNativeNBTIntArray(NBTIntArray nbtIntArray) {
+        return new NBTTagIntArray(nbtIntArray.getValues());
+    }
+
+    /**
+     * Create a NBTTagLongArray (NMS Type) from our NBTLongArray representation
+     * @param nbtLongArray target NBTLongArray
+     * @return NBTTagLongArray copy of our NBTLongArray representation
+     */
+    private NBTTagLongArray asNativeNBTLongArray(NBTLongArray nbtLongArray) {
+        return new NBTTagLongArray(nbtLongArray.getValues());
+    }
+}

+ 11 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/adapters/NMS_114/BukkitPlatformAdapter.java

@@ -0,0 +1,11 @@
+package com.gmail.nossr50.core.adapters.NMS_114;
+
+import com.gmail.nossr50.core.adapters.PlatformAdapter;
+
+public class BukkitPlatformAdapter extends PlatformAdapter {
+
+    public BukkitPlatformAdapter() {
+        super(new BukkitNBTAdapter());
+    }
+
+}

+ 19 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/adapters/PlatformAdapter.java

@@ -0,0 +1,19 @@
+package com.gmail.nossr50.core.adapters;
+
+public abstract class PlatformAdapter {
+
+    private NBTAdapter nbtAdapter; //nbt
+
+    public PlatformAdapter(NBTAdapter nbtAdapter) {
+        this.nbtAdapter = nbtAdapter;
+    }
+
+    /**
+     * Get the NBT Adapter for this platform
+     * @return the platform's NBT adapter
+     */
+    public NBTAdapter getNbtAdapter() {
+        return nbtAdapter;
+    }
+
+}

+ 11 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTBase.java

@@ -0,0 +1,11 @@
+package com.gmail.nossr50.core.nbt;
+
+public interface NBTBase {
+
+    /**
+     * Get the NBTType for this NBTBase
+     * @return this NBTType
+     */
+    NBTType getNBTType();
+
+}

+ 45 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTByte.java

@@ -0,0 +1,45 @@
+package com.gmail.nossr50.core.nbt;
+
+import java.util.Objects;
+
+public class NBTByte implements NBTBase {
+
+    private byte value;
+
+    public NBTByte(byte value) {
+        this.value = value;
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.BYTE;
+    }
+
+    public byte getValue() {
+        return value;
+    }
+
+    public void setValue(byte value) {
+        this.value = value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTByte nbtByte = (NBTByte) o;
+        return value == nbtByte.value;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(value);
+    }
+
+    @Override
+    public String toString() {
+        return "NBTByte{" +
+                "value=" + value +
+                '}';
+    }
+}

+ 49 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTByteArray.java

@@ -0,0 +1,49 @@
+package com.gmail.nossr50.core.nbt;
+
+import java.util.Arrays;
+
+public class NBTByteArray implements NBTBase {
+
+    private byte[] values;
+
+    public NBTByteArray(byte[] values) {
+        this.values = values;
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.BYTE_ARRAY;
+    }
+
+    public int getLength() {
+        return values.length;
+    }
+
+    public byte[] getValues() {
+        return values;
+    }
+
+    public void setValues(byte[] values) {
+        this.values = values;
+    }
+
+    @Override
+    public String toString() {
+        return "NBTByteArray{" +
+                "values=" + Arrays.toString(values) +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTByteArray that = (NBTByteArray) o;
+        return Arrays.equals(values, that.values);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(values);
+    }
+}

+ 65 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTCompound.java

@@ -0,0 +1,65 @@
+package com.gmail.nossr50.core.nbt;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+import java.util.*;
+
+public class NBTCompound implements NBTBase {
+
+    @NonNull
+    private Map<String, NBTBase> tagMap;
+
+    public NBTCompound() {
+        tagMap = new LinkedHashMap<>();
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.COMPOUND;
+    }
+
+    public NBTBase getTag(String key) {
+        return tagMap.get(key);
+    }
+
+    public void addNBT(String tagKey, NBTBase nbt) {
+        tagMap.put(tagKey, nbt);
+    }
+
+    public Collection<NBTBase> getMapValues() {
+        return tagMap.values();
+    }
+
+    public Set<String> getMapKeys() {
+        return tagMap.keySet();
+    }
+
+    public int getMapSize() {
+        return tagMap.size();
+    }
+
+    public void removeEntry(String tagKey) {
+        tagMap.remove(tagKey);
+    }
+
+    @Override
+    public String toString() {
+        return "NBTCompound{" +
+                "tagMap=" + tagMap +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTCompound that = (NBTCompound) o;
+        return tagMap.equals(that.tagMap);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(tagMap);
+    }
+}
+

+ 45 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTDouble.java

@@ -0,0 +1,45 @@
+package com.gmail.nossr50.core.nbt;
+
+import java.util.Objects;
+
+public class NBTDouble implements NBTBase {
+
+    private double value;
+
+    public NBTDouble(double value) {
+        this.value = value;
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.DOUBLE;
+    }
+
+    public double getValue() {
+        return value;
+    }
+
+    public void setValue(double value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "NBTDouble{" +
+                "value=" + value +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTDouble nbtDouble = (NBTDouble) o;
+        return Double.compare(nbtDouble.value, value) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(value);
+    }
+}

+ 8 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTEnd.java

@@ -0,0 +1,8 @@
+package com.gmail.nossr50.core.nbt;
+
+public class NBTEnd implements NBTBase {
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.END;
+    }
+}

+ 45 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTFloat.java

@@ -0,0 +1,45 @@
+package com.gmail.nossr50.core.nbt;
+
+import java.util.Objects;
+
+public class NBTFloat implements NBTBase {
+
+    private float value;
+
+    public NBTFloat(float value) {
+        this.value = value;
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.FLOAT;
+    }
+
+    public float getValue() {
+        return value;
+    }
+
+    public void setValue(float value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "NBTFloat{" +
+                "value=" + value +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTFloat nbtFloat = (NBTFloat) o;
+        return Float.compare(nbtFloat.value, value) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(value);
+    }
+}

+ 45 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTInt.java

@@ -0,0 +1,45 @@
+package com.gmail.nossr50.core.nbt;
+
+import java.util.Objects;
+
+public class NBTInt implements NBTBase {
+
+    private int value;
+
+    public NBTInt(int value) {
+        this.value = value;
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.INT;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public void setValue(int value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "NBTInt{" +
+                "value=" + value +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTInt nbtInt = (NBTInt) o;
+        return value == nbtInt.value;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(value);
+    }
+}

+ 49 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTIntArray.java

@@ -0,0 +1,49 @@
+package com.gmail.nossr50.core.nbt;
+
+import java.util.Arrays;
+
+public class NBTIntArray implements NBTBase {
+
+    private int[] values;
+
+    public NBTIntArray(int[] values) {
+        this.values = values;
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.INT_ARRAY;
+    }
+
+    public int getLength() {
+        return values.length;
+    }
+
+    public int[] getValues() {
+        return values;
+    }
+
+    public void setValues(int[] values) {
+        this.values = values;
+    }
+
+    @Override
+    public String toString() {
+        return "NBTIntArray{" +
+                "values=" + Arrays.toString(values) +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTIntArray that = (NBTIntArray) o;
+        return Arrays.equals(values, that.values);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(values);
+    }
+}

+ 53 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTList.java

@@ -0,0 +1,53 @@
+package com.gmail.nossr50.core.nbt;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+import java.util.List;
+import java.util.Objects;
+
+public class NBTList implements NBTBase {
+
+    @NonNull
+    private List<? extends NBTBase> values;
+
+    public NBTList(@NonNull List<? extends NBTBase> values) {
+        this.values = values;
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.LIST;
+    }
+
+    public int getLength() {
+        return values.size();
+    }
+
+    public List<? extends NBTBase> getValues() {
+        return values;
+    }
+
+    public void setValues(@NonNull List<? extends NBTBase> values) {
+        this.values = values;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTList nbtList = (NBTList) o;
+        return values.equals(nbtList.values);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(values);
+    }
+
+    @Override
+    public String toString() {
+        return "NBTList{" +
+                "values=" + values +
+                '}';
+    }
+}

+ 45 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTLong.java

@@ -0,0 +1,45 @@
+package com.gmail.nossr50.core.nbt;
+
+import java.util.Objects;
+
+public class NBTLong implements NBTBase {
+
+    private long value;
+
+    public NBTLong(long value) {
+        this.value = value;
+    }
+
+    public long getValue() {
+        return value;
+    }
+
+    public void setValue(long value) {
+        this.value = value;
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.LONG;
+    }
+
+    @Override
+    public String toString() {
+        return "NBTLong{" +
+                "value=" + value +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTLong nbtLong = (NBTLong) o;
+        return value == nbtLong.value;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(value);
+    }
+}

+ 49 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTLongArray.java

@@ -0,0 +1,49 @@
+package com.gmail.nossr50.core.nbt;
+
+import java.util.Arrays;
+
+public class NBTLongArray implements NBTBase {
+
+    private long[] values;
+
+    public NBTLongArray(long[] values) {
+        this.values = values;
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.LONG_ARRAY;
+    }
+
+    public int getLength() {
+        return values.length;
+    }
+
+    public long[] getValues() {
+        return values;
+    }
+
+    public void setValues(long[] values) {
+        this.values = values;
+    }
+
+    @Override
+    public String toString() {
+        return "NBTLongArray{" +
+                "values=" + Arrays.toString(values) +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTLongArray that = (NBTLongArray) o;
+        return Arrays.equals(values, that.values);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(values);
+    }
+}

+ 45 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTShort.java

@@ -0,0 +1,45 @@
+package com.gmail.nossr50.core.nbt;
+
+import java.util.Objects;
+
+public class NBTShort implements NBTBase {
+
+    private short value;
+
+    public NBTShort(short value) {
+        this.value = value;
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.SHORT;
+    }
+
+    public short getValue() {
+        return value;
+    }
+
+    public void setValue(short value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "NBTShort{" +
+                "value=" + value +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTShort nbtShort = (NBTShort) o;
+        return value == nbtShort.value;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(value);
+    }
+}

+ 50 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTString.java

@@ -0,0 +1,50 @@
+package com.gmail.nossr50.core.nbt;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Objects;
+
+public class NBTString implements NBTBase {
+
+    @NonNull
+    private String value;
+
+    public NBTString(@NonNull String value) {
+        this.value = value;
+    }
+
+    @Override
+    public NBTType getNBTType() {
+        return NBTType.STRING;
+    }
+
+    @NotNull
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(@NotNull String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "NBTString{" +
+                "value='" + value + '\'' +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        NBTString nbtString = (NBTString) o;
+        return value.equals(nbtString.value);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(value);
+    }
+}

+ 22 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/nbt/NBTType.java

@@ -0,0 +1,22 @@
+package com.gmail.nossr50.core.nbt;
+
+/**
+ * Represents the NBT Type
+ * Based on NBT Structure in 1.14.4
+ */
+public enum NBTType {
+    ////String[] a = new String[]{"END", "BYTE", "SHORT", "INT", "LONG", "FLOAT", "DOUBLE", "BYTE[]", "STRING", "LIST", "COMPOUND", "INT[]", "LONG[]"};
+    END,
+    BYTE,
+    SHORT,
+    INT,
+    LONG,
+    FLOAT,
+    DOUBLE,
+    BYTE_ARRAY,
+    STRING,
+    LIST,
+    COMPOUND,
+    INT_ARRAY,
+    LONG_ARRAY
+}

+ 14 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java

@@ -900,4 +900,18 @@ public class PlayerListener implements Listener {
             }
         }
     }
+
+//    @EventHandler(priority = EventPriority.LOWEST)
+//    public void onDebugPlayerInteract(PlayerInteractEvent event) {
+//        if(pluginRef.getUserManager().getPlayer(event.getPlayer()) != null) {
+//            McMMOPlayer mcMMOPlayer = pluginRef.getUserManager().getPlayer(event.getPlayer());
+//            if(mcMMOPlayer.isDebugMode()) {
+//                switch(event.getAction()) {
+//                    case LEFT_CLICK_AIR:
+//                    case LEFT_CLICK_BLOCK:
+//                        pluginRef.getNbtManager().debugNBTInMainHandItem(event.getPlayer());
+//                }
+//            }
+//        }
+//    }
 }

+ 16 - 2
mcmmo-core/src/main/java/com/gmail/nossr50/mcMMO.java

@@ -11,6 +11,7 @@ import com.gmail.nossr50.config.scoreboard.ConfigScoreboard;
 import com.gmail.nossr50.core.DynamicSettingsManager;
 import com.gmail.nossr50.core.MaterialMapStore;
 import com.gmail.nossr50.core.MetadataConstants;
+import com.gmail.nossr50.core.PlatformManager;
 import com.gmail.nossr50.database.DatabaseManager;
 import com.gmail.nossr50.database.DatabaseManagerFactory;
 import com.gmail.nossr50.datatypes.skills.subskills.acrobatics.Roll;
@@ -33,6 +34,7 @@ import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManagerFactory;
 import com.gmail.nossr50.util.commands.CommandRegistrationManager;
 import com.gmail.nossr50.util.commands.CommandTools;
 import com.gmail.nossr50.util.experience.FormulaManager;
+import com.gmail.nossr50.util.nbt.NBTManager;
 import com.gmail.nossr50.util.player.NotificationManager;
 import com.gmail.nossr50.util.player.PlayerLevelTools;
 import com.gmail.nossr50.util.player.UserManager;
@@ -76,7 +78,7 @@ public class mcMMO extends JavaPlugin {
     private FormulaManager formulaManager;
     private NotificationManager notificationManager;
     private CommandRegistrationManager commandRegistrationManager;
-//    private NBTManager nbtManager;
+    private NBTManager nbtManager;
     private PartyManager partyManager;
     private LocaleManager localeManager;
     private ChatManager chatManager;
@@ -86,6 +88,7 @@ public class mcMMO extends JavaPlugin {
     private ScoreboardManager scoreboardManager;
     private SoundManager soundManager;
     private HardcoreManager hardcoreManager;
+    private PlatformManager platformManager;
     private WorldGuardManager worldGuardManager;
 
     /* Not-Managers but my naming scheme sucks */
@@ -133,6 +136,9 @@ public class mcMMO extends JavaPlugin {
         try {
             getLogger().setFilter(new LogFilter(this));
 
+            //Init PlatformManager
+            platformManager = new PlatformManager(this);
+
             //TODO: Disgusting...
             MetadataConstants.metadataValue = new FixedMetadataValue(this, true);
 
@@ -214,7 +220,7 @@ public class mcMMO extends JavaPlugin {
                 commandRegistrationManager = new CommandRegistrationManager(this);
                 commandRegistrationManager.registerCommands();
 
-//                nbtManager = new NBTManager();
+                nbtManager = new NBTManager();
 
                 //Init Chunk Manager Factory
                 chunkManagerFactory = new ChunkManagerFactory(this);
@@ -854,4 +860,12 @@ public class mcMMO extends JavaPlugin {
     public PerkUtils getPerkUtils() {
         return perkUtils;
     }
+
+    public NBTManager getNbtManager() {
+        return nbtManager;
+    }
+
+    public PlatformManager getPlatformManager() {
+        return platformManager;
+    }
 }

+ 41 - 10
mcmmo-core/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java

@@ -1,6 +1,8 @@
 package com.gmail.nossr50.util.commands;
 
+import co.aikar.commands.BukkitCommandManager;
 import com.gmail.nossr50.commands.*;
+import com.gmail.nossr50.commands.admin.NBTToolsCommand;
 import com.gmail.nossr50.commands.admin.PlayerDebugCommand;
 import com.gmail.nossr50.commands.admin.ReloadLocaleCommand;
 import com.gmail.nossr50.commands.chat.AdminChatCommand;
@@ -30,10 +32,12 @@ import java.util.Locale;
 public final class CommandRegistrationManager {
     private final mcMMO pluginRef;
     private String permissionsMessage;
+    private BukkitCommandManager bukkitCommandManager;
 
     public CommandRegistrationManager(mcMMO pluginRef) {
         this.pluginRef = pluginRef;
         permissionsMessage = pluginRef.getLocaleManager().getString("mcMMO.NoPermission");
+        bukkitCommandManager = new BukkitCommandManager(pluginRef);
     }
 
     private void registerSkillCommands() {
@@ -117,6 +121,40 @@ public final class CommandRegistrationManager {
         }
     }
 
+    /**
+     * Initialize ACF commands
+     */
+    private void initACF() {
+        //TODO: See if needed
+        bukkitCommandManager.enableUnstableAPI("help");
+
+
+        registerACFCommands();
+    }
+
+    /**
+     * Register ACF Commands
+     */
+    private void registerACFCommands() {
+        //Register ACF Commands
+        registerNBTToolsCommand();
+        registerMmoDebugCommand();
+    }
+
+    /**
+     * Register the NBT Tools command
+     */
+    private void registerNBTToolsCommand() {
+        bukkitCommandManager.registerCommand(new NBTToolsCommand());
+    }
+
+    /**
+     * Register the MMO Debug command
+     */
+    private void registerMmoDebugCommand() {
+        bukkitCommandManager.registerCommand(new PlayerDebugCommand());
+    }
+
     private void registerAddlevelsCommand() {
         PluginCommand command = pluginRef.getCommand("addlevels");
         command.setDescription(pluginRef.getLocaleManager().getString("Commands.Description.addlevels"));
@@ -153,16 +191,6 @@ public final class CommandRegistrationManager {
         command.setExecutor(new MmoInfoCommand(pluginRef));
     }
 
-
-    private void registerMmoDebugCommand() {
-        PluginCommand command = pluginRef.getCommand("mmodebug");
-        command.setDescription(pluginRef.getLocaleManager().getString("Commands.Description.mmodebug"));
-        command.setPermission(null); //No perm required to save support headaches
-        command.setPermissionMessage(permissionsMessage);
-        command.setUsage(pluginRef.getLocaleManager().getString("Commands.Usage.0", "mmodebug"));
-        command.setExecutor(new PlayerDebugCommand(pluginRef));
-    }
-
     private void registerMcChatSpyCommand() {
         PluginCommand command = pluginRef.getCommand("mcchatspy");
         command.setDescription(pluginRef.getLocaleManager().getString("Commands.Description.mcchatspy"));
@@ -476,5 +504,8 @@ public final class CommandRegistrationManager {
         registerMcmmoReloadCommand();
         // Admin commands
         registerReloadLocaleCommand();
+
+        //ACF Commands
+        initACF();
     }
 }

+ 71 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/util/nbt/NBTFactory.java

@@ -0,0 +1,71 @@
+package com.gmail.nossr50.util.nbt;
+
+import com.gmail.nossr50.core.nbt.NBTByte;
+import net.minecraft.server.v1_14_R1.NBTTagByte;
+
+public class NBTFactory {
+    //TODO: Finish
+    /**
+     * Converts NMS NBT types into our own NBT type representation
+     * @param nmsNBT target NMS Compound
+     * @return NMS Representation of our NBT
+     */
+//    public NBTCompound asNBT(net.minecraft.server.v1_14_R1.NBTTagCompound nmsNBT) {
+//        NBTCompound nbtCompound = new NBTCompound("");
+//
+//        //Traverse the NMS Map
+//        for(String key : nmsNBT.getKeys()) {
+//
+//        }
+//    }
+
+    //TODO: Finish
+//    /**
+//     * Convert our NBT type into the NMS NBT Type equivalent
+//     * @param nbtCompound target nbt compound
+//     * @return NMS NBT copy of our NBT type
+//     */
+//    public net.minecraft.server.v1_14_R1.NBTTagCompound asNMSCopy(NBTCompound nbtCompound) {
+//
+//    }
+
+    /**
+     * Create a new NMS NBT tag compound with only 1 tag compound named "tag"
+     * @return new NMS NBT tag compound
+     */
+    private net.minecraft.server.v1_14_R1.NBTTagCompound makeNewNMSNBT() {
+        net.minecraft.server.v1_14_R1.NBTTagCompound nbtTagCompound = new net.minecraft.server.v1_14_R1.NBTTagCompound();
+
+        //Add the 'tag' compound where arbitrary data persists
+        nbtTagCompound.set("tag", new net.minecraft.server.v1_14_R1.NBTTagCompound());
+        return nbtTagCompound;
+    }
+
+    //TODO: Finish
+//    private NBTCompound deepCopy(NBTCompound target, String key, net.minecraft.server.v1_14_R1.NBTBase nbtBase) {
+//        switch (nbtBase.getTypeId()) {
+//            case 0:
+//                return new NBTCompound();
+//        }
+//    }
+
+    /**
+     * Create a NBTByte representation of NBTTagByte (NMS Type)
+     * @param nmsNBTByte target NMS NBTTagByte
+     * @return NBTByte representation of the targeted NMS nbt-type
+     */
+    private NBTByte asNBTByte(NBTTagByte nmsNBTByte) {
+        NBTByte nbtByte = new NBTByte(nmsNBTByte.asByte());
+        return nbtByte;
+    }
+
+    /**
+     * Create a NBTTagByte (NMS Type) from our NBTByte representation
+     * @param nbtByte target NBTByte
+     * @return NBTTagByte copy of our NBTByte representation
+     */
+    private NBTTagByte asNBTTagByte(NBTByte nbtByte) {
+        NBTTagByte nbtTagByte = new NBTTagByte(nbtByte.getValue());
+        return nbtTagByte;
+    }
+}

+ 141 - 17
mcmmo-core/src/main/java/com/gmail/nossr50/util/nbt/NBTManager.java

@@ -1,17 +1,21 @@
 package com.gmail.nossr50.util.nbt;
 
 
-import net.minecraft.server.v1_14_R1.NBTBase;
+import com.gmail.nossr50.core.nbt.NBTBase;
 import net.minecraft.server.v1_14_R1.NBTList;
 import net.minecraft.server.v1_14_R1.NBTTagCompound;
-import org.bukkit.Bukkit;
 import org.bukkit.craftbukkit.v1_14_R1.inventory.CraftItemStack;
 import org.bukkit.craftbukkit.v1_14_R1.util.CraftNBTTagConfigSerializer;
+import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 
 public class NBTManager {
 
-    private static final String CRAFT_META_ITEM_CLASS_PATH = "org.bukkit.craftbukkit.inventory.CraftMetaItem";
+    private final String CRAFT_META_ITEM_CLASS_PATH = "org.bukkit.craftbukkit.inventory.CraftMetaItem";
     private Class<?> craftMetaItemClass;
 
     public NBTManager() {
@@ -19,21 +23,133 @@ public class NBTManager {
     }
 
     private void init() {
-        try {
-            Class<?> craftMetaItemClass = Class.forName(CRAFT_META_ITEM_CLASS_PATH); //for type comparisons
-        } catch (ClassNotFoundException e) {
-            e.printStackTrace();
-        }
+//        try {
+//            Class<?> craftMetaItemClass = Class.forName(CRAFT_META_ITEM_CLASS_PATH); //for type comparisons
+//        } catch (ClassNotFoundException e) {
+//            e.printStackTrace();
+//        }
     }
 
-    public static NBTTagCompound getNBT(ItemStack itemStack) {
-        Bukkit.broadcastMessage("Checking NBT for "+itemStack.toString());
+    /**
+     * Used for testing NBT stuff, will be deleted later
+     * @param player target player
+     */
+    //TODO: DELETE
+    //TODO: DELETE
+    //TODO: DELETE
+    //TODO: DELETE
+    //TODO: DELETE
+    //TODO: DELETE
+    //TODO: DELETE
+    //TODO: DELETE
+    //TODO: DELETE
+    //TODO: DELETE
+    //TODO: DELETE
+    //TODO: DELETE
+    //TODO: DELETE
+    public void debugNBTInMainHandItem(Player player) {
+        player.sendMessage("Starting NBT Debug Dump...");
+
+        ItemStack itemStack = player.getInventory().getItemInMainHand();
+        player.sendMessage("Checking NBT for "+itemStack.toString());
+
+        player.sendMessage("Total NBT Entries: "+getNBTCopy(player.getInventory().getItemInMainHand()).getKeys().size());
+        printNBT(player.getInventory().getItemInMainHand(), player);
+        player.sendMessage("-- END OF NBT REPORT --");
+
+        player.sendMessage("Attempting to add NBT key named - Herp");
+        addFloatNBT(player.getInventory().getItemInMainHand(), "herp", 13.37F);
+        player.updateInventory();
+
+        player.sendMessage("(After HERP) Total NBT Entries: "+getNBTCopy(player.getInventory().getItemInMainHand()).getKeys().size());
+        printNBT(player.getInventory().getItemInMainHand(), player);
+        player.sendMessage("-- END OF NBT REPORT --");
+
+        player.sendMessage("Attempting to save NBT data...");
+        player.updateInventory();
+    }
+
+    /**
+     * Gets the NMS.ItemStack Copy of a Bukkit.ItemStack
+     * @param itemStack target bukkit ItemStack
+     * @return the NMS.ItemStack "copy" of the Bukkit ItemStack
+     */
+    public net.minecraft.server.v1_14_R1.ItemStack getNMSItemStack(ItemStack itemStack) {
+        return CraftItemStack.asNMSCopy(itemStack);
+    }
+
+    /**
+     * Copies the NBT off an ItemStack and adds a tag compound if it doesn't exist
+     * @param itemStack target ItemStack
+     * @return the NBT copy of an ItemStack
+     */
+    @NonNull
+    public NBTTagCompound getNBTCopy(ItemStack itemStack) {
         net.minecraft.server.v1_14_R1.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
-        NBTTagCompound rootTag = nmsItemStack.getTag();
-        return rootTag;
+        NBTTagCompound freshNBTCopy = nmsItemStack.save(new NBTTagCompound());
+
+        if(!freshNBTCopy.hasKeyOfType("tag", 10)) {
+            freshNBTCopy.set("tag", new NBTTagCompound());
+        }
+
+        return freshNBTCopy;
+    }
+
+    /**
+     * Adds a Float Value to an ItemStack's NBT
+     * @param itemStack target ItemStack
+     * @param key the key for the new NBT float kv pair
+     * @param value the value of the new NBT float kv pair
+     */
+    public void addFloatNBT(ItemStack itemStack, String key, float value) {
+        //NBT Copied off Item
+        net.minecraft.server.v1_14_R1.ItemStack nmsIS = getNMSItemStack(itemStack);
+        NBTTagCompound freshNBTCopy = getNBTCopy(itemStack);
+
+        //New Float NBT Value
+        NBTTagCompound updatedNBT = new NBTTagCompound();
+        updatedNBT.setFloat(key, value);
+
+        //Merge
+        mergeToTagCompound(freshNBTCopy, updatedNBT);
+
+        //Invoke load() time
+        applyNBT(nmsIS, freshNBTCopy);
+
+        //Apply Item Meta (Not sure if needed)
+        CraftItemStack craftItemStack = CraftItemStack.asCraftMirror(nmsIS);
+        itemStack.setItemMeta(craftItemStack.getItemMeta());
+    }
+
+    /**
+     * Merges the modification compound into the target compound's tag NBT node
+     * @param targetCompound target NBT to merge into
+     * @param modificationCompound data to merge
+     */
+    public void mergeToTagCompound(NBTTagCompound targetCompound, NBTTagCompound modificationCompound) {
+        NBTTagCompound tagCompound = (NBTTagCompound) targetCompound.get("tag");
+        tagCompound.a(modificationCompound);
+    }
+
+    /**
+     * Applies NBT to an NMS.ItemStack
+     * @param nmsItemStack target NMS.ItemStack
+     * @param nbtTagCompound the new NBT data for the NMS.ItemStack
+     */
+    public void applyNBT(net.minecraft.server.v1_14_R1.ItemStack nmsItemStack, NBTTagCompound nbtTagCompound) {
+
+        try {
+            Class clazz = Class.forName("net.minecraft.server.v1_14_R1.ItemStack");
+            Class[] methodParameters = new Class[]{ NBTTagCompound.class };
+            Method loadMethod = clazz.getDeclaredMethod("load", methodParameters);
+            loadMethod.setAccessible(true);
+            loadMethod.invoke(nmsItemStack, nbtTagCompound);
+        } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+            e.printStackTrace();
+        }
     }
 
-    public static NBTBase constructNBT(String nbtString) {
+    public net.minecraft.server.v1_14_R1.NBTBase constructNBT(String nbtString) {
         try {
             return CraftNBTTagConfigSerializer.deserialize(nbtString);
         } catch (Exception e) {
@@ -43,13 +159,21 @@ public class NBTManager {
         }
     }
 
-    public static void printNBT(ItemStack itemStack) {
-        for(String key : getNBT(itemStack).getKeys()) {
-            Bukkit.broadcastMessage("NBT Key found: "+key);
+    /**
+     * Prints all the NBT KV pairs on an ItemStack
+     * @param itemStack target ItemStack
+     * @param player target player to send the message to
+     */
+    public void printNBT(ItemStack itemStack, Player player) {
+        NBTTagCompound tagCompoundCopy = getNBTCopy(itemStack);
+        for(String key : tagCompoundCopy.getKeys()) {
+            player.sendMessage("");
+            player.sendMessage("NBT Key: "+key);
+            player.sendMessage("NBT Value: " + tagCompoundCopy.get(key).asString());
         }
     }
 
-    public static boolean hasNBT(NBTBase nbt, NBTTagCompound otherNbt) {
+    public boolean hasNBT(NBTBase nbt, NBTTagCompound otherNbt) {
         if(nbt instanceof NBTList<?>) {
 
         } else {

+ 8 - 0
mcmmo-core/src/main/resources/plugin.yml

@@ -19,6 +19,9 @@ load: POSTWORLD
 api-version: 1.13
 
 commands:
+    nbttools:
+        description: Modify or Read NBT from an item in hand
+        permission: mcmmo.commands.nbttools
     mmodebug:
         aliases: [mcmmodebugmode]
         description: Toggles a debug mode which will print useful information to chat
@@ -628,6 +631,10 @@ permissions:
         children:
             mcmmo.commands.mcconvert.all: true
             mcmmo.commands.xprate.all: true
+            mcmmo.commands.nbttools: true
+    mcmmo.commands.nbttools:
+        default: false
+        description: Modify or Read NBT of an item in-hand
     mcmmo.bypass.*:
         default: false
         description: Implies all bypass permissions.
@@ -731,6 +738,7 @@ permissions:
             mcmmo.commands.mcmmoreload: true
             mcmmo.commands.mmoedit: true
             mcmmo.commands.mmoedit.others: true
+            mcmmo.commands.nbttools: true
             mcmmo.commands.mmoshowdb: true
             mcmmo.commands.ptp.world.all: true
             mcmmo.commands.reloadlocale: true