Sfoglia il codice sorgente

Fixed possible NPE in our events, and BrewingStands now remember who last interacted with them, this allows for hoppers to be used with Alchemy Fixes #5004 Fixes #4958 Fixes #4641

nossr50 1 anno fa
parent
commit
3ba6b93135
80 ha cambiato i file con 1132 aggiunte e 792 eliminazioni
  1. 39 0
      Changelog.txt
  2. 1 1
      pom.xml
  3. 2 2
      src/main/java/com/gmail/nossr50/commands/skills/AcrobaticsCommand.java
  4. 2 10
      src/main/java/com/gmail/nossr50/commands/skills/AlchemyCommand.java
  5. 2 2
      src/main/java/com/gmail/nossr50/commands/skills/ArcheryCommand.java
  6. 2 3
      src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java
  7. 0 3
      src/main/java/com/gmail/nossr50/commands/skills/CrossbowsCommand.java
  8. 1 2
      src/main/java/com/gmail/nossr50/commands/skills/ExcavationCommand.java
  9. 1 2
      src/main/java/com/gmail/nossr50/commands/skills/FishingCommand.java
  10. 5 5
      src/main/java/com/gmail/nossr50/commands/skills/HerbalismCommand.java
  11. 0 4
      src/main/java/com/gmail/nossr50/commands/skills/MacesCommand.java
  12. 3 4
      src/main/java/com/gmail/nossr50/commands/skills/MiningCommand.java
  13. 2 3
      src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java
  14. 1 2
      src/main/java/com/gmail/nossr50/commands/skills/SalvageCommand.java
  15. 5 4
      src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java
  16. 3 4
      src/main/java/com/gmail/nossr50/commands/skills/SmeltingCommand.java
  17. 2 3
      src/main/java/com/gmail/nossr50/commands/skills/SwordsCommand.java
  18. 1 1
      src/main/java/com/gmail/nossr50/commands/skills/TamingCommand.java
  19. 0 6
      src/main/java/com/gmail/nossr50/commands/skills/TridentsCommand.java
  20. 4 5
      src/main/java/com/gmail/nossr50/commands/skills/UnarmedCommand.java
  21. 2 2
      src/main/java/com/gmail/nossr50/commands/skills/WoodcuttingCommand.java
  22. 5 4
      src/main/java/com/gmail/nossr50/config/skills/alchemy/PotionConfig.java
  23. 8 3
      src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java
  24. 4 3
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/AbstractSubSkill.java
  25. 79 136
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/Roll.java
  26. 7 6
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/SubSkill.java
  27. 26 0
      src/main/java/com/gmail/nossr50/events/skills/McMMOPlayerSkillEvent.java
  28. 11 1
      src/main/java/com/gmail/nossr50/events/skills/abilities/McMMOPlayerAbilityActivateEvent.java
  29. 12 2
      src/main/java/com/gmail/nossr50/events/skills/abilities/McMMOPlayerAbilityDeactivateEvent.java
  30. 23 1
      src/main/java/com/gmail/nossr50/events/skills/abilities/McMMOPlayerAbilityEvent.java
  31. 13 1
      src/main/java/com/gmail/nossr50/events/skills/alchemy/McMMOPlayerBrewEvent.java
  32. 12 1
      src/main/java/com/gmail/nossr50/events/skills/alchemy/McMMOPlayerCatalysisEvent.java
  33. 9 1
      src/main/java/com/gmail/nossr50/events/skills/fishing/McMMOPlayerFishingEvent.java
  34. 14 3
      src/main/java/com/gmail/nossr50/events/skills/fishing/McMMOPlayerFishingTreasureEvent.java
  35. 17 5
      src/main/java/com/gmail/nossr50/events/skills/fishing/McMMOPlayerMagicHunterEvent.java
  36. 38 7
      src/main/java/com/gmail/nossr50/events/skills/secondaryabilities/SubSkillEvent.java
  37. 7 10
      src/main/java/com/gmail/nossr50/listeners/BlockListener.java
  38. 13 14
      src/main/java/com/gmail/nossr50/listeners/EntityListener.java
  39. 57 43
      src/main/java/com/gmail/nossr50/listeners/InventoryListener.java
  40. 0 19
      src/main/java/com/gmail/nossr50/mcMMO.java
  41. 0 49
      src/main/java/com/gmail/nossr50/metadata/BlockMetadataService.java
  42. 0 71
      src/main/java/com/gmail/nossr50/metadata/MetadataService.java
  43. 27 8
      src/main/java/com/gmail/nossr50/runnables/skills/AlchemyBrewCheckTask.java
  44. 56 32
      src/main/java/com/gmail/nossr50/runnables/skills/AlchemyBrewTask.java
  45. 2 1
      src/main/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsManager.java
  46. 53 11
      src/main/java/com/gmail/nossr50/skills/alchemy/AlchemyPotionBrewer.java
  47. 2 2
      src/main/java/com/gmail/nossr50/skills/archery/ArcheryManager.java
  48. 11 5
      src/main/java/com/gmail/nossr50/skills/axes/AxesManager.java
  49. 1 1
      src/main/java/com/gmail/nossr50/skills/crossbows/CrossbowsManager.java
  50. 4 2
      src/main/java/com/gmail/nossr50/skills/excavation/ExcavationManager.java
  51. 3 3
      src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java
  52. 18 19
      src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java
  53. 0 2
      src/main/java/com/gmail/nossr50/skills/maces/MacesManager.java
  54. 2 2
      src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java
  55. 3 3
      src/main/java/com/gmail/nossr50/skills/repair/RepairManager.java
  56. 5 3
      src/main/java/com/gmail/nossr50/skills/salvage/SalvageManager.java
  57. 1 1
      src/main/java/com/gmail/nossr50/skills/smelting/SmeltingManager.java
  58. 2 2
      src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java
  59. 5 3
      src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java
  60. 0 2
      src/main/java/com/gmail/nossr50/skills/tridents/TridentsManager.java
  61. 5 5
      src/main/java/com/gmail/nossr50/skills/unarmed/UnarmedManager.java
  62. 3 3
      src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java
  63. 26 3
      src/main/java/com/gmail/nossr50/util/BlockUtils.java
  64. 116 0
      src/main/java/com/gmail/nossr50/util/ContainerMetadataUtils.java
  65. 38 25
      src/main/java/com/gmail/nossr50/util/EventUtils.java
  66. 11 16
      src/main/java/com/gmail/nossr50/util/ItemMetadataUtils.java
  67. 3 2
      src/main/java/com/gmail/nossr50/util/MetadataConstants.java
  68. 48 0
      src/main/java/com/gmail/nossr50/util/MetadataService.java
  69. 20 19
      src/main/java/com/gmail/nossr50/util/MobMetadataUtils.java
  70. 12 1
      src/main/java/com/gmail/nossr50/util/Permissions.java
  71. 3 1
      src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java
  72. 3 1
      src/main/java/com/gmail/nossr50/util/TransientMetadataTools.java
  73. 181 43
      src/main/java/com/gmail/nossr50/util/random/ProbabilityUtil.java
  74. 8 11
      src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java
  75. 16 0
      src/main/java/com/gmail/nossr50/util/skills/PerksUtils.java
  76. 6 7
      src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java
  77. 0 102
      src/main/java/com/gmail/nossr50/util/skills/SmeltingTracker.java
  78. 3 1
      src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java
  79. 0 1
      src/test/java/com/gmail/nossr50/MMOTestEnvironment.java
  80. 2 1
      src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java

+ 39 - 0
Changelog.txt

@@ -1,3 +1,42 @@
+Version 2.2.010
+    Fixed rare NPE in mcMMO events when player data was unable to be retrieved
+    Fixed a NPE that could happen when damaging armor with Axes
+    Fixed a bug where Alchemy brewing would be cancelled if the player died
+    (API) Added getMcMMOPlayer() to McMMOPlayerSkillEvent
+    (API) Added new ctor McMMOPlayerSkillEvent(@NotNull McMMOPlayer mmoPlayer, @NotNull PrimarySkillType primarySkillType)
+    (API) Deprecated ctor McMMOPlayerSkillEvent(org.bukkit.entity.Player, com.gmail.nossr50.datatypes.skills.PrimarySkillType)
+    (API) Added ctor McMMOPlayerAbilityEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, com.gmail.nossr50.datatypes.skills.PrimarySkillType)
+    (API) Deprecated ctor McMMOPlayerAbilityEvent(org.bukkit.entity.Player, com.gmail.nossr50.datatypes.skills.PrimarySkillType)
+    (API) Deprecated ctor McMMOPlayerAbilityActivateEvent(org.bukkit.entity.Player, com.gmail.nossr50.datatypes.skills.PrimarySkillType)
+    (API) Added ctor McMMOPlayerAbilityActivateEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, com.gmail.nossr50.datatypes.skills.PrimarySkillType)
+    (API) Deprecated ctor McMMOPlayerCatalysisEvent(org.bukkit.entity.Player, double)
+    (API) Added ctor McMMOPlayerCatalysisEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, double)
+    (API) Deprecated util method EventUtils.callPlayerAbilityActivateEvent(org.bukkit.entity.Player, com.gmail.nossr50.datatypes.skills.PrimarySkillType)
+    (API) Added util method EventUtils.callPlayerAbilityActivateEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, com.gmail.nossr50.datatypes.skills.PrimarySkillType)
+    (API) Deprecated ctor McMMOPlayerFishingEvent(org.bukkit.entity.Player)
+    (API) Added ctor McMMOPlayerFishingEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer)
+    (API) Deprecated ctor McMMOPlayerFishingTreasureEvent.McMMOPlayerFishingTreasureEvent(org.bukkit.entity.Player, org.bukkit.inventory.ItemStack, int)
+    (API) Added ctor McMMOPlayerFishingTreasureEvent.McMMOPlayerFishingTreasureEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, org.bukkit.inventory.ItemStack, int)
+    (API) Deprecated ctor McMMOPlayerMagicHunterEvent(org.bukkit.entity.Player, org.bukkit.inventory.ItemStack, int, java.util.Map<org.bukkit.enchantments.Enchantment,java.lang.Integer>)
+    (API) Added ctor McMMOPlayerMagicHunterEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, org.bukkit.inventory.ItemStack, int, java.util.Map<org.bukkit.enchantments.Enchantment,java.lang.Integer>)
+    (API) Deprecated ctor McMMOPlayerAbilityDeactivateEvent(org.bukkit.entity.Player, com.gmail.nossr50.datatypes.skills.PrimarySkillType)
+    (API) Added ctor McMMOPlayerAbilityDeactivateEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, com.gmail.nossr50.datatypes.skills.PrimarySkillType)
+    (API) Deprecated util method EventUtils.callAbilityDeactivateEvent(org.bukkit.entity.Player, com.gmail.nossr50.datatypes.skills.SuperAbilityType)
+    (API) Added util method EventUtils.callAbilityDeactivateEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, com.gmail.nossr50.datatypes.skills.SuperAbilityType)
+    (API) Deprecated util EventUtils.callSubSkillEvent(org.bukkit.entity.Player, com.gmail.nossr50.datatypes.skills.SubSkillType)
+    (API) Added util EventUtils.callSubSkillEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, com.gmail.nossr50.datatypes.skills.SubSkillType)
+    (API) Deprecated ctor SubSkillEvent(org.bukkit.entity.Player, com.gmail.nossr50.datatypes.skills.SubSkillType)
+    (API) Added ctor SubSkillEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, com.gmail.nossr50.datatypes.skills.SubSkillType)
+    (API) Deprecated ctor SubSkillEvent(org.bukkit.entity.Player, com.gmail.nossr50.datatypes.skills.SubSkillType, double)
+    (API) Added ctor SubSkillEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, com.gmail.nossr50.datatypes.skills.SubSkillType, double)
+    (API) Deprecated ctor SubSkillEvent(org.bukkit.entity.Player, com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill)
+    (API) Added ctor SubSkillEvent(com.gmail.nossr50.datatypes.player.McMMOPlayer, com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill)
+    (API) Deprecated ctor AlchemyBrewCheckTask(org.bukkit.entity.Player, org.bukkit.block.BrewingStand)
+    (API) Added ctor AlchemyBrewCheckTask(org.bukkit.block.BrewingStand)
+
+    NOTES:
+    This is not an exhaustive list of API changes in this update, but most of them should be documented here.
+
 Version 2.2.009
     Fixed a bug that prevented mcMMO from loading on MC versions older than 1.20.6
     Dramatically increased the base XP for Alchemy again (see notes)

+ 1 - 1
pom.xml

@@ -2,7 +2,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.gmail.nossr50.mcMMO</groupId>
     <artifactId>mcMMO</artifactId>
-    <version>2.2.009</version>
+    <version>2.2.010-SNAPSHOT</version>
     <name>mcMMO</name>
     <url>https://github.com/mcMMO-Dev/mcMMO</url>
     <scm>

+ 2 - 2
src/main/java/com/gmail/nossr50/commands/skills/AcrobaticsCommand.java

@@ -29,7 +29,7 @@ public class AcrobaticsCommand extends SkillCommand {
     protected void dataCalculations(Player player, float skillValue) {
         // ACROBATICS_DODGE
         if (canDodge) {
-            String[] dodgeStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.ACROBATICS_DODGE);
+            final String[] dodgeStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.ACROBATICS_DODGE);
             dodgeChance = dodgeStrings[0];
             dodgeChanceLucky = dodgeStrings[1];
         }
@@ -56,7 +56,7 @@ public class AcrobaticsCommand extends SkillCommand {
 
             if(abstractSubSkill != null)
             {
-                String[] rollStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.ACROBATICS_ROLL);
+                String[] rollStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.ACROBATICS_ROLL);
 
                 messages.add(getStatMessage(SubSkillType.ACROBATICS_ROLL, rollStrings[0])
                         + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", rollStrings[1]) : ""));

+ 2 - 10
src/main/java/com/gmail/nossr50/commands/skills/AlchemyCommand.java

@@ -5,7 +5,6 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.skills.alchemy.AlchemyManager;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.text.TextComponentFactory;
 import net.kyori.adventure.text.Component;
@@ -30,14 +29,7 @@ public class AlchemyCommand extends SkillCommand {
     }
 
     protected String[] calculateAbilityDisplayValues(Player player) {
-        //TODO: Needed?
-        if(UserManager.getPlayer(player) == null)
-        {
-            player.sendMessage(LocaleLoader.getString("Profile.PendingLoad"));
-            return new String[] {"DATA NOT LOADED", "DATA NOT LOADED"};
-        }
-
-        AlchemyManager alchemyManager = UserManager.getPlayer(player).getAlchemyManager();
+        AlchemyManager alchemyManager = mmoPlayer.getAlchemyManager();
         String[] displayValues = new String[2];
 
         boolean isLucky = Permissions.lucky(player, PrimarySkillType.ALCHEMY);
@@ -59,7 +51,7 @@ public class AlchemyCommand extends SkillCommand {
 
         // ALCHEMY_CONCOCTIONS
         if (canConcoctions) {
-            AlchemyManager alchemyManager = UserManager.getPlayer(player).getAlchemyManager();
+            AlchemyManager alchemyManager = mmoPlayer.getAlchemyManager();
             tier = alchemyManager.getTier();
             ingredientCount = alchemyManager.getIngredients().size();
             ingredientList = alchemyManager.getIngredientList();

+ 2 - 2
src/main/java/com/gmail/nossr50/commands/skills/ArcheryCommand.java

@@ -33,14 +33,14 @@ public class ArcheryCommand extends SkillCommand {
     protected void dataCalculations(Player player, float skillValue) {
         // ARCHERY_ARROW_RETRIEVAL
         if (canRetrieve) {
-            String[] retrieveStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.ARCHERY_ARROW_RETRIEVAL);
+            String[] retrieveStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.ARCHERY_ARROW_RETRIEVAL);
             retrieveChance = retrieveStrings[0];
             retrieveChanceLucky = retrieveStrings[1];
         }
         
         // ARCHERY_DAZE
         if (canDaze) {
-            String[] dazeStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.ARCHERY_DAZE);
+            String[] dazeStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.ARCHERY_DAZE);
             dazeChance = dazeStrings[0];
             dazeChanceLucky = dazeStrings[1];
         }

+ 2 - 3
src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java

@@ -5,7 +5,6 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.skills.axes.Axes;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.random.ProbabilityUtil;
 import com.gmail.nossr50.util.skills.CombatUtils;
 import com.gmail.nossr50.util.skills.RankUtils;
@@ -38,7 +37,7 @@ public class AxesCommand extends SkillCommand {
     protected void dataCalculations(Player player, float skillValue) {
         // ARMOR IMPACT
         if (canImpact) {
-            impactDamage = UserManager.getPlayer(player).getAxesManager().getImpactDurabilityDamage();
+            impactDamage = mmoPlayer.getAxesManager().getImpactDurabilityDamage();
         }
 
         // AXE MASTERY
@@ -48,7 +47,7 @@ public class AxesCommand extends SkillCommand {
         
         // CRITICAL HIT
         if (canCritical) {
-            String[] criticalHitStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.AXES_CRITICAL_STRIKES);
+            String[] criticalHitStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.AXES_CRITICAL_STRIKES);
             critChance = criticalHitStrings[0];
             critChanceLucky = criticalHitStrings[1];
         }

+ 0 - 3
src/main/java/com/gmail/nossr50/commands/skills/CrossbowsCommand.java

@@ -1,9 +1,7 @@
 package com.gmail.nossr50.commands.skills;
 
-import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.skills.CombatUtils;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.text.TextComponentFactory;
@@ -42,7 +40,6 @@ public class CrossbowsCommand extends SkillCommand {
     protected List<String> statsDisplay(Player player, float skillValue, boolean hasEndurance, boolean isLucky) {
         List<String> messages = new ArrayList<>();
 
-        McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
         if (mmoPlayer == null) {
             return messages;
         }

+ 1 - 2
src/main/java/com/gmail/nossr50/commands/skills/ExcavationCommand.java

@@ -5,7 +5,6 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.skills.excavation.ExcavationManager;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.text.TextComponentFactory;
 import net.kyori.adventure.text.Component;
@@ -45,7 +44,7 @@ public class ExcavationCommand extends SkillCommand {
     protected List<String> statsDisplay(Player player, float skillValue, boolean hasEndurance, boolean isLucky) {
         List<String> messages = new ArrayList<>();
 
-        ExcavationManager excavationManager = UserManager.getPlayer(player).getExcavationManager();
+        ExcavationManager excavationManager = mmoPlayer.getExcavationManager();
 
         if (canGigaDrill) {
             messages.add(getStatMessage(SubSkillType.EXCAVATION_GIGA_DRILL_BREAKER, gigaDrillBreakerLength)

+ 1 - 2
src/main/java/com/gmail/nossr50/commands/skills/FishingCommand.java

@@ -8,7 +8,6 @@ import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.skills.fishing.FishingManager;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.random.Probability;
 import com.gmail.nossr50.util.random.ProbabilityUtil;
 import com.gmail.nossr50.util.skills.RankUtils;
@@ -50,7 +49,7 @@ public class FishingCommand extends SkillCommand {
 
     @Override
     protected void dataCalculations(Player player, float skillValue) {
-        FishingManager fishingManager = UserManager.getPlayer(player).getFishingManager();
+        FishingManager fishingManager = mmoPlayer.getFishingManager();
 
         // TREASURE HUNTER
         if (canTreasureHunt) {

+ 5 - 5
src/main/java/com/gmail/nossr50/commands/skills/HerbalismCommand.java

@@ -49,13 +49,13 @@ public class HerbalismCommand extends SkillCommand {
         
         // DOUBLE DROPS
         if (canDoubleDrop) {
-            String[] doubleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.HERBALISM_DOUBLE_DROPS);
+            String[] doubleDropStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.HERBALISM_DOUBLE_DROPS);
             doubleDropChance = doubleDropStrings[0];
             doubleDropChanceLucky = doubleDropStrings[1];
         }
 
         if (canTripleDrop) {
-            String[] tripleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.HERBALISM_VERDANT_BOUNTY);
+            String[] tripleDropStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.HERBALISM_VERDANT_BOUNTY);
             tripleDropChance = tripleDropStrings[0];
             tripleDropChanceLucky = tripleDropStrings[1];
         }
@@ -76,21 +76,21 @@ public class HerbalismCommand extends SkillCommand {
         if (canGreenThumbBlocks || canGreenThumbPlants) {
             greenThumbStage = RankUtils.getRank(player, SubSkillType.HERBALISM_GREEN_THUMB);
 
-            String[] greenThumbStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.HERBALISM_GREEN_THUMB);
+            String[] greenThumbStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.HERBALISM_GREEN_THUMB);
             greenThumbChance = greenThumbStrings[0];
             greenThumbChanceLucky = greenThumbStrings[1];
         }
 
         // HYLIAN LUCK
         if (hasHylianLuck) {
-            String[] hylianLuckStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.HERBALISM_HYLIAN_LUCK);
+            String[] hylianLuckStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.HERBALISM_HYLIAN_LUCK);
             hylianLuckChance = hylianLuckStrings[0];
             hylianLuckChanceLucky = hylianLuckStrings[1];
         }
 
         // SHROOM THUMB
         if (canShroomThumb) {
-            String[] shroomThumbStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.HERBALISM_SHROOM_THUMB);
+            String[] shroomThumbStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.HERBALISM_SHROOM_THUMB);
             shroomThumbChance = shroomThumbStrings[0];
             shroomThumbChanceLucky = shroomThumbStrings[1];
         }

+ 0 - 4
src/main/java/com/gmail/nossr50/commands/skills/MacesCommand.java

@@ -30,10 +30,6 @@
 //    @Override
 //    protected List<String> statsDisplay(Player player, float skillValue, boolean hasEndurance, boolean isLucky) {
 //        List<String> messages = new ArrayList<>();
-//        McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
-//        if (mmoPlayer == null) {
-//            return messages;
-//        }
 //
 //        if(SkillUtils.canUseSubskill(player, MACES_MACES_LIMIT_BREAK)) {
 //            messages.add(getStatMessage(MACES_MACES_LIMIT_BREAK,

+ 3 - 4
src/main/java/com/gmail/nossr50/commands/skills/MiningCommand.java

@@ -5,7 +5,6 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.skills.mining.MiningManager;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.random.ProbabilityUtil;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.text.TextComponentFactory;
@@ -45,7 +44,7 @@ public class MiningCommand extends SkillCommand {
     protected void dataCalculations(Player player, float skillValue) {
         // BLAST MINING
         if (canBlast || canDemoExpert || canBiggerBombs) {
-            MiningManager miningManager = UserManager.getPlayer(player).getMiningManager();
+            MiningManager miningManager = mmoPlayer.getMiningManager();
 
             blastMiningRank = miningManager.getBlastMiningTier();
             bonusTNTDrops = miningManager.getDropMultiplier();
@@ -57,14 +56,14 @@ public class MiningCommand extends SkillCommand {
 
         // Mastery TRIPLE DROPS
         if (canTripleDrop) {
-            String[] masteryTripleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.MINING_MOTHER_LODE);
+            String[] masteryTripleDropStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.MINING_MOTHER_LODE);
             tripleDropChance = masteryTripleDropStrings[0];
             tripleDropChanceLucky = masteryTripleDropStrings[1];
         }
         
         // DOUBLE DROPS
         if (canDoubleDrop) {
-            String[] doubleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.MINING_DOUBLE_DROPS);
+            String[] doubleDropStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.MINING_DOUBLE_DROPS);
             doubleDropChance = doubleDropStrings[0];
             doubleDropChanceLucky = doubleDropStrings[1];
         }

+ 2 - 3
src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java

@@ -10,7 +10,6 @@ import com.gmail.nossr50.skills.repair.Repair;
 import com.gmail.nossr50.skills.repair.RepairManager;
 import com.gmail.nossr50.skills.repair.repairables.Repairable;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.random.ProbabilityUtil;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.text.TextComponentFactory;
@@ -68,7 +67,7 @@ public class RepairCommand extends SkillCommand {
 
         // SUPER REPAIR
         if (canSuperRepair) {
-            String[] superRepairStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.REPAIR_SUPER_REPAIR);
+            String[] superRepairStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.REPAIR_SUPER_REPAIR);
             superRepairChance = superRepairStrings[0];
             superRepairChanceLucky = superRepairStrings[1];
         }
@@ -94,7 +93,7 @@ public class RepairCommand extends SkillCommand {
         List<String> messages = new ArrayList<>();
 
         if (canArcaneForge) {
-            RepairManager repairManager = UserManager.getPlayer(player).getRepairManager();
+            RepairManager repairManager = mmoPlayer.getRepairManager();
 
             messages.add(getStatMessage(false, true,
                     SubSkillType.REPAIR_ARCANE_FORGING,

+ 1 - 2
src/main/java/com/gmail/nossr50/commands/skills/SalvageCommand.java

@@ -6,7 +6,6 @@ import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.skills.salvage.Salvage;
 import com.gmail.nossr50.skills.salvage.SalvageManager;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.text.TextComponentFactory;
 import net.kyori.adventure.text.Component;
@@ -38,7 +37,7 @@ public class SalvageCommand extends SkillCommand {
     @Override
     protected List<String> statsDisplay(Player player, float skillValue, boolean hasEndurance, boolean isLucky) {
         List<String> messages = new ArrayList<>();
-        SalvageManager salvageManager = UserManager.getPlayer(player).getSalvageManager();
+        SalvageManager salvageManager = mmoPlayer.getSalvageManager();
 
         if (canScrapCollector) {
             messages.add(getStatMessage(false, true,

+ 5 - 4
src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java

@@ -34,6 +34,7 @@ public abstract class SkillCommand implements TabExecutor {
 
     protected DecimalFormat percent = new DecimalFormat("##0.00%");
     protected DecimalFormat decimal = new DecimalFormat("##0.00");
+    protected McMMOPlayer mmoPlayer;
 
     private final CommandExecutor skillGuideCommand;
 
@@ -53,9 +54,9 @@ public abstract class SkillCommand implements TabExecutor {
         }
 
         Player player = (Player) sender;
-        McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
+        mmoPlayer = UserManager.getPlayer(player);
 
-        if (mcMMOPlayer == null) {
+        if (mmoPlayer == null) {
             sender.sendMessage(LocaleLoader.getString("Profile.PendingLoad"));
             return true;
         }
@@ -63,7 +64,7 @@ public abstract class SkillCommand implements TabExecutor {
         if (args.length == 0) {
             boolean isLucky = Permissions.lucky(player, skill);
             boolean hasEndurance = PerksUtils.handleActivationPerks(player, 0, 0) != 0;
-            float skillValue = mcMMOPlayer.getSkillLevel(skill);
+            float skillValue = mmoPlayer.getSkillLevel(skill);
 
             //Send the players a few blank lines to make finding the top of the skill command easier
             if (mcMMO.p.getAdvancedConfig().doesSkillCommandSendBlankLines())
@@ -75,7 +76,7 @@ public abstract class SkillCommand implements TabExecutor {
             dataCalculations(player, skillValue);
 
             sendSkillCommandHeader(mcMMO.p.getSkillTools().getLocalizedSkillName(skill),
-                    player, mcMMOPlayer, (int) skillValue);
+                    player, mmoPlayer, (int) skillValue);
 
             //Make JSON text components
             List<Component> subskillTextComponents = getTextComponents(player);

+ 3 - 4
src/main/java/com/gmail/nossr50/commands/skills/SmeltingCommand.java

@@ -4,7 +4,6 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.random.ProbabilityUtil;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.text.TextComponentFactory;
@@ -34,7 +33,7 @@ public class SmeltingCommand extends SkillCommand {
     protected void dataCalculations(Player player, float skillValue) {
         // FUEL EFFICIENCY
         if (canFuelEfficiency) {
-            burnTimeModifier = String.valueOf(UserManager.getPlayer(player).getSmeltingManager().getFuelEfficiencyMultiplier());
+            burnTimeModifier = String.valueOf(mmoPlayer.getSmeltingManager().getFuelEfficiencyMultiplier());
         }
 
         // FLUX MINING
@@ -46,7 +45,7 @@ public class SmeltingCommand extends SkillCommand {
         
         // SECOND SMELT
         if (canSecondSmelt) {
-            String[] secondSmeltStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.SMELTING_SECOND_SMELT);
+            String[] secondSmeltStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.SMELTING_SECOND_SMELT);
             str_secondSmeltChance = secondSmeltStrings[0];
             str_secondSmeltChanceLucky = secondSmeltStrings[1];
         }
@@ -81,7 +80,7 @@ public class SmeltingCommand extends SkillCommand {
 
         if (canUnderstandTheArt) {
             messages.add(getStatMessage(false, true, SubSkillType.SMELTING_UNDERSTANDING_THE_ART,
-                    String.valueOf(UserManager.getPlayer(player).getSmeltingManager().getVanillaXpMultiplier())));
+                    String.valueOf(mmoPlayer.getSmeltingManager().getVanillaXpMultiplier())));
         }
 
         return messages;

+ 2 - 3
src/main/java/com/gmail/nossr50/commands/skills/SwordsCommand.java

@@ -5,7 +5,6 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.random.ProbabilityUtil;
 import com.gmail.nossr50.util.skills.CombatUtils;
 import com.gmail.nossr50.util.skills.RankUtils;
@@ -39,7 +38,7 @@ public class SwordsCommand extends SkillCommand {
     protected void dataCalculations(Player player, float skillValue) {
         // SWORDS_COUNTER_ATTACK
         if (canCounter) {
-            String[] counterStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.SWORDS_COUNTER_ATTACK);
+            String[] counterStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.SWORDS_COUNTER_ATTACK);
             counterChance = counterStrings[0];
             counterChanceLucky = counterStrings[1];
         }
@@ -105,7 +104,7 @@ public class SwordsCommand extends SkillCommand {
         if(SkillUtils.canUseSubskill(player, SubSkillType.SWORDS_STAB))
         {
             messages.add(getStatMessage(SubSkillType.SWORDS_STAB,
-                    String.valueOf(UserManager.getPlayer(player).getSwordsManager().getStabDamage())));
+                    String.valueOf(mmoPlayer.getSwordsManager().getStabDamage())));
         }
 
         if(SkillUtils.canUseSubskill(player, SubSkillType.SWORDS_SWORDS_LIMIT_BREAK)) {

+ 1 - 1
src/main/java/com/gmail/nossr50/commands/skills/TamingCommand.java

@@ -35,7 +35,7 @@ public class TamingCommand extends SkillCommand {
     @Override
     protected void dataCalculations(Player player, float skillValue) {
         if (canGore) {
-            String[] goreStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.TAMING_GORE);
+            String[] goreStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.TAMING_GORE);
             goreChance = goreStrings[0];
             goreChanceLucky = goreStrings[1];
         }

+ 0 - 6
src/main/java/com/gmail/nossr50/commands/skills/TridentsCommand.java

@@ -1,8 +1,6 @@
 package com.gmail.nossr50.commands.skills;
 
-import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.skills.CombatUtils;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import com.gmail.nossr50.util.text.TextComponentFactory;
@@ -32,10 +30,6 @@ public class TridentsCommand extends SkillCommand {
     @Override
     protected List<String> statsDisplay(Player player, float skillValue, boolean hasEndurance, boolean isLucky) {
         List<String> messages = new ArrayList<>();
-        McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
-        if (mmoPlayer == null) {
-            return messages;
-        }
 
         if(SkillUtils.canUseSubskill(player, TRIDENTS_TRIDENTS_LIMIT_BREAK)) {
             messages.add(getStatMessage(TRIDENTS_TRIDENTS_LIMIT_BREAK,

+ 4 - 5
src/main/java/com/gmail/nossr50/commands/skills/UnarmedCommand.java

@@ -4,7 +4,6 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.random.ProbabilityUtil;
 import com.gmail.nossr50.util.skills.CombatUtils;
 import com.gmail.nossr50.util.skills.RankUtils;
@@ -40,7 +39,7 @@ public class UnarmedCommand extends SkillCommand {
     protected void dataCalculations(Player player, float skillValue) {
         // UNARMED_ARROW_DEFLECT
         if (canDeflect) {
-            String[] deflectStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.UNARMED_ARROW_DEFLECT);
+            String[] deflectStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.UNARMED_ARROW_DEFLECT);
             deflectChance = deflectStrings[0];
             deflectChanceLucky = deflectStrings[1];
         }
@@ -54,19 +53,19 @@ public class UnarmedCommand extends SkillCommand {
 
         // UNARMED_DISARM
         if (canDisarm) {
-            String[] disarmStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.UNARMED_DISARM);
+            String[] disarmStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.UNARMED_DISARM);
             disarmChance = disarmStrings[0];
             disarmChanceLucky = disarmStrings[1];
         }
 
         // IRON ARM
         if (canIronArm) {
-            ironArmBonus = UserManager.getPlayer(player).getUnarmedManager().getSteelArmStyleDamage();
+            ironArmBonus = mmoPlayer.getUnarmedManager().getSteelArmStyleDamage();
         }
 
         // IRON GRIP
         if (canIronGrip) {
-            String[] ironGripStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.UNARMED_IRON_GRIP);
+            String[] ironGripStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.UNARMED_IRON_GRIP);
             ironGripChance = ironGripStrings[0];
             ironGripChanceLucky = ironGripStrings[1];
         }

+ 2 - 2
src/main/java/com/gmail/nossr50/commands/skills/WoodcuttingCommand.java

@@ -41,7 +41,7 @@ public class WoodcuttingCommand extends SkillCommand {
 
         //Clean Cuts
         if(canTripleDrop) {
-            String[] tripleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.WOODCUTTING_CLEAN_CUTS);
+            String[] tripleDropStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.WOODCUTTING_CLEAN_CUTS);
             tripleDropChance = tripleDropStrings[0];
             tripleDropChanceLucky = tripleDropStrings[1];
         }
@@ -55,7 +55,7 @@ public class WoodcuttingCommand extends SkillCommand {
     }
 
     private void setDoubleDropClassicChanceStrings(Player player) {
-        String[] doubleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.WOODCUTTING_HARVEST_LUMBER);
+        String[] doubleDropStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.WOODCUTTING_HARVEST_LUMBER);
         doubleDropChance = doubleDropStrings[0];
         doubleDropChanceLucky = doubleDropStrings[1];
     }

+ 5 - 4
src/main/java/com/gmail/nossr50/config/skills/alchemy/PotionConfig.java

@@ -14,15 +14,16 @@ import org.bukkit.inventory.meta.PotionMeta;
 import org.bukkit.potion.PotionEffect;
 import org.bukkit.potion.PotionEffectType;
 import org.bukkit.potion.PotionType;
-import org.codehaus.plexus.util.StringUtils;
 import org.jetbrains.annotations.VisibleForTesting;
 
 import java.io.File;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import static com.gmail.nossr50.util.ItemUtils.setItemName;
-import static com.gmail.nossr50.util.PotionUtil.*;
-import static com.gmail.nossr50.util.text.StringUtils.convertKeyToName;
+import static com.gmail.nossr50.util.PotionUtil.matchPotionType;
 
 public class PotionConfig extends LegacyConfigLoader {
 

+ 8 - 3
src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java

@@ -72,6 +72,9 @@ import java.util.EnumMap;
 import java.util.Map;
 import java.util.UUID;
 
+import static com.gmail.nossr50.util.EventUtils.callPlayerAbilityActivateEvent;
+import static java.util.Objects.requireNonNull;
+
 public class McMMOPlayer implements Identified {
     private final @NotNull Identity identity;
 
@@ -120,6 +123,8 @@ public class McMMOPlayer implements Identified {
     private PrimarySkillType lastSkillShownScoreboard = PrimarySkillType.values()[0];
 
     public McMMOPlayer(Player player, PlayerProfile profile) {
+        requireNonNull(player, "player cannot be null");
+        requireNonNull(profile, "profile cannot be null");
         this.playerName = player.getName();
         UUID uuid = player.getUniqueId();
         identity = Identity.identity(uuid);
@@ -747,11 +752,11 @@ public class McMMOPlayer implements Identified {
      * Players & Profiles
      */
 
-    public Player getPlayer() {
+    public @NotNull Player getPlayer() {
         return player;
     }
 
-    public PlayerProfile getProfile() {
+    public @NotNull PlayerProfile getProfile() {
         return profile;
     }
 
@@ -925,7 +930,7 @@ public class McMMOPlayer implements Identified {
             return;
         }
 
-        if (EventUtils.callPlayerAbilityActivateEvent(player, primarySkillType).isCancelled()) {
+        if (callPlayerAbilityActivateEvent(this, primarySkillType).isCancelled()) {
             return;
         }
 

+ 4 - 3
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/AbstractSubSkill.java

@@ -1,6 +1,7 @@
 package com.gmail.nossr50.datatypes.skills.subskills;
 
 import com.gmail.nossr50.config.CoreSkillsConfig;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.subskills.interfaces.Interaction;
 import com.gmail.nossr50.datatypes.skills.subskills.interfaces.Rank;
@@ -48,13 +49,13 @@ public abstract class AbstractSubSkill implements SubSkill, Interaction, Rank, S
     /**
      * Prints detailed info about this subskill to the player
      *
-     * @param player the target player
+     * @param mmoPlayer the target player
      */
     @Override
-    public void printInfo(Player player) {
+    public void printInfo(McMMOPlayer mmoPlayer) {
         /* DEFAULT SETTINGS PRINT THE BARE MINIMUM */
 
-        //TextComponentFactory.sendPlayerUrlHeader(player);
+        final Player player = mmoPlayer.getPlayer();
         player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.Header"));
         player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.SubSkillHeader", getConfigKeyName()));
         player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.DetailsHeader"));

+ 79 - 136
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/Roll.java

@@ -4,7 +4,6 @@ import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.datatypes.experience.XPGainReason;
 import com.gmail.nossr50.datatypes.interactions.NotificationType;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
-import com.gmail.nossr50.datatypes.player.PlayerProfile;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
@@ -13,7 +12,6 @@ import com.gmail.nossr50.util.EventUtils;
 import com.gmail.nossr50.util.ItemUtils;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.player.NotificationManager;
-import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.random.Probability;
 import com.gmail.nossr50.util.random.ProbabilityUtil;
 import com.gmail.nossr50.util.skills.PerksUtils;
@@ -67,25 +65,25 @@ public class Roll extends AcrobaticsSubSkill {
             return false;
 
         if (entityDamageEvent.getCause() == EntityDamageEvent.DamageCause.FALL) {//Grab the player
-            McMMOPlayer mcMMOPlayer = EventUtils.getMcMMOPlayer(entityDamageEvent.getEntity());
+            McMMOPlayer mmoPlayer = EventUtils.getMcMMOPlayer(entityDamageEvent.getEntity());
 
-            if (mcMMOPlayer == null)
+            if (mmoPlayer == null)
                 return false;
 
             /*
              * Check for success
              */
-            Player player = (Player) ((EntityDamageEvent) event).getEntity();
-            if (canRoll(player)) {
-                entityDamageEvent.setDamage(rollCheck(player, mcMMOPlayer, entityDamageEvent.getFinalDamage()));
+            
+            if (canRoll(mmoPlayer)) {
+                entityDamageEvent.setDamage(rollCheck(mmoPlayer, entityDamageEvent.getFinalDamage()));
 
                 if (entityDamageEvent.getFinalDamage() == 0) {
                     entityDamageEvent.setCancelled(true);
                     return true;
                 }
-            } else if(mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.ACROBATICS)) {
+            } else if(mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(mmoPlayer.getPlayer(), PrimarySkillType.ACROBATICS)) {
                 //Give XP Anyways
-                SkillUtils.applyXpGain(mcMMOPlayer, getPrimarySkill(), calculateRollXP(player, ((EntityDamageEvent) event).getFinalDamage(), false), XPGainReason.PVE);
+                SkillUtils.applyXpGain(mmoPlayer, getPrimarySkill(), calculateRollXP(mmoPlayer, ((EntityDamageEvent) event).getFinalDamage(), false), XPGainReason.PVE);
             }
         }
 
@@ -117,25 +115,24 @@ public class Roll extends AcrobaticsSubSkill {
      * Adds detailed stats specific to this skill
      *
      * @param componentBuilder target component builder
-     * @param player target player
+     * @param mmoPlayer target player
      */
     @Override
-    public void addStats(TextComponent.Builder componentBuilder, Player player) {
+    public void addStats(TextComponent.Builder componentBuilder, McMMOPlayer mmoPlayer) {
         String rollChance, rollChanceLucky, gracefulRollChance, gracefulRollChanceLucky;
 
         /* Values related to the player */
-        PlayerProfile playerProfile = UserManager.getPlayer(player).getProfile();
-        float skillValue = playerProfile.getSkillLevel(getPrimarySkill());
-        boolean isLucky = Permissions.lucky(player, getPrimarySkill());
+        float skillValue = mmoPlayer.getSkillLevel(getPrimarySkill());
+        boolean isLucky = Permissions.lucky(mmoPlayer.getPlayer(), getPrimarySkill());
 
-        String[] rollStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.ACROBATICS_ROLL);
+        String[] rollStrings = ProbabilityUtil.getRNGDisplayValues(mmoPlayer, SubSkillType.ACROBATICS_ROLL);
         rollChance = rollStrings[0];
         rollChanceLucky = rollStrings[1];
 
         /*
          * Graceful is double the odds of a normal roll
          */
-        Probability probability = getRollProbability(player);
+        Probability probability = getRollProbability(mmoPlayer);
         Probability gracefulProbability = Probability.ofValue(probability.getValue() * 2);
         String[] gracefulRollStrings = ProbabilityUtil.getRNGDisplayValues(gracefulProbability);
         gracefulRollChance = gracefulRollStrings[0];
@@ -169,8 +166,8 @@ public class Roll extends AcrobaticsSubSkill {
     }
 
     @NotNull
-    private Probability getRollProbability(Player player) {
-        return ProbabilityUtil.getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, player);
+    private Probability getRollProbability(McMMOPlayer mmoPlayer) {
+        return ProbabilityUtil.getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, mmoPlayer);
     }
 
     @Override
@@ -188,8 +185,9 @@ public class Roll extends AcrobaticsSubSkill {
         return true;
     }
 
-    private boolean canRoll(Player player) {
-        return RankUtils.hasUnlockedSubskill(player, SubSkillType.ACROBATICS_ROLL) && Permissions.isSubSkillEnabled(player, SubSkillType.ACROBATICS_ROLL);
+    private boolean canRoll(McMMOPlayer mmoPlayer) {
+        return RankUtils.hasUnlockedSubskill(mmoPlayer.getPlayer(), SubSkillType.ACROBATICS_ROLL)
+                && Permissions.isSubSkillEnabled(mmoPlayer.getPlayer(), SubSkillType.ACROBATICS_ROLL);
     }
 
     /**
@@ -199,43 +197,42 @@ public class Roll extends AcrobaticsSubSkill {
      * @return the modified event damage if the ability was successful, the original event damage otherwise
      */
     @VisibleForTesting
-    public double rollCheck(Player player, McMMOPlayer mcMMOPlayer, double damage) {
+    public double rollCheck(McMMOPlayer mmoPlayer, double damage) {
+        int skillLevel = mmoPlayer.getSkillLevel(getPrimarySkill());
 
-        int skillLevel = mcMMOPlayer.getSkillLevel(getPrimarySkill());
-
-        if (player.isSneaking()) {
-            return gracefulRollCheck(player, mcMMOPlayer, damage, skillLevel);
+        if (mmoPlayer.getPlayer().isSneaking()) {
+            return gracefulRollCheck(mmoPlayer, damage, skillLevel);
         }
 
         double modifiedDamage = calculateModifiedRollDamage(damage, mcMMO.p.getAdvancedConfig().getRollDamageThreshold());
 
-        if (!isFatal(player, modifiedDamage)
-                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ACROBATICS_ROLL, player)) {
-            NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Acrobatics.Roll.Text");
-            SoundManager.sendCategorizedSound(player, player.getLocation(), SoundType.ROLL_ACTIVATED, SoundCategory.PLAYERS);
-            //player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.Text"));
+        if (!isFatal(mmoPlayer, modifiedDamage)
+                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ACROBATICS_ROLL, mmoPlayer)) {
+            NotificationManager.sendPlayerInformation(mmoPlayer.getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Acrobatics.Roll.Text");
+            SoundManager.sendCategorizedSound(mmoPlayer.getPlayer(), mmoPlayer.getPlayer().getLocation(), SoundType.ROLL_ACTIVATED, SoundCategory.PLAYERS);
+            //mmoPlayer.getPlayer().sendMessage(LocaleLoader.getString("Acrobatics.Roll.Text"));
 
-            //if (!SkillUtils.cooldownExpired((long) mcMMOPlayer.getTeleportATS(), Config.getInstance().getXPAfterTeleportCooldown())) {
-            if(!isExploiting(player) && mcMMOPlayer.getAcrobaticsManager().canGainRollXP())
-                SkillUtils.applyXpGain(mcMMOPlayer, getPrimarySkill(), calculateRollXP(player, damage, true), XPGainReason.PVE);
+            //if (!SkillUtils.cooldownExpired((long) mcMMOmmoPlayer.getPlayer().getTeleportATS(), Config.getInstance().getXPAfterTeleportCooldown())) {
+            if(!isExploiting(mmoPlayer) && mmoPlayer.getAcrobaticsManager().canGainRollXP())
+                SkillUtils.applyXpGain(mmoPlayer, getPrimarySkill(), calculateRollXP(mmoPlayer, damage, true), XPGainReason.PVE);
             //}
 
-            addFallLocation(player);
+            addFallLocation(mmoPlayer);
             return modifiedDamage;
         }
-        else if (!isFatal(player, damage)) {
-            //if (!SkillUtils.cooldownExpired((long) mcMMOPlayer.getTeleportATS(), Config.getInstance().getXPAfterTeleportCooldown())) {
-            if(!isExploiting(player) && mcMMOPlayer.getAcrobaticsManager().canGainRollXP())
-                SkillUtils.applyXpGain(mcMMOPlayer, getPrimarySkill(), calculateRollXP(player, damage, false), XPGainReason.PVE);
+        else if (!isFatal(mmoPlayer, damage)) {
+            //if (!SkillUtils.cooldownExpired((long) mmoPlayer.getTeleportATS(), Config.getInstance().getXPAfterTeleportCooldown())) {
+            if(!isExploiting(mmoPlayer) && mmoPlayer.getAcrobaticsManager().canGainRollXP())
+                SkillUtils.applyXpGain(mmoPlayer, getPrimarySkill(), calculateRollXP(mmoPlayer, damage, false), XPGainReason.PVE);
             //}
         }
 
-        addFallLocation(player);
+        addFallLocation(mmoPlayer);
         return damage;
     }
 
-    private int getActivationChance(McMMOPlayer mcMMOPlayer) {
-        return PerksUtils.handleLuckyPerks(mcMMOPlayer.getPlayer(), getPrimarySkill());
+    private int getActivationChance(McMMOPlayer mmoPlayer) {
+        return PerksUtils.handleLuckyPerks(mmoPlayer, getPrimarySkill());
     }
 
     /**
@@ -244,36 +241,36 @@ public class Roll extends AcrobaticsSubSkill {
      * @param damage The amount of damage initially dealt by the event
      * @return the modified event damage if the ability was successful, the original event damage otherwise
      */
-    private double gracefulRollCheck(Player player, McMMOPlayer mcMMOPlayer, double damage, int skillLevel) {
+    private double gracefulRollCheck(McMMOPlayer mmoPlayer, double damage, int skillLevel) {
         double modifiedDamage = calculateModifiedRollDamage(damage, mcMMO.p.getAdvancedConfig().getRollDamageThreshold() * 2);
 
-        Probability gracefulProbability = getGracefulProbability(player);
+        final Probability gracefulProbability = getGracefulProbability(mmoPlayer);
 
-        if (!isFatal(player, modifiedDamage)
+        if (!isFatal(mmoPlayer, modifiedDamage)
                 //TODO: Graceful isn't sending out an event
-                && ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.ACROBATICS, player, gracefulProbability))
+                && ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.ACROBATICS, mmoPlayer, gracefulProbability))
         {
-            NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Acrobatics.Ability.Proc");
-            SoundManager.sendCategorizedSound(player, player.getLocation(), SoundType.ROLL_ACTIVATED, SoundCategory.PLAYERS,0.5F);
-            if(!isExploiting(player) && mcMMOPlayer.getAcrobaticsManager().canGainRollXP())
-                SkillUtils.applyXpGain(mcMMOPlayer, getPrimarySkill(), calculateRollXP(player, damage, true), XPGainReason.PVE);
+            NotificationManager.sendPlayerInformation(mmoPlayer.getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Acrobatics.Ability.Proc");
+            SoundManager.sendCategorizedSound(mmoPlayer.getPlayer(), mmoPlayer.getPlayer().getLocation(), SoundType.ROLL_ACTIVATED, SoundCategory.PLAYERS,0.5F);
+            if(!isExploiting(mmoPlayer) && mmoPlayer.getAcrobaticsManager().canGainRollXP())
+                SkillUtils.applyXpGain(mmoPlayer, getPrimarySkill(), calculateRollXP(mmoPlayer, damage, true), XPGainReason.PVE);
 
-            addFallLocation(player);
+            addFallLocation(mmoPlayer);
             return modifiedDamage;
         }
-        else if (!isFatal(player, damage)) {
-            if(!isExploiting(player) && mcMMOPlayer.getAcrobaticsManager().canGainRollXP())
-                SkillUtils.applyXpGain(mcMMOPlayer, getPrimarySkill(), calculateRollXP(player, damage, false), XPGainReason.PVE);
+        else if (!isFatal(mmoPlayer, damage)) {
+            if(!isExploiting(mmoPlayer) && mmoPlayer.getAcrobaticsManager().canGainRollXP())
+                SkillUtils.applyXpGain(mmoPlayer, getPrimarySkill(), calculateRollXP(mmoPlayer, damage, false), XPGainReason.PVE);
             
-            addFallLocation(player);
+            addFallLocation(mmoPlayer);
         }
 
         return damage;
     }
 
     @NotNull
-    public static Probability getGracefulProbability(Player player) {
-        double gracefulOdds = ProbabilityUtil.getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, player).getValue() * 2;
+    public static Probability getGracefulProbability(McMMOPlayer mmoPlayer) {
+        double gracefulOdds = ProbabilityUtil.getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, mmoPlayer).getValue() * 2;
         return Probability.ofValue(gracefulOdds);
     }
 
@@ -283,24 +280,22 @@ public class Roll extends AcrobaticsSubSkill {
      *
      * @return true if exploits are detected, false otherwise
      */
-    private boolean isExploiting(Player player) {
+    private boolean isExploiting(McMMOPlayer mmoPlayer) {
         if (!ExperienceConfig.getInstance().isAcrobaticsExploitingPrevented()) {
             return false;
         }
 
-        McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
-
-        if (ItemUtils.hasItemInEitherHand(player, Material.ENDER_PEARL) || player.isInsideVehicle()) {
-            if(mcMMOPlayer.isDebugMode()) {
-                mcMMOPlayer.getPlayer().sendMessage("Acrobatics XP Prevented: Ender Pearl or Inside Vehicle");
+        if (ItemUtils.hasItemInEitherHand(mmoPlayer.getPlayer(), Material.ENDER_PEARL) || mmoPlayer.getPlayer().isInsideVehicle()) {
+            if(mmoPlayer.isDebugMode()) {
+                mmoPlayer.getPlayer().sendMessage("Acrobatics XP Prevented: Ender Pearl or Inside Vehicle");
             }
             return true;
         }
 
-        if(UserManager.getPlayer(player).getAcrobaticsManager().hasFallenInLocationBefore(getBlockLocation(player)))
+        if(mmoPlayer.getAcrobaticsManager().hasFallenInLocationBefore(getBlockLocation(mmoPlayer)))
         {
-            if(mcMMOPlayer.isDebugMode()) {
-                mcMMOPlayer.getPlayer().sendMessage("Acrobatics XP Prevented: Fallen in location before");
+            if(mmoPlayer.isDebugMode()) {
+                mmoPlayer.getPlayer().sendMessage("Acrobatics XP Prevented: Fallen in location before");
             }
 
             return true;
@@ -309,11 +304,11 @@ public class Roll extends AcrobaticsSubSkill {
         return false; //NOT EXPLOITING
     }
 
-    private float calculateRollXP(Player player, double damage, boolean isRoll) {
+    private float calculateRollXP(McMMOPlayer mmoPlayer, double damage, boolean isRoll) {
         //Clamp Damage to account for insane DRs
         damage = Math.min(20, damage);
 
-        ItemStack boots = player.getInventory().getBoots();
+        ItemStack boots = mmoPlayer.getPlayer().getInventory().getBoots();
         float xp = (float) (damage * (isRoll ? ExperienceConfig.getInstance().getRollXPModifier() : ExperienceConfig.getInstance().getFallXPModifier()));
 
         if (boots != null && boots.containsEnchantment(mcMMO.p.getEnchantmentMapper().getFeatherFalling())) {
@@ -327,8 +322,8 @@ public class Roll extends AcrobaticsSubSkill {
         return Math.max(damage - damageThreshold, 0.0);
     }
 
-    private boolean isFatal(Player player, double damage) {
-        return player.getHealth() - damage <= 0;
+    private boolean isFatal(McMMOPlayer mmoPlayer, double damage) {
+        return mmoPlayer.getPlayer().getHealth() - damage <= 0;
     }
 
     /**
@@ -344,103 +339,51 @@ public class Roll extends AcrobaticsSubSkill {
     /**
      * Prints detailed info about this subskill to the player
      *
-     * @param player the target player
+     * @param mmoPlayer the target player
      */
     @Override
-    public void printInfo(Player player) {
+    public void printInfo(McMMOPlayer mmoPlayer) {
         //Header
-        super.printInfo(player);
+        super.printInfo(mmoPlayer);
 
         //Start the description string.
         //player.sendMessage(getDescription());
         //Player stats
-        player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.Stats",
-                            LocaleLoader.getString("Acrobatics.SubSkill.Roll.Stats", getStats(player))));
+        mmoPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.MmoInfo.Stats",
+                            LocaleLoader.getString("Acrobatics.SubSkill.Roll.Stats", getStats(mmoPlayer))));
 
         //Mechanics
-        player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.Mechanics"));
-        player.sendMessage(getMechanics());
+        mmoPlayer.getPlayer().sendMessage(LocaleLoader.getString("Commands.MmoInfo.Mechanics"));
+        mmoPlayer.getPlayer().sendMessage(getMechanics());
     }
 
-    /**
-     * Returns a collection of strings about how a skill works
-     * Used in the MMO Info command
-     *
-     * @return
-     */
     @Override
     public String getMechanics() {
-        //Vars passed to locale
-        //0 = chance to roll at half max level
-        //1 = chance to roll with grace at half max level
-        //2 = level where maximum bonus is reached
-        //3 = additive chance to succeed per level
-        //4 = damage threshold when rolling
-        //5 = damage threshold when rolling with grace
-        //6 = half of level where maximum bonus is reached
-        /*
-        Roll:
-            # ChanceMax: Maximum chance of rolling when on <MaxBonusLevel> or higher
-            # MaxBonusLevel: On this level or higher, the roll chance will not go higher than <ChanceMax>
-            # DamageThreshold: The max damage a player can negate with a roll
-            ChanceMax: 100.0
-            MaxBonusLevel: 100
-            DamageThreshold: 7.0
-         */
-
         return "Under Construction: This will work in a future update.";
-//
-//        double rollChanceHalfMax, graceChanceHalfMax, damageThreshold, chancePerLevel;
-//
-//        //Chance to roll at half max skill
-//        RandomChanceSkill rollHalfMaxSkill = new RandomChanceSkill(null, subSkillType);
-//        int halfMaxSkillValue = mcMMO.p.getAdvancedConfig().getMaxBonusLevel(SubSkillType.ACROBATICS_ROLL)/2;
-//        rollHalfMaxSkill.setSkillLevel(halfMaxSkillValue);
-//
-//        //Chance to graceful roll at full skill
-//        RandomChanceSkill rollGraceHalfMaxSkill = new RandomChanceSkill(null, subSkillType);
-//        rollGraceHalfMaxSkill.setSkillLevel(halfMaxSkillValue * 2); //Double the effective odds
-//
-//        //Chance to roll per level
-//        RandomChanceSkill rollOneSkillLevel = new RandomChanceSkill(null, subSkillType);
-//        rollGraceHalfMaxSkill.setSkillLevel(1); //Level 1 skill
-//
-//        //Chance Stat Calculations
-//        rollChanceHalfMax       = RandomChanceUtil.getRandomChanceExecutionChance(rollHalfMaxSkill);
-//        graceChanceHalfMax      = RandomChanceUtil.getRandomChanceExecutionChance(rollGraceHalfMaxSkill);
-//        damageThreshold         = mcMMO.p.getAdvancedConfig().getRollDamageThreshold();
-//
-//        chancePerLevel          = RandomChanceUtil.getRandomChanceExecutionChance(rollOneSkillLevel);
-//
-//        double maxLevel         = mcMMO.p.getAdvancedConfig().getMaxBonusLevel(SubSkillType.ACROBATICS_ROLL);
-//
-//        return LocaleLoader.getString("Acrobatics.SubSkill.Roll.Mechanics", rollChanceHalfMax, graceChanceHalfMax, maxLevel, chancePerLevel, damageThreshold, damageThreshold * 2,halfMaxSkillValue);
     }
 
     /**
      * Get an array of various stats for a player
      *
-     * @param player target player
+     * @param mmoPlayer target player
      * @return stat array for target player for this skill
      */
     @Override
-    public Double[] getStats(Player player)
+    public Double[] getStats(McMMOPlayer mmoPlayer)
     {
-        double playerChanceRoll = ProbabilityUtil.getSubSkillProbability(subSkillType, player).getValue();
+        double playerChanceRoll = ProbabilityUtil.getSubSkillProbability(subSkillType, mmoPlayer).getValue();
         double playerChanceGrace = playerChanceRoll * 2;
 
-        double gracefulOdds = ProbabilityUtil.getSubSkillProbability(subSkillType, player).getValue() * 2;
+        double gracefulOdds = ProbabilityUtil.getSubSkillProbability(subSkillType, mmoPlayer).getValue() * 2;
 
         return new Double[]{ playerChanceRoll, playerChanceGrace };
     }
 
-    public void addFallLocation(@NotNull Player player)
-    {
-        UserManager.getPlayer(player).getAcrobaticsManager().addLocationToFallMap(getBlockLocation(player));
+    public void addFallLocation(@NotNull McMMOPlayer mmoPlayer) {
+        mmoPlayer.getAcrobaticsManager().addLocationToFallMap(getBlockLocation(mmoPlayer));
     }
 
-    public @NotNull Location getBlockLocation(@NotNull Player player)
-    {
-        return player.getLocation().getBlock().getLocation();
+    public @NotNull Location getBlockLocation(@NotNull McMMOPlayer mmoPlayer) {
+        return mmoPlayer.getPlayer().getLocation().getBlock().getLocation();
     }
 }

+ 7 - 6
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/SubSkill.java

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.datatypes.skills.subskills.interfaces;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.interfaces.Skill;
 import net.kyori.adventure.text.TextComponent;
 import org.bukkit.entity.Player;
@@ -19,10 +20,10 @@ public interface SubSkill extends Skill {
 
     /**
      * Get an array of various stats for a player
-     * @param player target player
+     * @param mmoPlayer target player
      * @return stat array for target player for this skill
      */
-    Double[] getStats(Player player);
+    Double[] getStats(McMMOPlayer mmoPlayer);
 
     /**
      * Checks if a player has permission to use this skill
@@ -59,9 +60,9 @@ public interface SubSkill extends Skill {
     /**
      * Adds detailed stats specific to this skill
      * @param componentBuilder target component builder
-     * @param player owner of this skill
+     * @param mmoPlayer owner of this skill
      */
-    void addStats(TextComponent.Builder componentBuilder, Player player);
+    void addStats(TextComponent.Builder componentBuilder, McMMOPlayer mmoPlayer);
 
     /**
      * Whether this subskill is enabled
@@ -71,7 +72,7 @@ public interface SubSkill extends Skill {
 
     /**
      * Prints detailed info about this subskill to the player
-     * @param player the target player
+     * @param mmoPlayer the target player
      */
-    void printInfo(Player player);
+    void printInfo(McMMOPlayer mmoPlayer);
 }

+ 26 - 0
src/main/java/com/gmail/nossr50/events/skills/McMMOPlayerSkillEvent.java

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.events.skills;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.entity.Player;
@@ -7,19 +8,35 @@ import org.bukkit.event.HandlerList;
 import org.bukkit.event.player.PlayerEvent;
 import org.jetbrains.annotations.NotNull;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * Generic event for mcMMO skill handling.
  */
 public abstract class McMMOPlayerSkillEvent extends PlayerEvent {
     protected @NotNull PrimarySkillType skill;
     protected int skillLevel;
+    protected McMMOPlayer mmoPlayer;
 
+    @Deprecated(forRemoval = true, since = "2.2.010")
     protected McMMOPlayerSkillEvent(@NotNull Player player, @NotNull PrimarySkillType skill) {
         super(player);
+        McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
+        requireNonNull(mmoPlayer, "Player not found in UserManager," +
+                "contact the dev and tell them to use the constructor for" +
+                " McMMOPlayerSkillEvent(McMMOPlayer, PrimarySkillType) instead");
         this.skill = skill;
         this.skillLevel = UserManager.getPlayer(player).getSkillLevel(skill);
     }
 
+    protected McMMOPlayerSkillEvent(@NotNull McMMOPlayer mmoPlayer, @NotNull PrimarySkillType primarySkillType) {
+        super(mmoPlayer.getPlayer());
+        requireNonNull(mmoPlayer, "mmoPlayer cannot be null");
+        requireNonNull(primarySkillType, "primarySkillType cannot be null");
+        this.skill = primarySkillType;
+        this.skillLevel = mmoPlayer.getSkillLevel(primarySkillType);
+    }
+
     /**
      * @return The skill involved in this event
      */
@@ -37,6 +54,15 @@ public abstract class McMMOPlayerSkillEvent extends PlayerEvent {
     /** Rest of file is required boilerplate for custom events **/
     private static final HandlerList handlers = new HandlerList();
 
+    /**
+     * Returns the {@link McMMOPlayer} associated with this event.
+     *
+     * @return The {@link McMMOPlayer} associated with this event.
+     */
+    public @NotNull McMMOPlayer getMcMMOPlayer() {
+        return mmoPlayer;
+    }
+
     @Override
     public @NotNull HandlerList getHandlers() {
         return handlers;

+ 11 - 1
src/main/java/com/gmail/nossr50/events/skills/abilities/McMMOPlayerAbilityActivateEvent.java

@@ -1,14 +1,24 @@
 package com.gmail.nossr50.events.skills.abilities;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.entity.Player;
 import org.bukkit.event.Cancellable;
 
+import java.util.Objects;
+
 public class McMMOPlayerAbilityActivateEvent extends McMMOPlayerAbilityEvent implements Cancellable {
     private boolean cancelled;
 
+    @Deprecated(forRemoval = true, since = "2.2.010")
     public McMMOPlayerAbilityActivateEvent(Player player, PrimarySkillType skill) {
-        super(player, skill);
+        super(Objects.requireNonNull(UserManager.getPlayer(player)), skill);
+        cancelled = false;
+    }
+
+    public McMMOPlayerAbilityActivateEvent(McMMOPlayer mmoPlayer, PrimarySkillType skill) {
+        super(mmoPlayer, skill);
         cancelled = false;
     }
 

+ 12 - 2
src/main/java/com/gmail/nossr50/events/skills/abilities/McMMOPlayerAbilityDeactivateEvent.java

@@ -1,10 +1,20 @@
 package com.gmail.nossr50.events.skills.abilities;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import static java.util.Objects.requireNonNull;
 
 public class McMMOPlayerAbilityDeactivateEvent extends McMMOPlayerAbilityEvent {
-    public McMMOPlayerAbilityDeactivateEvent(Player player, PrimarySkillType skill) {
-        super(player, skill);
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public McMMOPlayerAbilityDeactivateEvent(@NotNull Player player, @NotNull PrimarySkillType skill) {
+        this(requireNonNull(UserManager.getPlayer(player)), skill);
+    }
+
+    public McMMOPlayerAbilityDeactivateEvent(@NotNull McMMOPlayer mmoPlayer, @NotNull PrimarySkillType skill) {
+        super(mmoPlayer, skill);
     }
 }

+ 23 - 1
src/main/java/com/gmail/nossr50/events/skills/abilities/McMMOPlayerAbilityEvent.java

@@ -1,16 +1,38 @@
 package com.gmail.nossr50.events.skills.abilities;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
 import com.gmail.nossr50.events.skills.McMMOPlayerSkillEvent;
 import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.entity.Player;
 
+import java.util.Objects;
+
 public class McMMOPlayerAbilityEvent extends McMMOPlayerSkillEvent {
     private final SuperAbilityType ability;
 
+    /**
+     * Create a new McMMOPlayerAbilityEvent.
+     *
+     * @param player The player involved in this event
+     * @param skill The skill involved in this event
+     * @deprecated Use {@link #McMMOPlayerAbilityEvent(McMMOPlayer, PrimarySkillType)} instead
+     */
+    @Deprecated(forRemoval = true, since = "2.2.010")
     protected McMMOPlayerAbilityEvent(Player player, PrimarySkillType skill) {
-        super(player, skill);
+        super(Objects.requireNonNull(UserManager.getPlayer(player)), skill);
+        ability = mcMMO.p.getSkillTools().getSuperAbility(skill);
+    }
+
+    /**
+     * Create a new McMMOPlayerAbilityEvent.
+     * @param mmoPlayer The McMMOPlayer involved in this event
+     * @param skill The skill involved in this event
+     */
+    protected McMMOPlayerAbilityEvent(McMMOPlayer mmoPlayer, PrimarySkillType skill) {
+        super(mmoPlayer, skill);
         ability = mcMMO.p.getSkillTools().getSuperAbility(skill);
     }
 

+ 13 - 1
src/main/java/com/gmail/nossr50/events/skills/alchemy/McMMOPlayerBrewEvent.java

@@ -1,24 +1,36 @@
 package com.gmail.nossr50.events.skills.alchemy;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.events.skills.McMMOPlayerSkillEvent;
+import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.block.Block;
 import org.bukkit.block.BlockState;
 import org.bukkit.block.BrewingStand;
 import org.bukkit.entity.Player;
 import org.bukkit.event.Cancellable;
+import org.jetbrains.annotations.NotNull;
+
+import static java.util.Objects.requireNonNull;
 
 public class McMMOPlayerBrewEvent extends McMMOPlayerSkillEvent implements Cancellable {
     private final BlockState brewingStand;
 
     private boolean cancelled;
 
+    @Deprecated(forRemoval = true, since = "2.2.010")
     public McMMOPlayerBrewEvent(Player player, BlockState brewingStand) {
-        super(player, PrimarySkillType.ALCHEMY);
+        super(requireNonNull(UserManager.getPlayer(player)), PrimarySkillType.ALCHEMY);
         this.brewingStand = brewingStand;
         cancelled = false;
     }
 
+    public McMMOPlayerBrewEvent(@NotNull McMMOPlayer mmoPlayer, @NotNull BlockState brewingStand) {
+        super(mmoPlayer, PrimarySkillType.ALCHEMY);
+        this.brewingStand = requireNonNull(brewingStand);
+        cancelled = false;
+    }
+
     public boolean isCancelled() {
         return cancelled;
     }

+ 12 - 1
src/main/java/com/gmail/nossr50/events/skills/alchemy/McMMOPlayerCatalysisEvent.java

@@ -1,17 +1,28 @@
 package com.gmail.nossr50.events.skills.alchemy;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.events.skills.McMMOPlayerSkillEvent;
+import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.entity.Player;
 import org.bukkit.event.Cancellable;
 
+import static java.util.Objects.requireNonNull;
+
 public class McMMOPlayerCatalysisEvent extends McMMOPlayerSkillEvent implements Cancellable {
     private double speed;
 
     private boolean cancelled;
 
+    @Deprecated(forRemoval = true, since = "2.2.010")
     public McMMOPlayerCatalysisEvent(Player player, double speed) {
-        super(player, PrimarySkillType.ALCHEMY);
+        super(requireNonNull(UserManager.getPlayer(player)), PrimarySkillType.ALCHEMY);
+        this.speed = speed;
+        cancelled = false;
+    }
+
+    public McMMOPlayerCatalysisEvent(McMMOPlayer mmoPlayer, double speed) {
+        super(mmoPlayer, PrimarySkillType.ALCHEMY);
         this.speed = speed;
         cancelled = false;
     }

+ 9 - 1
src/main/java/com/gmail/nossr50/events/skills/fishing/McMMOPlayerFishingEvent.java

@@ -1,15 +1,23 @@
 package com.gmail.nossr50.events.skills.fishing;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.events.skills.McMMOPlayerSkillEvent;
+import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.entity.Player;
 import org.bukkit.event.Cancellable;
 
 public class McMMOPlayerFishingEvent extends McMMOPlayerSkillEvent implements Cancellable {
     private boolean cancelled;
 
+    @Deprecated(forRemoval = true, since = "2.2.010")
     protected McMMOPlayerFishingEvent(Player player) {
-        super(player, PrimarySkillType.FISHING);
+        super(UserManager.getPlayer(player), PrimarySkillType.FISHING);
+        cancelled = false;
+    }
+
+    protected McMMOPlayerFishingEvent(McMMOPlayer mmoPlayer) {
+        super(mmoPlayer, PrimarySkillType.FISHING);
         cancelled = false;
     }
 

+ 14 - 3
src/main/java/com/gmail/nossr50/events/skills/fishing/McMMOPlayerFishingTreasureEvent.java

@@ -1,23 +1,34 @@
 package com.gmail.nossr50.events.skills.fishing;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import static java.util.Objects.requireNonNull;
 
 public class McMMOPlayerFishingTreasureEvent extends McMMOPlayerFishingEvent {
     private ItemStack treasure;
     private int xp;
 
+    @Deprecated(forRemoval = true, since = "2.2.010")
     public McMMOPlayerFishingTreasureEvent(Player player, ItemStack treasure, int xp) {
-        super(player);
+        this(requireNonNull(UserManager.getPlayer(player)), treasure, xp);
+    }
+
+    public McMMOPlayerFishingTreasureEvent(@NotNull McMMOPlayer mmoPlayer, @Nullable ItemStack treasure, int xp) {
+        super(mmoPlayer);
         this.treasure = treasure;
         this.xp = xp;
     }
 
-    public ItemStack getTreasure() {
+    public @Nullable ItemStack getTreasure() {
         return treasure;
     }
 
-    public void setTreasure(ItemStack item) {
+    public void setTreasure(@Nullable ItemStack item) {
         this.treasure = item;
     }
 

+ 17 - 5
src/main/java/com/gmail/nossr50/events/skills/fishing/McMMOPlayerMagicHunterEvent.java

@@ -1,20 +1,32 @@
 package com.gmail.nossr50.events.skills.fishing;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.enchantments.Enchantment;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
 
+import java.util.HashMap;
 import java.util.Map;
 
+import static java.util.Objects.requireNonNull;
+
 public class McMMOPlayerMagicHunterEvent extends McMMOPlayerFishingTreasureEvent {
-    private final Map<Enchantment, Integer> enchants;
+    private final Map<Enchantment, Integer> enchants = new HashMap<>();
+
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public McMMOPlayerMagicHunterEvent(@NotNull Player player, @NotNull ItemStack treasure, int xp, @NotNull Map<Enchantment, Integer> enchants) {
+        this(requireNonNull(UserManager.getPlayer(player)), treasure, xp, enchants);
+    }
 
-    public McMMOPlayerMagicHunterEvent(Player player, ItemStack treasure, int xp, Map<Enchantment, Integer> enchants) {
-        super(player, treasure, xp);
-        this.enchants = enchants;
+    public McMMOPlayerMagicHunterEvent(@NotNull McMMOPlayer mmoPlayer, @NotNull ItemStack treasure, int xp, @NotNull Map<Enchantment, Integer> enchants) {
+        super(mmoPlayer, treasure, xp);
+        requireNonNull(enchants, "enchants cannot be null");
+        this.enchants.putAll(enchants);
     }
 
-    public Map<Enchantment, Integer> getEnchantments() {
+    public @NotNull Map<Enchantment, Integer> getEnchantments() {
         return enchants;
     }
 }

+ 38 - 7
src/main/java/com/gmail/nossr50/events/skills/secondaryabilities/SubSkillEvent.java

@@ -1,11 +1,16 @@
 package com.gmail.nossr50.events.skills.secondaryabilities;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
 import com.gmail.nossr50.events.skills.McMMOPlayerSkillEvent;
 import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.entity.Player;
 import org.bukkit.event.Cancellable;
+import org.jetbrains.annotations.NotNull;
+
+import static java.util.Objects.requireNonNull;
 
 public class SubSkillEvent extends McMMOPlayerSkillEvent implements Cancellable {
     private SubSkillType subSkillType;
@@ -16,9 +21,20 @@ public class SubSkillEvent extends McMMOPlayerSkillEvent implements Cancellable
      * Only skills using the old system will fire this event
      * @param player target player
      * @param subSkillType target subskill
+     * @deprecated Use {@link #SubSkillEvent(McMMOPlayer, SubSkillType)} instead
      */
-    public SubSkillEvent(Player player, SubSkillType subSkillType) {
-        super(player, mcMMO.p.getSkillTools().getPrimarySkillBySubSkill(subSkillType));
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public SubSkillEvent(@NotNull Player player, @NotNull SubSkillType subSkillType) {
+        this(requireNonNull(UserManager.getPlayer(player)), subSkillType);
+    }
+
+    /**
+     * Only skills using the old system will fire this event
+     * @param mmoPlayer target player
+     * @param subSkillType target subskill
+     */
+    public SubSkillEvent(@NotNull McMMOPlayer mmoPlayer, @NotNull SubSkillType subSkillType) {
+        super(mmoPlayer, mcMMO.p.getSkillTools().getPrimarySkillBySubSkill(subSkillType));
         this.subSkillType = subSkillType;
     }
 
@@ -28,15 +44,30 @@ public class SubSkillEvent extends McMMOPlayerSkillEvent implements Cancellable
      * @param subSkillType target subskill
      * @param resultModifier a value multiplied against the final result of the dice roll, typically between 0-1.0
      */
-    public SubSkillEvent(Player player, SubSkillType subSkillType, double resultModifier) {
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public SubSkillEvent(@NotNull Player player, @NotNull SubSkillType subSkillType, double resultModifier) {
+        this(requireNonNull(UserManager.getPlayer(player)), subSkillType, resultModifier);
+    }
+
+    /**
+     * Only skills using the old system will fire this event
+     * @param player target player
+     * @param subSkillType target subskill
+     * @param resultModifier a value multiplied against the final result of the dice roll, typically between 0-1.0
+     */
+    public SubSkillEvent(@NotNull McMMOPlayer player, @NotNull SubSkillType subSkillType, double resultModifier) {
         super(player, mcMMO.p.getSkillTools().getPrimarySkillBySubSkill(subSkillType));
-        this.subSkillType = subSkillType;
+        this.subSkillType = requireNonNull(subSkillType, "subSkillType cannot be null");
         this.resultModifier = resultModifier;
     }
 
-    public SubSkillEvent(Player player, AbstractSubSkill abstractSubSkill)
-    {
-        super(player, abstractSubSkill.getPrimarySkill());
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public SubSkillEvent(@NotNull Player player, @NotNull AbstractSubSkill abstractSubSkill) {
+        this(requireNonNull(UserManager.getPlayer(player)), abstractSubSkill);
+    }
+
+    public SubSkillEvent(@NotNull McMMOPlayer mmoPlayer, @NotNull AbstractSubSkill abstractSubSkill) {
+        super(mmoPlayer, abstractSubSkill.getPrimarySkill());
     }
 
     public double getResultModifier() {

+ 7 - 10
src/main/java/com/gmail/nossr50/listeners/BlockListener.java

@@ -708,19 +708,16 @@ public class BlockListener implements Listener {
                     player.sendMessage("[mcMMO DEBUG] World Guard xp flag is not permitted for this player in this region");
             }
 
-            if(blockState instanceof Furnace furnace)
-            {
-                if(mcMMO.getSmeltingTracker().isFurnaceOwned(furnace))
-                {
-                    player.sendMessage("[mcMMO DEBUG] This furnace has a registered owner");
-                    OfflinePlayer furnacePlayer = mcMMO.getSmeltingTracker().getFurnaceOwner(furnace);
-                    if(furnacePlayer != null)
-                    {
-                        player.sendMessage("[mcMMO DEBUG] This furnace is owned by player "+furnacePlayer.getName());
+            if(blockState instanceof Furnace || blockState instanceof BrewingStand) {
+                if(ContainerMetadataUtils.isContainerOwned(blockState)) {
+                    player.sendMessage("[mcMMO DEBUG] This container has a registered owner");
+                    final OfflinePlayer furnacePlayer = ContainerMetadataUtils.getContainerOwner(blockState);
+                    if(furnacePlayer != null) {
+                        player.sendMessage("[mcMMO DEBUG] This container is owned by player "+furnacePlayer.getName());
                     }
                 }
                 else
-                    player.sendMessage("[mcMMO DEBUG] This furnace does not have a registered owner");
+                    player.sendMessage("[mcMMO DEBUG] This container does not have a registered owner");
             }
 
             if(ExperienceConfig.getInstance().isExperienceBarsEnabled())

+ 13 - 14
src/main/java/com/gmail/nossr50/listeners/EntityListener.java

@@ -8,7 +8,6 @@ import com.gmail.nossr50.datatypes.skills.subskills.interfaces.InteractType;
 import com.gmail.nossr50.events.fake.FakeEntityTameEvent;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.metadata.MobMetaFlagType;
-import com.gmail.nossr50.metadata.MobMetadataService;
 import com.gmail.nossr50.runnables.TravelingBlockMetaCleanup;
 import com.gmail.nossr50.skills.archery.Archery;
 import com.gmail.nossr50.skills.crossbows.Crossbows;
@@ -47,9 +46,10 @@ import org.bukkit.potion.PotionEffectType;
 import org.bukkit.projectiles.ProjectileSource;
 import org.jetbrains.annotations.NotNull;
 
+import static com.gmail.nossr50.util.MobMetadataUtils.*;
+
 public class EntityListener implements Listener {
     private final mcMMO pluginRef;
-    private final @NotNull MobMetadataService mobMetadataService;
 
     /**
      * We can use this {@link NamespacedKey} for {@link Enchantment} comparisons to
@@ -59,7 +59,6 @@ public class EntityListener implements Listener {
 
     public EntityListener(final mcMMO pluginRef) {
         this.pluginRef = pluginRef;
-        mobMetadataService = mcMMO.getMetadataService().getMobMetadataService();
     }
 
     @EventHandler(priority = EventPriority.MONITOR)
@@ -67,10 +66,10 @@ public class EntityListener implements Listener {
         if(event.getEntity() instanceof LivingEntity livingEntity) {
 
             //Transfer metadata keys from mob-spawned mobs to new mobs
-            if(mobMetadataService.hasMobFlags(livingEntity)) {
+            if(hasMobFlags(livingEntity)) {
                 for(Entity entity : event.getTransformedEntities()) {
                     if(entity instanceof LivingEntity transformedEntity) {
-                        mobMetadataService.addMobFlags(livingEntity, transformedEntity);
+                        addMobFlags(livingEntity, transformedEntity);
                     }
                 }
             }
@@ -93,8 +92,8 @@ public class EntityListener implements Listener {
         {
             if(event.getEntity() instanceof Enderman enderman) {
 
-                if(!mobMetadataService.hasMobFlag(MobMetaFlagType.EXPLOITED_ENDERMEN, enderman)) {
-                    mobMetadataService.flagMetadata(MobMetaFlagType.EXPLOITED_ENDERMEN, enderman);
+                if(!hasMobFlag(MobMetaFlagType.EXPLOITED_ENDERMEN, enderman)) {
+                    flagMetadata(MobMetaFlagType.EXPLOITED_ENDERMEN, enderman);
                 }
             }
         }
@@ -168,7 +167,7 @@ public class EntityListener implements Listener {
                     return;
                 }
 
-                if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ARCHERY_ARROW_RETRIEVAL, player)) {
+                if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ARCHERY_ARROW_RETRIEVAL, UserManager.getPlayer(player))) {
                     arrow.setMetadata(MetadataConstants.METADATA_KEY_TRACKED_ARROW, MetadataConstants.MCMMO_METADATA_VALUE);
                 }
             }
@@ -730,11 +729,11 @@ public class EntityListener implements Listener {
     }
 
     private void trackSpawnedAndPassengers(LivingEntity livingEntity, MobMetaFlagType mobMetaFlagType) {
-        mobMetadataService.flagMetadata(mobMetaFlagType, livingEntity);
+        flagMetadata(mobMetaFlagType, livingEntity);
 
         for(Entity passenger : livingEntity.getPassengers()) {
             if(passenger != null) {
-                mobMetadataService.flagMetadata(mobMetaFlagType, livingEntity);
+                flagMetadata(mobMetaFlagType, livingEntity);
             }
         }
     }
@@ -742,7 +741,7 @@ public class EntityListener implements Listener {
     @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
     public void onEntityBreed(EntityBreedEvent event) {
         if(ExperienceConfig.getInstance().isCOTWBreedingPrevented()) {
-            if(mobMetadataService.hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, event.getFather()) || mobMetadataService.hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, event.getMother())) {
+            if(hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, event.getFather()) || hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, event.getMother())) {
                 event.setCancelled(true);
                 Animals mom = (Animals) event.getMother();
                 Animals father = (Animals) event.getFather();
@@ -1005,12 +1004,12 @@ public class EntityListener implements Listener {
 
         if (!UserManager.hasPlayerDataKey(player)
                 || (ExperienceConfig.getInstance().isNPCInteractionPrevented() && Misc.isNPCEntityExcludingVillagers(livingEntity))
-                || mobMetadataService.hasMobFlag(MobMetaFlagType.EGG_MOB, livingEntity)
-                || mobMetadataService.hasMobFlag(MobMetaFlagType.MOB_SPAWNER_MOB, livingEntity)) {
+                || hasMobFlag(MobMetaFlagType.EGG_MOB, livingEntity)
+                || hasMobFlag(MobMetaFlagType.MOB_SPAWNER_MOB, livingEntity)) {
             return;
         }
 
-        mobMetadataService.flagMetadata(MobMetaFlagType.PLAYER_TAMED_MOB, livingEntity);
+        flagMetadata(MobMetaFlagType.PLAYER_TAMED_MOB, livingEntity);
 
         //Profile not loaded
         if(UserManager.getPlayer(player) == null)

+ 57 - 43
src/main/java/com/gmail/nossr50/listeners/InventoryListener.java

@@ -5,11 +5,11 @@ import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.events.fake.FakeBrewEvent;
-import com.gmail.nossr50.events.fake.FakeEvent;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.runnables.player.PlayerUpdateInventoryTask;
 import com.gmail.nossr50.skills.alchemy.Alchemy;
 import com.gmail.nossr50.skills.alchemy.AlchemyPotionBrewer;
+import com.gmail.nossr50.util.ContainerMetadataUtils;
 import com.gmail.nossr50.util.ItemUtils;
 import com.gmail.nossr50.util.MetadataConstants;
 import com.gmail.nossr50.util.Permissions;
@@ -29,7 +29,6 @@ import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.EventPriority;
 import org.bukkit.event.Listener;
-import org.bukkit.event.block.BrewingStartEvent;
 import org.bukkit.event.inventory.*;
 import org.bukkit.inventory.*;
 
@@ -55,7 +54,7 @@ public class InventoryListener implements Listener {
         }
 
         Furnace furnace = (Furnace) furnaceState;
-        OfflinePlayer offlinePlayer = mcMMO.getSmeltingTracker().getFurnaceOwner(furnace);
+        OfflinePlayer offlinePlayer = ContainerMetadataUtils.getContainerOwner(furnace);
         Player player;
 
         if(offlinePlayer != null && offlinePlayer.isOnline() && offlinePlayer instanceof Player) {
@@ -101,7 +100,7 @@ public class InventoryListener implements Listener {
         }
 
         if(blockState instanceof Furnace furnace) {
-            OfflinePlayer offlinePlayer = mcMMO.getSmeltingTracker().getFurnaceOwner(furnace);
+            OfflinePlayer offlinePlayer = ContainerMetadataUtils.getContainerOwner(furnace);
 
             if(offlinePlayer != null) {
 
@@ -167,16 +166,16 @@ public class InventoryListener implements Listener {
         Inventory inventory = event.getInventory();
 
         Player player = ((Player) event.getWhoClicked()).getPlayer();
+        McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
 
-        if(event.getInventory() instanceof FurnaceInventory)
-        {
-            Furnace furnace = mcMMO.getSmeltingTracker().getFurnaceFromInventory(event.getInventory());
+        if(event.getInventory() instanceof FurnaceInventory furnaceInventory) {
+            //Switch owners
+            ContainerMetadataUtils.processContainerOwnership(furnaceInventory.getHolder(), player);
+        }
 
-            if (furnace != null)
-            {
-                //Switch owners
-                mcMMO.getSmeltingTracker().processFurnaceOwnership(furnace, player);
-            }
+        if(event.getInventory() instanceof BrewerInventory brewerInventory) {
+            // switch owners
+            ContainerMetadataUtils.processContainerOwnership(brewerInventory.getHolder(), player);
         }
 
         if (!(inventory instanceof BrewerInventory)) {
@@ -191,10 +190,11 @@ public class InventoryListener implements Listener {
 
         HumanEntity whoClicked = event.getWhoClicked();
 
-        if (!UserManager.hasPlayerDataKey(event.getWhoClicked()) || !Permissions.isSubSkillEnabled(whoClicked, SubSkillType.ALCHEMY_CONCOCTIONS)) {
+        if (mmoPlayer == null || !Permissions.isSubSkillEnabled(whoClicked, SubSkillType.ALCHEMY_CONCOCTIONS)) {
             return;
         }
 
+        // TODO: Investigate why this WG check is all the way down here?
         /* WORLD GUARD MAIN FLAG CHECK */
         if(WorldGuardUtils.isWorldGuardLoaded())
         {
@@ -202,11 +202,16 @@ public class InventoryListener implements Listener {
                 return;
         }
 
-        ItemStack clicked = event.getCurrentItem();
-        ItemStack cursor = event.getCursor();
+        final ItemStack clicked = event.getCurrentItem();
+        final ItemStack cursor = event.getCursor();
 
-        if ((clicked != null && (clicked.getType() == Material.POTION || clicked.getType() == Material.SPLASH_POTION || clicked.getType() == Material.LINGERING_POTION)) || (cursor != null && (cursor.getType() == Material.POTION || cursor.getType() == Material.SPLASH_POTION || cursor.getType() == Material.LINGERING_POTION))) {
-            AlchemyPotionBrewer.scheduleCheck(player, stand);
+        if ((clicked != null && (clicked.getType() == Material.POTION
+                || clicked.getType() == Material.SPLASH_POTION
+                || clicked.getType() == Material.LINGERING_POTION))
+                || (cursor != null && (cursor.getType() == Material.POTION
+                        || cursor.getType() == Material.SPLASH_POTION
+                        || cursor.getType() == Material.LINGERING_POTION))) {
+            AlchemyPotionBrewer.scheduleCheck(stand);
             return;
         }
 
@@ -216,11 +221,11 @@ public class InventoryListener implements Listener {
         if (click.isShiftClick()) {
             switch (slot) {
                 case FUEL:
-                    AlchemyPotionBrewer.scheduleCheck(player, stand);
+                    AlchemyPotionBrewer.scheduleCheck(stand);
                     return;
                 case CONTAINER:
                 case QUICKBAR:
-                    if (!AlchemyPotionBrewer.isValidIngredient(player, clicked)) {
+                    if (!AlchemyPotionBrewer.isValidIngredientByPlayer(player, clicked)) {
                         return;
                     }
 
@@ -230,7 +235,7 @@ public class InventoryListener implements Listener {
 
                     event.setCancelled(true);
                     AlchemyPotionBrewer.scheduleUpdate(inventory);
-                    AlchemyPotionBrewer.scheduleCheck(player, stand);
+                    AlchemyPotionBrewer.scheduleCheck(stand);
                     return;
                 default:
             }
@@ -240,14 +245,14 @@ public class InventoryListener implements Listener {
 
             if (AlchemyPotionBrewer.isEmpty(cursor)) {
                 if (emptyClicked && click == ClickType.NUMBER_KEY) {
-                    AlchemyPotionBrewer.scheduleCheck(player, stand);
+                    AlchemyPotionBrewer.scheduleCheck(stand);
                     return;
                 }
 
-                AlchemyPotionBrewer.scheduleCheck(player, stand);
+                AlchemyPotionBrewer.scheduleCheck(stand);
             }
             else if (emptyClicked) {
-                if (AlchemyPotionBrewer.isValidIngredient(player, cursor)) {
+                if (AlchemyPotionBrewer.isValidIngredientByPlayer(player, cursor)) {
                     int amount = cursor.getAmount();
 
                     if (click == ClickType.LEFT || (click == ClickType.RIGHT && amount == 1)) {
@@ -256,7 +261,7 @@ public class InventoryListener implements Listener {
                         event.setCursor(null);
 
                         AlchemyPotionBrewer.scheduleUpdate(inventory);
-                        AlchemyPotionBrewer.scheduleCheck(player, stand);
+                        AlchemyPotionBrewer.scheduleCheck(stand);
                     }
                     else if (click == ClickType.RIGHT) {
                         event.setCancelled(true);
@@ -271,7 +276,7 @@ public class InventoryListener implements Listener {
                         event.setCursor(rest);
 
                         AlchemyPotionBrewer.scheduleUpdate(inventory);
-                        AlchemyPotionBrewer.scheduleCheck(player, stand);
+                        AlchemyPotionBrewer.scheduleCheck(stand);
                     }
                 }
             }
@@ -324,9 +329,9 @@ public class InventoryListener implements Listener {
                     return;
             }
 
-            if (AlchemyPotionBrewer.isValidIngredient(player, cursor)) {
+            if (AlchemyPotionBrewer.isValidIngredientByPlayer(player, cursor)) {
                 // Not handled: dragging custom ingredients over ingredient slot (does not trigger any event)
-                AlchemyPotionBrewer.scheduleCheck(player, (BrewingStand) holder);
+                AlchemyPotionBrewer.scheduleCheck((BrewingStand) holder);
                 return;
             }
 
@@ -371,32 +376,41 @@ public class InventoryListener implements Listener {
             if(WorldBlacklist.isWorldBlacklisted(event.getSource().getLocation().getWorld()))
                 return;
 
-        Inventory inventory = event.getDestination();
+        final Inventory inventory = event.getDestination();
 
         if (!(inventory instanceof BrewerInventory)) {
             return;
         }
 
-        InventoryHolder holder = inventory.getHolder();
+        final InventoryHolder holder = inventory.getHolder();
 
-        if (!(holder instanceof BrewingStand)) {
-            return;
-        }
+        if (holder instanceof BrewingStand brewingStand) {
 
-        ItemStack item = event.getItem();
+            ItemStack item = event.getItem();
 
-        if (mcMMO.p.getGeneralConfig().getPreventHopperTransferIngredients() && item.getType() != Material.POTION && item.getType() != Material.SPLASH_POTION && item.getType() != Material.LINGERING_POTION) {
-            event.setCancelled(true);
-            return;
-        }
+            if (mcMMO.p.getGeneralConfig().getPreventHopperTransferIngredients() && item.getType() != Material.POTION && item.getType() != Material.SPLASH_POTION && item.getType() != Material.LINGERING_POTION) {
+                event.setCancelled(true);
+                return;
+            }
 
-        if (mcMMO.p.getGeneralConfig().getPreventHopperTransferBottles() && (item.getType() == Material.POTION || item.getType() == Material.SPLASH_POTION || item.getType() == Material.LINGERING_POTION)) {
-            event.setCancelled(true);
-            return;
-        }
+            if (mcMMO.p.getGeneralConfig().getPreventHopperTransferBottles() && (item.getType() == Material.POTION || item.getType() == Material.SPLASH_POTION || item.getType() == Material.LINGERING_POTION)) {
+                event.setCancelled(true);
+                return;
+            }
+            int ingredientLevel = 1;
+
+            OfflinePlayer offlinePlayer = ContainerMetadataUtils.getContainerOwner(brewingStand);
+            if (offlinePlayer != null && offlinePlayer.isOnline()) {
+                McMMOPlayer mmoPlayer = UserManager.getPlayer(offlinePlayer.getPlayer());
+                if (mmoPlayer != null) {
+                    ingredientLevel = mmoPlayer.getAlchemyManager().getTier();
+                }
+            }
 
-        if (mcMMO.p.getGeneralConfig().getEnabledForHoppers() && AlchemyPotionBrewer.isValidIngredient(null, item)) {
-            AlchemyPotionBrewer.scheduleCheck(null, (BrewingStand) holder);
+            if (mcMMO.p.getGeneralConfig().getEnabledForHoppers()
+                    && AlchemyPotionBrewer.isValidIngredientByLevel(ingredientLevel, item)) {
+                AlchemyPotionBrewer.scheduleCheck(brewingStand);
+            }
         }
     }
 

+ 0 - 19
src/main/java/com/gmail/nossr50/mcMMO.java

@@ -19,7 +19,6 @@ import com.gmail.nossr50.database.DatabaseManagerFactory;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.subskills.acrobatics.Roll;
 import com.gmail.nossr50.listeners.*;
-import com.gmail.nossr50.metadata.MetadataService;
 import com.gmail.nossr50.party.PartyManager;
 import com.gmail.nossr50.placeholders.PapiExpansion;
 import com.gmail.nossr50.runnables.SaveTimerTask;
@@ -50,7 +49,6 @@ import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.scoreboards.ScoreboardManager;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.skills.SkillTools;
-import com.gmail.nossr50.util.skills.SmeltingTracker;
 import com.gmail.nossr50.util.upgrade.UpgradeManager;
 import com.gmail.nossr50.worldguard.WorldGuardManager;
 import com.tcoded.folialib.FoliaLib;
@@ -80,7 +78,6 @@ import java.util.List;
 public class mcMMO extends JavaPlugin {
     /* Managers & Services */
     private static PlatformManager platformManager;
-    private static MetadataService metadataService;
     private static ChunkManager       placeStore;
     private static RepairableManager  repairableManager;
     private static SalvageableManager salvageableManager;
@@ -90,12 +87,10 @@ public class mcMMO extends JavaPlugin {
     private static UpgradeManager     upgradeManager;
     private static MaterialMapStore materialMapStore;
     private static PlayerLevelUtils playerLevelUtils;
-    private static SmeltingTracker smeltingTracker;
     private static TransientMetadataTools transientMetadataTools;
     private static ChatManager chatManager;
     private static CommandManager commandManager; //ACF
     private static TransientEntityTracker transientEntityTracker;
-//    private static ProtocolLibManager protocolLibManager;
 
     private SkillTools skillTools;
 
@@ -186,9 +181,6 @@ public class mcMMO extends JavaPlugin {
             //Platform Manager
             platformManager = new PlatformManager();
 
-            //metadata service
-            metadataService = new MetadataService(this);
-
             MetadataConstants.MCMMO_METADATA_VALUE = new FixedMetadataValue(this, true);
 
             PluginManager pluginManager = getServer().getPluginManager();
@@ -319,9 +311,6 @@ public class mcMMO extends JavaPlugin {
         //Init the blacklist
         worldBlacklist = new WorldBlacklist(this);
 
-        //Init smelting tracker
-        smeltingTracker = new SmeltingTracker();
-
         //Set up Adventure's audiences
         audiences = BukkitAudiences.create(this);
 
@@ -482,10 +471,6 @@ public class mcMMO extends JavaPlugin {
         return platformManager.getCompatibilityManager();
     }
 
-    public static MetadataService getMetadataService() {
-        return metadataService;
-    }
-
     @Deprecated
     public static void setDatabaseManager(DatabaseManager databaseManager) {
         mcMMO.databaseManager = databaseManager;
@@ -741,10 +726,6 @@ public class mcMMO extends JavaPlugin {
         return platformManager;
     }
 
-    public static SmeltingTracker getSmeltingTracker() {
-        return smeltingTracker;
-    }
-
     public static BukkitAudiences getAudiences() {
         return audiences;
     }

+ 0 - 49
src/main/java/com/gmail/nossr50/metadata/BlockMetadataService.java

@@ -1,49 +0,0 @@
-package com.gmail.nossr50.metadata;
-
-import com.gmail.nossr50.mcMMO;
-import org.bukkit.block.Furnace;
-import org.bukkit.persistence.PersistentDataContainer;
-import org.bukkit.persistence.PersistentDataHolder;
-import org.bukkit.persistence.PersistentDataType;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.UUID;
-
-import static com.gmail.nossr50.metadata.MetadataService.NSK_FURNACE_UUID_LEAST_SIG;
-import static com.gmail.nossr50.metadata.MetadataService.NSK_FURNACE_UUID_MOST_SIG;
-
-public class BlockMetadataService {
-
-    private final @NotNull mcMMO pluginRef;
-
-    public BlockMetadataService(@NotNull mcMMO pluginRef) {
-        this.pluginRef = pluginRef;
-    }
-
-    public @Nullable UUID getFurnaceOwner(@NotNull Furnace furnace) {
-        //Get container from entity
-        PersistentDataContainer dataContainer = ((PersistentDataHolder) furnace).getPersistentDataContainer();
-
-        //Too lazy to make a custom data type for this stuff
-        Long mostSigBits = dataContainer.get(NSK_FURNACE_UUID_MOST_SIG, PersistentDataType.LONG);
-        Long leastSigBits = dataContainer.get(NSK_FURNACE_UUID_LEAST_SIG, PersistentDataType.LONG);
-
-        if (mostSigBits != null && leastSigBits != null) {
-            return new UUID(mostSigBits, leastSigBits);
-        } else {
-            return null;
-        }
-    }
-
-    public void setFurnaceOwner(@NotNull Furnace furnace, @NotNull UUID uuid) {
-        PersistentDataContainer dataContainer = ((PersistentDataHolder) furnace).getPersistentDataContainer();
-
-        dataContainer.set(NSK_FURNACE_UUID_MOST_SIG, PersistentDataType.LONG, uuid.getMostSignificantBits());
-        dataContainer.set(NSK_FURNACE_UUID_LEAST_SIG, PersistentDataType.LONG, uuid.getLeastSignificantBits());
-
-        furnace.update();
-    }
-
-
-}

+ 0 - 71
src/main/java/com/gmail/nossr50/metadata/MetadataService.java

@@ -1,71 +0,0 @@
-package com.gmail.nossr50.metadata;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.util.MetadataConstants;
-import org.bukkit.NamespacedKey;
-import org.jetbrains.annotations.NotNull;
-
-public class MetadataService {
-    private final @NotNull mcMMO pluginRef;
-
-    protected static final @NotNull NamespacedKey NSK_SUPER_ABILITY_BOOSTED_ITEM;
-    protected static final @NotNull NamespacedKey NSK_MOB_SPAWNER_MOB;
-    protected static final @NotNull NamespacedKey NSK_EGG_MOB;
-    protected static final @NotNull NamespacedKey NSK_NETHER_GATE_MOB;
-    protected static final @NotNull NamespacedKey NSK_COTW_SUMMONED_MOB;
-    protected static final @NotNull NamespacedKey NSK_PLAYER_BRED_MOB;
-    protected static final @NotNull NamespacedKey NSK_PLAYER_TAMED_MOB;
-    protected static final @NotNull NamespacedKey NSK_VILLAGER_TRADE_ORIGIN_ITEM;
-    protected static final @NotNull NamespacedKey NSK_EXPLOITED_ENDERMEN;
-    protected static final @NotNull NamespacedKey NSK_FURNACE_UUID_MOST_SIG;
-    protected static final @NotNull NamespacedKey NSK_FURNACE_UUID_LEAST_SIG;
-
-    static {
-        NSK_SUPER_ABILITY_BOOSTED_ITEM = getNamespacedKey(MetadataConstants.METADATA_KEY_SUPER_ABILITY_BOOSTED_ITEM);
-        NSK_MOB_SPAWNER_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_MOB_SPAWNER_MOB);
-        NSK_EGG_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_EGG_MOB);
-        NSK_NETHER_GATE_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_NETHER_PORTAL_MOB);
-        NSK_COTW_SUMMONED_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_COTW_SUMMONED_MOB);
-        NSK_PLAYER_BRED_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_PLAYER_BRED_MOB);
-        NSK_PLAYER_TAMED_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_PLAYER_TAMED_MOB);
-        NSK_VILLAGER_TRADE_ORIGIN_ITEM = getNamespacedKey(MetadataConstants.METADATA_KEY_VILLAGER_TRADE_ORIGIN_ITEM);
-        NSK_EXPLOITED_ENDERMEN = getNamespacedKey(MetadataConstants.METADATA_KEY_EXPLOITED_ENDERMEN);
-        NSK_FURNACE_UUID_MOST_SIG = getNamespacedKey(MetadataConstants.METADATA_KEY_FURNACE_UUID_MOST_SIG);
-        NSK_FURNACE_UUID_LEAST_SIG = getNamespacedKey(MetadataConstants.METADATA_KEY_FURNACE_UUID_LEAST_SIG);
-    }
-
-    private final @NotNull ItemMetadataService itemMetadataService;
-    private final @NotNull MobMetadataService mobMetadataService;
-    private final @NotNull BlockMetadataService blockMetadataService;
-
-    public MetadataService(@NotNull mcMMO pluginRef) {
-        this.pluginRef = pluginRef;
-
-        blockMetadataService = new BlockMetadataService(pluginRef);
-        mobMetadataService = new MobMetadataService(pluginRef);
-        itemMetadataService = new ItemMetadataService(pluginRef);
-    }
-
-    /**
-     * Helper method to simplify generating namespaced keys
-     *
-     * @param key the {@link String} value of the key
-     *
-     * @return the generated {@link NamespacedKey}
-     */
-    public static @NotNull NamespacedKey getNamespacedKey(@NotNull String key) {
-        return new NamespacedKey(mcMMO.p, key);
-    }
-
-    public @NotNull ItemMetadataService getItemMetadataService() {
-        return itemMetadataService;
-    }
-
-    public @NotNull MobMetadataService getMobMetadataService() {
-        return mobMetadataService;
-    }
-
-    public @NotNull BlockMetadataService getBlockMetadataService() {
-        return blockMetadataService;
-    }
-}

+ 27 - 8
src/main/java/com/gmail/nossr50/runnables/skills/AlchemyBrewCheckTask.java

@@ -1,32 +1,51 @@
 package com.gmail.nossr50.runnables.skills;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.skills.alchemy.Alchemy;
 import com.gmail.nossr50.skills.alchemy.AlchemyPotionBrewer;
 import com.gmail.nossr50.util.CancellableRunnable;
-import org.bukkit.Bukkit;
+import com.gmail.nossr50.util.ContainerMetadataUtils;
+import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.Location;
+import org.bukkit.OfflinePlayer;
 import org.bukkit.block.BrewingStand;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Arrays;
 
+import static com.gmail.nossr50.skills.alchemy.AlchemyPotionBrewer.isValidBrew;
+import static com.gmail.nossr50.util.EventUtils.getMcMMOPlayer;
+
 public class AlchemyBrewCheckTask extends CancellableRunnable {
-    private final Player player;
     private final BrewingStand brewingStand;
     private final ItemStack[] oldInventory;
 
-    public AlchemyBrewCheckTask(Player player, BrewingStand brewingStand) {
-        this.player = player;
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public AlchemyBrewCheckTask(@Nullable Player ignored, BrewingStand brewingStand) {
+        this(brewingStand);
+    }
+
+    public AlchemyBrewCheckTask(@NotNull BrewingStand brewingStand) {
         this.brewingStand = brewingStand;
         this.oldInventory = Arrays.copyOfRange(brewingStand.getInventory().getContents(), 0, 4);
     }
 
     @Override
     public void run() {
-        Location location = brewingStand.getLocation();
-        ItemStack[] newInventory = Arrays.copyOfRange(brewingStand.getInventory().getContents(), 0, 4);
-        boolean validBrew = brewingStand.getFuelLevel() > 0 && AlchemyPotionBrewer.isValidBrew(player, newInventory);
+        OfflinePlayer offlinePlayer = ContainerMetadataUtils.getContainerOwner(brewingStand);
+        int ingredientLevel = 1;
+        if (offlinePlayer != null && offlinePlayer.isOnline()) {
+            final McMMOPlayer mmoPlayer = UserManager.getPlayer(offlinePlayer.getPlayer());
+            if (mmoPlayer != null) {
+                ingredientLevel = mmoPlayer.getAlchemyManager().getTier();
+            }
+        }
+        final Location location = brewingStand.getLocation();
+        final ItemStack[] newInventory = Arrays.copyOfRange(brewingStand.getInventory().getContents(), 0, 4);
+        boolean validBrew = brewingStand.getFuelLevel() > 0 && isValidBrew(ingredientLevel, newInventory);
 
         if (Alchemy.brewingStandMap.containsKey(location)) {
             if (oldInventory[Alchemy.INGREDIENT_SLOT] == null
@@ -36,7 +55,7 @@ public class AlchemyBrewCheckTask extends CancellableRunnable {
                 Alchemy.brewingStandMap.get(location).cancelBrew();
             }
         } else if (validBrew) {
-            Alchemy.brewingStandMap.put(location, new AlchemyBrewTask(brewingStand, player));
+            Alchemy.brewingStandMap.put(location, new AlchemyBrewTask(brewingStand));
         }
     }
 }

+ 56 - 32
src/main/java/com/gmail/nossr50/runnables/skills/AlchemyBrewTask.java

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.runnables.skills;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.events.skills.alchemy.McMMOPlayerBrewEvent;
@@ -8,44 +9,56 @@ import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.skills.alchemy.Alchemy;
 import com.gmail.nossr50.skills.alchemy.AlchemyPotionBrewer;
 import com.gmail.nossr50.util.CancellableRunnable;
+import com.gmail.nossr50.util.ContainerMetadataUtils;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.player.UserManager;
-import org.bukkit.Bukkit;
-import org.bukkit.Location;
 import org.bukkit.Material;
+import org.bukkit.OfflinePlayer;
 import org.bukkit.block.BlockState;
 import org.bukkit.block.BrewingStand;
 import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
 
 public class AlchemyBrewTask extends CancellableRunnable {
     private static final double DEFAULT_BREW_SPEED = 1.0;
     private static final int    DEFAULT_BREW_TICKS = 400;
 
     private final BlockState brewingStand;
-    private final Location location;
+    private final OfflinePlayer offlinePlayer;
+    private McMMOPlayer mmoPlayer;
     private double brewSpeed;
     private double brewTimer;
-    private final Player player;
     private int fuel;
     private boolean firstRun = true;
+    private int ingredientLevel = 1;
+
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public AlchemyBrewTask(@NotNull BlockState brewingStand, Player ignored) {
+        this(brewingStand);
+    }
+
+    public AlchemyBrewTask(@NotNull BlockState brewingStand) {
+        offlinePlayer = ContainerMetadataUtils.getContainerOwner(brewingStand);
+        McMMOPlayer mmoPlayer = null;
+        if (offlinePlayer != null && offlinePlayer.isOnline()) {
+            mmoPlayer = UserManager.getPlayer(offlinePlayer.getPlayer());
+        }
 
-    public AlchemyBrewTask(BlockState brewingStand, Player player) {
         this.brewingStand = brewingStand;
-        this.location = brewingStand.getLocation();
-        this.player = player;
 
         brewSpeed = DEFAULT_BREW_SPEED;
         brewTimer = DEFAULT_BREW_TICKS;
 
-        if (player != null
-                && !Misc.isNPCEntityExcludingVillagers(player)
-                && Permissions.isSubSkillEnabled(player, SubSkillType.ALCHEMY_CATALYSIS)
-                && UserManager.getPlayer(player) != null) {
+        if (mmoPlayer != null
+                && !Misc.isNPCEntityExcludingVillagers(mmoPlayer.getPlayer())
+                && Permissions.isSubSkillEnabled(mmoPlayer.getPlayer(), SubSkillType.ALCHEMY_CATALYSIS)) {
+            ingredientLevel = mmoPlayer.getAlchemyManager().getTier();
 
-            double catalysis = UserManager.getPlayer(player).getAlchemyManager().calculateBrewSpeed(Permissions.lucky(player, PrimarySkillType.ALCHEMY));
+            double catalysis = mmoPlayer.getAlchemyManager().calculateBrewSpeed(Permissions.lucky(mmoPlayer.getPlayer(),
+                    PrimarySkillType.ALCHEMY));
 
-            McMMOPlayerCatalysisEvent event = new McMMOPlayerCatalysisEvent(player, catalysis);
+            McMMOPlayerCatalysisEvent event = new McMMOPlayerCatalysisEvent(mmoPlayer, catalysis);
             mcMMO.p.getServer().getPluginManager().callEvent(event);
 
             if (!event.isCancelled()) {
@@ -53,8 +66,8 @@ public class AlchemyBrewTask extends CancellableRunnable {
             }
         }
 
-        if (Alchemy.brewingStandMap.containsKey(location)) {
-            Alchemy.brewingStandMap.get(location).cancel();
+        if (Alchemy.brewingStandMap.containsKey(brewingStand.getLocation())) {
+            Alchemy.brewingStandMap.get(brewingStand.getLocation()).cancel();
         }
 
         fuel = ((BrewingStand) brewingStand).getFuelLevel();
@@ -62,16 +75,16 @@ public class AlchemyBrewTask extends CancellableRunnable {
         if (((BrewingStand) brewingStand).getBrewingTime() == -1) // Only decrement on our end if it isn't a vanilla ingredient.
             fuel--;
 
-        Alchemy.brewingStandMap.put(location, this);
-        mcMMO.p.getFoliaLib().getImpl().runAtLocationTimer(location, this, 1, 1);
+        Alchemy.brewingStandMap.put(brewingStand.getLocation(), this);
+        mcMMO.p.getFoliaLib().getImpl().runAtLocationTimer(brewingStand.getLocation(), this, 1, 1);
     }
 
     @Override
     public void run() {
         // Check if preconditions for brewing are not met
         if (shouldCancelBrewing()) {
-            if (Alchemy.brewingStandMap.containsKey(location)) {
-                Alchemy.brewingStandMap.remove(location);
+            if (Alchemy.brewingStandMap.containsKey(brewingStand.getLocation())) {
+                Alchemy.brewingStandMap.remove(brewingStand.getLocation());
             }
             this.cancel();
             return;
@@ -93,10 +106,7 @@ public class AlchemyBrewTask extends CancellableRunnable {
     }
 
     private boolean shouldCancelBrewing() {
-        if (player == null) {
-            return true;
-        }
-        if (!player.isValid()) {
+        if (offlinePlayer == null) {
             return true;
         }
         if (brewingStand == null) {
@@ -105,10 +115,24 @@ public class AlchemyBrewTask extends CancellableRunnable {
         if (brewingStand.getType() != Material.BREWING_STAND) {
             return true;
         }
-        if (!AlchemyPotionBrewer.isValidIngredient(player, ((BrewingStand) brewingStand).getInventory().getContents()[Alchemy.INGREDIENT_SLOT])) {
-            return true;
+        return !AlchemyPotionBrewer.isValidIngredientByLevel(
+                getIngredientLevelUpdated(), ((BrewingStand) brewingStand).getInventory().getContents()[Alchemy.INGREDIENT_SLOT]);
+    }
+
+    private int getIngredientLevelUpdated() {
+        if (mmoPlayer != null) {
+            ingredientLevel = mmoPlayer.getAlchemyManager().getTier();
+            return ingredientLevel;
+        } else if (offlinePlayer.isOnline() && mmoPlayer == null) {
+            final McMMOPlayer fetchedMMOPlayer = UserManager.getPlayer(offlinePlayer.getPlayer());
+            if (fetchedMMOPlayer != null) {
+                this.mmoPlayer = fetchedMMOPlayer;
+                ingredientLevel = mmoPlayer.getAlchemyManager().getTier();
+            }
+            return ingredientLevel;
+        } else {
+            return ingredientLevel;
         }
-        return false;
     }
 
     private void initializeBrewing() {
@@ -128,27 +152,27 @@ public class AlchemyBrewTask extends CancellableRunnable {
 
 
     private void finish() {
-        McMMOPlayerBrewEvent event = new McMMOPlayerBrewEvent(player, brewingStand);
+        final McMMOPlayerBrewEvent event = new McMMOPlayerBrewEvent(mmoPlayer, brewingStand);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
         if (!event.isCancelled()) {
-            AlchemyPotionBrewer.finishBrewing(brewingStand, player, false);
+            AlchemyPotionBrewer.finishBrewing(brewingStand, mmoPlayer.getPlayer(), false);
         }
 
-        Alchemy.brewingStandMap.remove(location);
+        Alchemy.brewingStandMap.remove(brewingStand.getLocation());
     }
 
     public void finishImmediately() {
         this.cancel();
 
-        AlchemyPotionBrewer.finishBrewing(brewingStand, player, true);
-        Alchemy.brewingStandMap.remove(location);
+        AlchemyPotionBrewer.finishBrewing(brewingStand, mmoPlayer.getPlayer(), true);
+        Alchemy.brewingStandMap.remove(brewingStand.getLocation());
     }
 
     public void cancelBrew() {
         this.cancel();
 
         ((BrewingStand) brewingStand).setBrewingTime(-1);
-        Alchemy.brewingStandMap.remove(location);
+        Alchemy.brewingStandMap.remove(brewingStand.getLocation());
     }
 }

+ 2 - 1
src/main/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsManager.java

@@ -14,6 +14,7 @@ import com.gmail.nossr50.util.MetadataConstants;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.player.NotificationManager;
+import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.random.ProbabilityUtil;
 import com.gmail.nossr50.util.skills.ParticleEffectUtils;
 import com.gmail.nossr50.util.skills.RankUtils;
@@ -94,7 +95,7 @@ public class AcrobaticsManager extends SkillManager {
         Player player = getPlayer();
 
         if (!isFatal(modifiedDamage)
-                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ACROBATICS_DODGE, player)) {
+                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ACROBATICS_DODGE, UserManager.getPlayer(player))) {
             ParticleEffectUtils.playDodgeEffect(player);
 
             if (mmoPlayer.useChatNotifications()) {

+ 53 - 11
src/main/java/com/gmail/nossr50/skills/alchemy/AlchemyPotionBrewer.java

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.skills.alchemy;
 
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.alchemy.AlchemyPotion;
 import com.gmail.nossr50.datatypes.skills.alchemy.PotionStage;
@@ -9,7 +10,6 @@ import com.gmail.nossr50.runnables.player.PlayerUpdateInventoryTask;
 import com.gmail.nossr50.runnables.skills.AlchemyBrewCheckTask;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.player.UserManager;
-import org.bukkit.Bukkit;
 import org.bukkit.Material;
 import org.bukkit.block.BlockState;
 import org.bukkit.block.BrewingStand;
@@ -20,14 +20,39 @@ import org.bukkit.inventory.BrewerInventory;
 import org.bukkit.inventory.Inventory;
 import org.bukkit.inventory.InventoryView;
 import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+// TODO: Update to use McMMOPlayer
 public final class AlchemyPotionBrewer {
+    @Deprecated(forRemoval = true, since = "2.2.010")
     public static boolean isValidBrew(Player player, ItemStack[] contents) {
-        if (!isValidIngredient(player, contents[Alchemy.INGREDIENT_SLOT])) {
+        if (!isValidIngredientByPlayer(player, contents[Alchemy.INGREDIENT_SLOT])) {
+            return false;
+        }
+
+        for (int i = 0; i < 3; i++) {
+            if (contents[i] == null || contents[i].getType() != Material.POTION
+                    && contents[i].getType() != Material.SPLASH_POTION
+                    && contents[i].getType() != Material.LINGERING_POTION) {
+                continue;
+            }
+
+            final AlchemyPotion potion = mcMMO.p.getPotionConfig().getPotion(contents[i]);
+            if (getChildPotion(potion, contents[Alchemy.INGREDIENT_SLOT]) != null) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public static boolean isValidBrew(int ingredientLevel, ItemStack[] contents) {
+        if (!isValidIngredientByLevel(ingredientLevel, contents[Alchemy.INGREDIENT_SLOT])) {
             return false;
         }
 
@@ -65,7 +90,7 @@ public final class AlchemyPotionBrewer {
 
         ItemStack ingredient = inventory.getIngredient().clone();
 
-        if (!isEmpty(ingredient) && isValidIngredient(player, ingredient)) {
+        if (!isEmpty(ingredient) && isValidIngredientByPlayer(player, ingredient)) {
             if (ingredient.getAmount() <= 1) {
                 inventory.setIngredient(null);
             }
@@ -79,15 +104,30 @@ public final class AlchemyPotionBrewer {
     private static boolean hasIngredient(BrewerInventory inventory, Player player) {
         ItemStack ingredient = inventory.getIngredient() == null ? null : inventory.getIngredient().clone();
 
-        return !isEmpty(ingredient) && isValidIngredient(player, ingredient);
+        return !isEmpty(ingredient) && isValidIngredientByPlayer(player, ingredient);
+    }
+
+    public static boolean isValidIngredientByPlayer(Player player, ItemStack item) {
+        if (isEmpty(item)) {
+            return false;
+        }
+
+        for (ItemStack ingredient : getValidIngredients(UserManager.getPlayer(player))) {
+            if (item.isSimilar(ingredient)) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
-    public static boolean isValidIngredient(Player player, ItemStack item) {
+    public static boolean isValidIngredientByLevel(int ingredientLevel, ItemStack item) {
         if (isEmpty(item)) {
             return false;
         }
 
-        for (ItemStack ingredient : getValidIngredients(player)) {
+        // TODO: Update this when we fix loading from hoppers
+        for (ItemStack ingredient : mcMMO.p.getPotionConfig().getIngredients(ingredientLevel)) {
             if (item.isSimilar(ingredient)) {
                 return true;
             }
@@ -96,12 +136,13 @@ public final class AlchemyPotionBrewer {
         return false;
     }
 
-    private static List<ItemStack> getValidIngredients(Player player) {
-        if(player == null || UserManager.getPlayer(player) == null) {
+    private static List<ItemStack> getValidIngredients(@Nullable McMMOPlayer mmoPlayer) {
+        if(mmoPlayer == null) {
             return mcMMO.p.getPotionConfig().getIngredients(1);
         }
 
-        return mcMMO.p.getPotionConfig().getIngredients(!Permissions.isSubSkillEnabled(player, SubSkillType.ALCHEMY_CONCOCTIONS) ? 1 : UserManager.getPlayer(player).getAlchemyManager().getTier());
+        return mcMMO.p.getPotionConfig().getIngredients(!Permissions.isSubSkillEnabled(mmoPlayer, SubSkillType.ALCHEMY_CONCOCTIONS)
+                ? 1 : mmoPlayer.getAlchemyManager().getTier());
     }
 
     public static void finishBrewing(BlockState brewingStand, Player player, boolean forced) {
@@ -280,8 +321,9 @@ public final class AlchemyPotionBrewer {
         return false;
     }
 
-    public static void scheduleCheck(Player player, BrewingStand brewingStand) {
-        mcMMO.p.getFoliaLib().getImpl().runAtEntity(player, new AlchemyBrewCheckTask(player, brewingStand));
+    public static void scheduleCheck(@NotNull BrewingStand brewingStand) {
+        mcMMO.p.getFoliaLib().getImpl().runAtLocation(
+                brewingStand.getLocation(), new AlchemyBrewCheckTask(brewingStand));
     }
 
     public static void scheduleUpdate(Inventory inventory) {

+ 2 - 2
src/main/java/com/gmail/nossr50/skills/archery/ArcheryManager.java

@@ -89,7 +89,7 @@ public class ArcheryManager extends SkillManager {
      * @param defender The {@link Player} being affected by the ability
      */
     public double daze(Player defender) {
-        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ARCHERY_DAZE, getPlayer())) {
+        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ARCHERY_DAZE, mmoPlayer)) {
             return 0;
         }
 
@@ -116,7 +116,7 @@ public class ArcheryManager extends SkillManager {
      * @param oldDamage The raw damage value of this arrow before we modify it
      */
     public double skillShot(double oldDamage) {
-        if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.ARCHERY_SKILL_SHOT, getPlayer())) {
+        if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.ARCHERY_SKILL_SHOT, mmoPlayer)) {
             return Archery.getSkillShotBonusDamage(getPlayer(), oldDamage);
         } else {
             return oldDamage;

+ 11 - 5
src/main/java/com/gmail/nossr50/skills/axes/AxesManager.java

@@ -18,6 +18,7 @@ import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.entity.Player;
+import org.bukkit.inventory.EntityEquipment;
 import org.bukkit.inventory.ItemStack;
 import org.jetbrains.annotations.NotNull;
 
@@ -69,7 +70,7 @@ public class AxesManager extends SkillManager {
      * Handle the effects of the Axe Mastery ability
      */
     public double axeMastery() {
-        if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.AXES_AXE_MASTERY, getPlayer())) {
+        if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.AXES_AXE_MASTERY, mmoPlayer)) {
             return Axes.getAxeMasteryBonusDamage(getPlayer());
         }
 
@@ -83,7 +84,7 @@ public class AxesManager extends SkillManager {
      * @param damage The amount of damage initially dealt by the event
      */
     public double criticalHit(LivingEntity target, double damage) {
-        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.AXES_CRITICAL_STRIKES, getPlayer())) {
+        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.AXES_CRITICAL_STRIKES, mmoPlayer)) {
             return 0;
         }
 
@@ -115,10 +116,15 @@ public class AxesManager extends SkillManager {
      */
     public void impactCheck(@NotNull LivingEntity target) {
         double durabilityDamage = getImpactDurabilityDamage();
+        final EntityEquipment equipment = target.getEquipment();
 
-        for (ItemStack armor : target.getEquipment().getArmorContents()) {
+        if (equipment == null) {
+            return;
+        }
+
+        for (ItemStack armor : equipment.getArmorContents()) {
             if (armor != null && ItemUtils.isArmor(armor)) {
-                if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.AXES_ARMOR_IMPACT, getPlayer())) {
+                if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.AXES_ARMOR_IMPACT, mmoPlayer)) {
                     SkillUtils.handleArmorDurabilityChange(armor, durabilityDamage, 1);
                 }
             }
@@ -136,7 +142,7 @@ public class AxesManager extends SkillManager {
      */
     public double greaterImpact(@NotNull LivingEntity target) {
         //static chance (3rd param)
-        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.AXES_GREATER_IMPACT, getPlayer())) {
+        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.AXES_GREATER_IMPACT, mmoPlayer)) {
             return 0;
         }
 

+ 1 - 1
src/main/java/com/gmail/nossr50/skills/crossbows/CrossbowsManager.java

@@ -100,7 +100,7 @@ public class CrossbowsManager extends SkillManager {
     }
 
     public double poweredShot(double oldDamage) {
-        if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.CROSSBOWS_POWERED_SHOT, getPlayer())) {
+        if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.CROSSBOWS_POWERED_SHOT, mmoPlayer)) {
             return getPoweredShotBonusDamage(getPlayer(), oldDamage);
         } else {
             return oldDamage;

+ 4 - 2
src/main/java/com/gmail/nossr50/skills/excavation/ExcavationManager.java

@@ -46,7 +46,8 @@ public class ExcavationManager extends SkillManager {
 
                 for (ExcavationTreasure treasure : treasures) {
                     if (skillLevel >= treasure.getDropLevel()
-                            && ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.EXCAVATION, getPlayer(), treasure.getDropProbability())) {
+                            && ProbabilityUtil.isStaticSkillRNGSuccessful(
+                                    PrimarySkillType.EXCAVATION, mmoPlayer, treasure.getDropProbability())) {
                         processExcavationBonusesOnBlock(blockState, treasure, location);
                     }
                 }
@@ -65,7 +66,8 @@ public class ExcavationManager extends SkillManager {
     @VisibleForTesting
     public void processExcavationBonusesOnBlock(BlockState blockState, ExcavationTreasure treasure, Location location) {
         //Spawn Vanilla XP orbs if a dice roll succeeds
-        if(ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.EXCAVATION, getPlayer(), getArchaelogyExperienceOrbChance())) {
+        if(ProbabilityUtil.isStaticSkillRNGSuccessful(
+                PrimarySkillType.EXCAVATION, mmoPlayer, getArchaelogyExperienceOrbChance())) {
             Misc.spawnExperienceOrb(location, getExperienceOrbsReward());
         }
 

+ 3 - 3
src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java

@@ -412,13 +412,13 @@ public class FishingManager extends SkillManager {
                     enchants.putAll(treasureDrop.getItemMeta().getEnchants());
                 }
 
-                event = EventUtils.callFishingTreasureEvent(player, treasureDrop, treasure.getXp(), enchants);
+                event = EventUtils.callFishingTreasureEvent(mmoPlayer, treasureDrop, treasure.getXp(), enchants);
             } else {
                 if (isMagicHunterEnabled() && ItemUtils.isEnchantable(treasureDrop)) {
                     enchants = processMagicHunter(treasureDrop);
                 }
 
-                event = EventUtils.callFishingTreasureEvent(player, treasureDrop, treasure.getXp(), enchants);
+                event = EventUtils.callFishingTreasureEvent(mmoPlayer, treasureDrop, treasure.getXp(), enchants);
             }
 
             if (!event.isCancelled()) {
@@ -480,7 +480,7 @@ public class FishingManager extends SkillManager {
      * @param target The {@link LivingEntity} affected by the ability
      */
     public void shakeCheck(@NotNull LivingEntity target) {
-        if (ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.FISHING, getPlayer(), getShakeChance())) {
+        if (ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.FISHING, mmoPlayer, getShakeChance())) {
             List<ShakeTreasure> possibleDrops = Fishing.findPossibleDrops(target);
 
             if (possibleDrops == null || possibleDrops.isEmpty()) {

+ 18 - 19
src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java

@@ -33,7 +33,6 @@ import org.bukkit.block.BlockFace;
 import org.bukkit.block.BlockState;
 import org.bukkit.block.data.Ageable;
 import org.bukkit.block.data.BlockData;
-import org.bukkit.entity.Item;
 import org.bukkit.entity.Player;
 import org.bukkit.event.block.BlockBreakEvent;
 import org.bukkit.inventory.ItemStack;
@@ -44,6 +43,7 @@ import java.util.*;
 
 import static com.gmail.nossr50.util.ItemUtils.hasItemIncludingOffHand;
 import static com.gmail.nossr50.util.ItemUtils.removeItemIncludingOffHand;
+import static java.util.Objects.requireNonNull;
 
 public class HerbalismManager extends SkillManager {
     public HerbalismManager(McMMOPlayer mcMMOPlayer) {
@@ -637,12 +637,13 @@ public class HerbalismManager extends SkillManager {
 
     /**
      * Check for success on herbalism double drops
+     *
      * @param blockState target block state
-     * @return true if double drop succeeds
+     * @return true if the double drop succeeds
      */
-    private boolean checkDoubleDrop(BlockState blockState)
-    {
-        return BlockUtils.checkDoubleDrops(getPlayer(), blockState, skill, SubSkillType.HERBALISM_DOUBLE_DROPS);
+    private boolean checkDoubleDrop(@NotNull BlockState blockState) {
+        requireNonNull(blockState, "BlockState cannot be null");
+        return BlockUtils.checkDoubleDrops(mmoPlayer, blockState, SubSkillType.HERBALISM_DOUBLE_DROPS);
     }
 
     /**
@@ -652,7 +653,7 @@ public class HerbalismManager extends SkillManager {
      * @return true if the ability was successful, false otherwise
      */
     public boolean processGreenThumbBlocks(BlockState blockState) {
-        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_GREEN_THUMB, getPlayer())) {
+        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_GREEN_THUMB, mmoPlayer)) {
             NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE_FAILED, "Herbalism.Ability.GTh.Fail");
             return false;
         }
@@ -667,7 +668,7 @@ public class HerbalismManager extends SkillManager {
      * @return true if the ability was successful, false otherwise
      */
     public boolean processHylianLuck(BlockState blockState) {
-        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_HYLIAN_LUCK, getPlayer())) {
+        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_HYLIAN_LUCK, mmoPlayer)) {
             return false;
         }
 
@@ -676,7 +677,6 @@ public class HerbalismManager extends SkillManager {
             return false;
         List<HylianTreasure> treasures = TreasureConfig.getInstance().hylianMap.get(friendly);
 
-        Player player = getPlayer();
 
         if (treasures.isEmpty()) {
             return false;
@@ -686,13 +686,13 @@ public class HerbalismManager extends SkillManager {
 
         for (HylianTreasure treasure : treasures) {
             if (skillLevel >= treasure.getDropLevel()
-                    && ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.HERBALISM, player, treasure.getDropChance())) {
-                if (!EventUtils.simulateBlockBreak(blockState.getBlock(), player)) {
+                    && ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.HERBALISM, mmoPlayer, treasure.getDropChance())) {
+                if (!EventUtils.simulateBlockBreak(blockState.getBlock(), mmoPlayer.getPlayer())) {
                     return false;
                 }
                 blockState.setType(Material.AIR);
                 Misc.spawnItem(getPlayer(), location, treasure.getDrop(), ItemSpawnReason.HYLIAN_LUCK_TREASURE);
-                NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Herbalism.HylianLuck");
+                NotificationManager.sendPlayerInformation(mmoPlayer.getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Herbalism.HylianLuck");
                 return true;
             }
         }
@@ -706,25 +706,24 @@ public class HerbalismManager extends SkillManager {
      * @return true if the ability was successful, false otherwise
      */
     public boolean processShroomThumb(BlockState blockState) {
-        Player player = getPlayer();
-        PlayerInventory playerInventory = player.getInventory();
+        PlayerInventory playerInventory = getPlayer().getInventory();
         
         if (!playerInventory.contains(Material.BROWN_MUSHROOM, 1)) {
-            NotificationManager.sendPlayerInformation(player, NotificationType.REQUIREMENTS_NOT_MET, "Skills.NeedMore", StringUtils.getPrettyItemString(Material.BROWN_MUSHROOM));
+            NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.REQUIREMENTS_NOT_MET, "Skills.NeedMore", StringUtils.getPrettyItemString(Material.BROWN_MUSHROOM));
             return false;
         }
 
         if (!playerInventory.contains(Material.RED_MUSHROOM, 1)) {
-            NotificationManager.sendPlayerInformation(player, NotificationType.REQUIREMENTS_NOT_MET, "Skills.NeedMore", StringUtils.getPrettyItemString(Material.RED_MUSHROOM));
+            NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.REQUIREMENTS_NOT_MET, "Skills.NeedMore", StringUtils.getPrettyItemString(Material.RED_MUSHROOM));
             return false;
         }
 
         playerInventory.removeItem(new ItemStack(Material.BROWN_MUSHROOM));
         playerInventory.removeItem(new ItemStack(Material.RED_MUSHROOM));
-        player.updateInventory();
+        getPlayer().updateInventory();
 
-        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_SHROOM_THUMB, player)) {
-            NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Herbalism.Ability.ShroomThumb.Fail");
+        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_SHROOM_THUMB, mmoPlayer)) {
+            NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE_FAILED, "Herbalism.Ability.ShroomThumb.Fail");
             return false;
         }
 
@@ -789,7 +788,7 @@ public class HerbalismManager extends SkillManager {
             return false;
         }
 
-        if (!greenTerra && !ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_GREEN_THUMB, player)) {
+        if (!greenTerra && !ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_GREEN_THUMB, mmoPlayer)) {
             return false;
         }
 

+ 0 - 2
src/main/java/com/gmail/nossr50/skills/maces/MacesManager.java

@@ -2,9 +2,7 @@ package com.gmail.nossr50.skills.maces;
 
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
-import com.gmail.nossr50.datatypes.skills.ToolType;
 import com.gmail.nossr50.skills.SkillManager;
-import com.gmail.nossr50.util.Permissions;
 
 public class MacesManager extends SkillManager {
     public MacesManager(McMMOPlayer mmoPlayer) {

+ 2 - 2
src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java

@@ -115,7 +115,7 @@ public class MiningManager extends SkillManager {
 
     private boolean processTripleDrops(@NotNull BlockState blockState) {
         //TODO: Make this readable
-        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.MINING_MOTHER_LODE, getPlayer())) {
+        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.MINING_MOTHER_LODE, mmoPlayer)) {
             BlockUtils.markDropsAsBonus(blockState, 2);
             return true;
         } else {
@@ -125,7 +125,7 @@ public class MiningManager extends SkillManager {
 
     private void processDoubleDrops(@NotNull BlockState blockState) {
         //TODO: Make this readable
-        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.MINING_DOUBLE_DROPS, getPlayer())) {
+        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.MINING_DOUBLE_DROPS, mmoPlayer)) {
             boolean useTriple = mmoPlayer.getAbilityMode(SuperAbilityType.SUPER_BREAKER) && mcMMO.p.getAdvancedConfig().getAllowMiningTripleDrops();
             BlockUtils.markDropsAsBonus(blockState, useTriple);
         }

+ 3 - 3
src/main/java/com/gmail/nossr50/skills/repair/RepairManager.java

@@ -329,7 +329,7 @@ public class RepairManager extends SkillManager {
         if(!RankUtils.hasUnlockedSubskill(getPlayer(), SubSkillType.REPAIR_SUPER_REPAIR))
             return false;
 
-        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.REPAIR_SUPER_REPAIR, getPlayer())) {
+        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.REPAIR_SUPER_REPAIR, mmoPlayer)) {
             NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Repair.Skills.FeltEasy");
             return true;
         }
@@ -380,10 +380,10 @@ public class RepairManager extends SkillManager {
 
             Enchantment enchantment = enchant.getKey();
 
-            if (ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.REPAIR, getPlayer(), getKeepEnchantChance())) {
+            if (ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.REPAIR, mmoPlayer, getKeepEnchantChance())) {
 
                 if (ArcaneForging.arcaneForgingDowngrades && enchantLevel > 1
-                        && (!ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.REPAIR, getPlayer(), 100 - getDowngradeEnchantChance()))) {
+                        && (!ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.REPAIR, mmoPlayer, 100 - getDowngradeEnchantChance()))) {
                     item.addUnsafeEnchantment(enchantment, enchantLevel - 1);
                     downgraded = true;
                 }

+ 5 - 3
src/main/java/com/gmail/nossr50/skills/salvage/SalvageManager.java

@@ -125,7 +125,7 @@ public class SalvageManager extends SkillManager {
 
         for(int x = 0; x < potentialSalvageYield-1; x++) {
 
-            if(ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.SALVAGE, player, chanceOfSuccess)) {
+            if(ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.SALVAGE, mmoPlayer, chanceOfSuccess)) {
                 chanceOfSuccess-=3;
                 chanceOfSuccess = Math.max(chanceOfSuccess, 90);
 
@@ -232,12 +232,14 @@ public class SalvageManager extends SkillManager {
 
             if (!Salvage.arcaneSalvageEnchantLoss
                     || Permissions.hasSalvageEnchantBypassPerk(player)
-                    || ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.SALVAGE, player, getExtractFullEnchantChance())) {
+                    || ProbabilityUtil.isStaticSkillRNGSuccessful(
+                            PrimarySkillType.SALVAGE, mmoPlayer, getExtractFullEnchantChance())) {
                 enchantMeta.addStoredEnchant(enchant.getKey(), enchantLevel, true);
             }
             else if (enchantLevel > 1
                     && Salvage.arcaneSalvageDowngrades
-                    && ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.SALVAGE, player, getExtractPartialEnchantChance())) {
+                    && ProbabilityUtil.isStaticSkillRNGSuccessful(
+                            PrimarySkillType.SALVAGE, mmoPlayer, getExtractPartialEnchantChance())) {
                 enchantMeta.addStoredEnchant(enchant.getKey(), enchantLevel - 1, true);
                 downgraded = true;
             } else {

+ 1 - 1
src/main/java/com/gmail/nossr50/skills/smelting/SmeltingManager.java

@@ -24,7 +24,7 @@ public class SmeltingManager extends SkillManager {
 
     public boolean isSecondSmeltSuccessful() {
         return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.SMELTING_SECOND_SMELT)
-                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.SMELTING_SECOND_SMELT, getPlayer());
+                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.SMELTING_SECOND_SMELT, mmoPlayer);
     }
 
     /**

+ 2 - 2
src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java

@@ -76,7 +76,7 @@ public class SwordsManager extends SkillManager {
         }
 
         double ruptureOdds = mcMMO.p.getAdvancedConfig().getRuptureChanceToApplyOnHit(getRuptureRank());
-        if (ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.SWORDS, this.getPlayer(), ruptureOdds)) {
+        if (ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.SWORDS, mmoPlayer, ruptureOdds)) {
 
             if (target instanceof Player defender) {
 
@@ -142,7 +142,7 @@ public class SwordsManager extends SkillManager {
      */
     public void counterAttackChecks(@NotNull LivingEntity attacker, double damage) {
 
-        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.SWORDS_COUNTER_ATTACK, getPlayer())) {
+        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.SWORDS_COUNTER_ATTACK, mmoPlayer)) {
             CombatUtils.dealDamage(attacker, damage / Swords.counterAttackModifier, getPlayer());
 
             NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Countered");

+ 5 - 3
src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java

@@ -31,6 +31,8 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.HashMap;
 
+import static com.gmail.nossr50.util.MobMetadataUtils.flagMetadata;
+
 public class TamingManager extends SkillManager {
     //TODO: Temporary static cache, will be changed in 2.2
     private static HashMap<Material, CallOfTheWildType> summoningItems;
@@ -143,7 +145,7 @@ public class TamingManager extends SkillManager {
      * @param damage The damage being absorbed by the wolf
      */
     public void fastFoodService(@NotNull Wolf wolf, double damage) {
-        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.TAMING_FAST_FOOD_SERVICE, getPlayer())) {
+        if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.TAMING_FAST_FOOD_SERVICE, mmoPlayer)) {
             return;
         }
 
@@ -262,7 +264,7 @@ public class TamingManager extends SkillManager {
         if(!RankUtils.hasUnlockedSubskill(getPlayer(), SubSkillType.TAMING_PUMMEL))
             return;
 
-        if(!ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.TAMING, getPlayer(), mcMMO.p.getAdvancedConfig().getPummelChance()))
+        if(!ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.TAMING, mmoPlayer, mcMMO.p.getAdvancedConfig().getPummelChance()))
             return;
 
         ParticleEffectUtils.playGreaterImpactEffect(target);
@@ -459,7 +461,7 @@ public class TamingManager extends SkillManager {
 
     private void applyMetaDataToCOTWEntity(LivingEntity summonedEntity) {
         //This helps identify the entity as being summoned by COTW
-        mcMMO.getMetadataService().getMobMetadataService().flagMetadata(MobMetaFlagType.COTW_SUMMONED_MOB, summonedEntity);
+        flagMetadata(MobMetaFlagType.COTW_SUMMONED_MOB, summonedEntity);
     }
 
     /**

+ 0 - 2
src/main/java/com/gmail/nossr50/skills/tridents/TridentsManager.java

@@ -3,9 +3,7 @@ package com.gmail.nossr50.skills.tridents;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
-import com.gmail.nossr50.datatypes.skills.ToolType;
 import com.gmail.nossr50.skills.SkillManager;
-import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.skills.RankUtils;
 
 public class TridentsManager extends SkillManager {

+ 5 - 5
src/main/java/com/gmail/nossr50/skills/unarmed/UnarmedManager.java

@@ -67,7 +67,7 @@ public class UnarmedManager extends SkillManager {
     }
 
     public boolean blockCrackerCheck(@NotNull BlockState blockState) {
-        if (!ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.UNARMED_BLOCK_CRACKER, getPlayer())) {
+        if (!ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.UNARMED_BLOCK_CRACKER, mmoPlayer)) {
             return false;
         }
 
@@ -98,7 +98,7 @@ public class UnarmedManager extends SkillManager {
      * @param defender The defending player
      */
     public void disarmCheck(@NotNull Player defender) {
-        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.UNARMED_DISARM, getPlayer()) && !hasIronGrip(defender)) {
+        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.UNARMED_DISARM, mmoPlayer) && !hasIronGrip(defender)) {
             if (EventUtils.callDisarmEvent(defender).isCancelled()) {
                 return;
             }
@@ -121,7 +121,7 @@ public class UnarmedManager extends SkillManager {
      * Check for arrow deflection.
      */
     public boolean deflectCheck() {
-        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.UNARMED_ARROW_DEFLECT, getPlayer())) {
+        if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.UNARMED_ARROW_DEFLECT, mmoPlayer)) {
             NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Combat.ArrowDeflect");
             return true;
         }
@@ -144,7 +144,7 @@ public class UnarmedManager extends SkillManager {
      * Handle the effects of the Iron Arm ability
      */
     public double calculateSteelArmStyleDamage() {
-        if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.UNARMED_STEEL_ARM_STYLE, getPlayer())) {
+        if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.UNARMED_STEEL_ARM_STYLE, mmoPlayer)) {
             return getSteelArmStyleDamage();
         }
 
@@ -178,7 +178,7 @@ public class UnarmedManager extends SkillManager {
     private boolean hasIronGrip(@NotNull Player defender) {
         if (!Misc.isNPCEntityExcludingVillagers(defender)
                 && Permissions.isSubSkillEnabled(defender, SubSkillType.UNARMED_IRON_GRIP)
-                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.UNARMED_IRON_GRIP, defender)) {
+                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.UNARMED_IRON_GRIP, UserManager.getPlayer(defender))) {
             NotificationManager.sendPlayerInformation(defender, NotificationType.SUBSKILL_MESSAGE, "Unarmed.Ability.IronGrip.Defender");
             NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Unarmed.Ability.IronGrip.Attacker");
 

+ 3 - 3
src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java

@@ -72,14 +72,14 @@ public class WoodcuttingManager extends SkillManager {
     private boolean checkHarvestLumberActivation(Material material) {
         return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
                 && RankUtils.hasReachedRank(1, getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
-                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.WOODCUTTING_HARVEST_LUMBER, getPlayer())
+                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.WOODCUTTING_HARVEST_LUMBER, mmoPlayer)
                 && mcMMO.p.getGeneralConfig().getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, material);
     }
 
     private boolean checkCleanCutsActivation(Material material) {
         return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
                 && RankUtils.hasReachedRank(1, getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
-                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.WOODCUTTING_CLEAN_CUTS, getPlayer())
+                && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.WOODCUTTING_CLEAN_CUTS, mmoPlayer)
                 && mcMMO.p.getGeneralConfig().getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, material);
     }
 
@@ -336,7 +336,7 @@ public class WoodcuttingManager extends SkillManager {
                 if(RankUtils.hasUnlockedSubskill(player, SubSkillType.WOODCUTTING_KNOCK_ON_WOOD)) {
                     if(RankUtils.hasReachedRank(2, player, SubSkillType.WOODCUTTING_KNOCK_ON_WOOD)) {
                         if(mcMMO.p.getAdvancedConfig().isKnockOnWoodXPOrbEnabled()) {
-                            if(ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.WOODCUTTING, player, 10)) {
+                            if(ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.WOODCUTTING, mmoPlayer, 10)) {
                                 int randOrbCount = Math.max(1, Misc.getRandom().nextInt(100));
                                 Misc.spawnExperienceOrb(blockState.getLocation(), randOrbCount);
                             }

+ 26 - 3
src/main/java/com/gmail/nossr50/util/BlockUtils.java

@@ -2,11 +2,13 @@ package com.gmail.nossr50.util;
 
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.datatypes.meta.BonusDropMeta;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.skills.repair.Repair;
 import com.gmail.nossr50.skills.salvage.Salvage;
+import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.random.ProbabilityUtil;
 import org.bukkit.Material;
 import org.bukkit.World;
@@ -16,9 +18,12 @@ import org.bukkit.block.data.Ageable;
 import org.bukkit.block.data.BlockData;
 import org.bukkit.entity.Player;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.HashSet;
 
+import static java.util.Objects.requireNonNull;
+
 public final class BlockUtils {
 
     public static final String SHORT_GRASS = "SHORT_GRASS";
@@ -94,10 +99,28 @@ public final class BlockUtils {
      *
      * @param blockState the blockstate
      * @return true if the player succeeded in the check
+     * @deprecated Use {@link #checkDoubleDrops(McMMOPlayer, BlockState, SubSkillType)} instead
+     */
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public static boolean checkDoubleDrops(Player player, BlockState blockState, PrimarySkillType ignored, SubSkillType subSkillType) {
+        return checkDoubleDrops(UserManager.getPlayer(player), blockState, subSkillType);
+    }
+
+    /**
+     * Checks if a player successfully passed the double drop check
+     *
+     * @param mmoPlayer    the player involved in the check
+     * @param blockState   the blockstate of the block
+     * @param subSkillType the subskill involved
+     * @return true if the player succeeded in the check
      */
-    public static boolean checkDoubleDrops(Player player, BlockState blockState, PrimarySkillType skillType, SubSkillType subSkillType) {
-        if (mcMMO.p.getGeneralConfig().getDoubleDropsEnabled(skillType, blockState.getType()) && Permissions.isSubSkillEnabled(player, subSkillType)) {
-            return ProbabilityUtil.isSkillRNGSuccessful(subSkillType, player);
+    public static boolean checkDoubleDrops(@Nullable McMMOPlayer mmoPlayer, @NotNull BlockState blockState,
+                                           @NotNull SubSkillType subSkillType) {
+        requireNonNull(blockState, "blockState cannot be null");
+        requireNonNull(subSkillType, "subSkillType cannot be null");
+        if (mcMMO.p.getGeneralConfig().getDoubleDropsEnabled(subSkillType.getParentSkill(), blockState.getType())
+                && Permissions.isSubSkillEnabled(mmoPlayer, subSkillType)) {
+            return ProbabilityUtil.isSkillRNGSuccessful(subSkillType, mmoPlayer);
         }
 
         return false;

+ 116 - 0
src/main/java/com/gmail/nossr50/util/ContainerMetadataUtils.java

@@ -0,0 +1,116 @@
+package com.gmail.nossr50.util;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.player.UserManager;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.block.BlockState;
+import org.bukkit.entity.Player;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataHolder;
+import org.bukkit.persistence.PersistentDataType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.UUID;
+
+import static com.gmail.nossr50.util.MetadataService.NSK_CONTAINER_UUID_LEAST_SIG;
+import static com.gmail.nossr50.util.MetadataService.NSK_CONTAINER_UUID_MOST_SIG;
+import static java.util.Objects.requireNonNull;
+
+public class ContainerMetadataUtils {
+
+    public static void changeContainerOwnership(@NotNull BlockState blockState, @NotNull Player player) {
+        requireNonNull(blockState, "blockState cannot be null");
+        requireNonNull(player, "Player cannot be null");
+
+        final McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
+
+        /*
+            Debug output
+         */
+        printOwnershipGainDebug(blockState, mmoPlayer);
+        printOwnershipLossDebug(blockState);
+        setOwner(blockState, player.getUniqueId());
+    }
+
+    public static void printOwnershipGainDebug(@NotNull BlockState blockState, @Nullable McMMOPlayer mmoPlayer) {
+        if(mmoPlayer != null && mmoPlayer.isDebugMode()) {
+            mmoPlayer.getPlayer().sendMessage("Container ownership " +
+                    ChatColor.GREEN +"gained " + ChatColor.RESET +
+                    "at location: " + blockState.getLocation().toString());
+        }
+    }
+
+    public static void printOwnershipLossDebug(BlockState blockState) {
+        OfflinePlayer containerOwner = getContainerOwner(blockState);
+
+        if(containerOwner != null && containerOwner.isOnline()) {
+            final McMMOPlayer mmoContainerOwner = UserManager.getPlayer(containerOwner.getPlayer());
+
+            if(mmoContainerOwner != null) {
+                if(mmoContainerOwner.isDebugMode()) {
+                    mmoContainerOwner.getPlayer().sendMessage("Container ownership " +
+                            ChatColor.RED + "lost " + ChatColor.RESET +
+                            "at location: " + blockState.getLocation().toString());
+                }
+            }
+        }
+    }
+
+    public static @Nullable OfflinePlayer getContainerOwner(BlockState container) {
+        if (container instanceof PersistentDataHolder persistentDataHolder) {
+            final UUID uuid = getOwner(persistentDataHolder);
+
+            if(uuid != null) {
+                return Bukkit.getOfflinePlayer(uuid);
+            }
+        }
+
+        return null;
+    }
+
+
+    public static boolean isContainerOwned(BlockState blockState) {
+        return getContainerOwner(blockState) != null;
+    }
+
+    public static void processContainerOwnership(BlockState blockState, Player player) {
+        if(!mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.SMELTING))
+            return;
+
+        if(getContainerOwner(blockState) != null) {
+            if(getContainerOwner(blockState).getUniqueId().equals(player.getUniqueId()))
+                return;
+        }
+
+        changeContainerOwnership(blockState, player);
+    }
+
+    public static @Nullable UUID getOwner(@NotNull PersistentDataHolder persistentDataHolder) {
+        //Get container from entity
+        final PersistentDataContainer dataContainer = persistentDataHolder.getPersistentDataContainer();
+
+        //Too lazy to make a custom data type for this stuff
+        final Long mostSigBits = dataContainer.get(NSK_CONTAINER_UUID_MOST_SIG, PersistentDataType.LONG);
+        final Long leastSigBits = dataContainer.get(NSK_CONTAINER_UUID_LEAST_SIG, PersistentDataType.LONG);
+
+        if (mostSigBits != null && leastSigBits != null) {
+            return new UUID(mostSigBits, leastSigBits);
+        } else {
+            return null;
+        }
+    }
+
+    public static void setOwner(@NotNull BlockState blockState, @NotNull UUID uuid) {
+        PersistentDataContainer dataContainer = ((PersistentDataHolder) blockState).getPersistentDataContainer();
+
+        dataContainer.set(NSK_CONTAINER_UUID_MOST_SIG, PersistentDataType.LONG, uuid.getMostSignificantBits());
+        dataContainer.set(NSK_CONTAINER_UUID_LEAST_SIG, PersistentDataType.LONG, uuid.getLeastSignificantBits());
+
+        blockState.update();
+    }
+}

+ 38 - 25
src/main/java/com/gmail/nossr50/util/EventUtils.java

@@ -10,7 +10,6 @@ import com.gmail.nossr50.datatypes.player.PlayerProfile;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
-import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
 import com.gmail.nossr50.events.experience.McMMOPlayerLevelChangeEvent;
 import com.gmail.nossr50.events.experience.McMMOPlayerLevelDownEvent;
 import com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent;
@@ -59,6 +58,8 @@ import org.jetbrains.annotations.NotNull;
 import java.util.HashMap;
 import java.util.Map;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * This class is meant to help make event related code less boilerplate
  */
@@ -163,8 +164,17 @@ public final class EventUtils {
      * Others
      */
 
-    public static @NotNull McMMOPlayerAbilityActivateEvent callPlayerAbilityActivateEvent(@NotNull Player player, @NotNull PrimarySkillType skill) {
-        McMMOPlayerAbilityActivateEvent event = new McMMOPlayerAbilityActivateEvent(player, skill);
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public static @NotNull McMMOPlayerAbilityActivateEvent callPlayerAbilityActivateEvent(@NotNull Player player,
+                                                                                          @NotNull PrimarySkillType skill) {
+        return callPlayerAbilityActivateEvent(requireNonNull(UserManager.getPlayer(player)), skill);
+    }
+
+    public static @NotNull McMMOPlayerAbilityActivateEvent callPlayerAbilityActivateEvent(@NotNull McMMOPlayer mmoPlayer,
+                                                                                          @NotNull PrimarySkillType skill) {
+        requireNonNull(mmoPlayer, "mmoPlayer cannot be null");
+        requireNonNull(skill, "skill cannot be null");
+        McMMOPlayerAbilityActivateEvent event = new McMMOPlayerAbilityActivateEvent(mmoPlayer, skill);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
         return event;
@@ -183,47 +193,40 @@ public final class EventUtils {
      * @param subSkillType target subskill
      * @return the event after it has been fired
      */
+    @Deprecated(forRemoval = true, since = "2.2.010")
     public static @NotNull SubSkillEvent callSubSkillEvent(@NotNull Player player, @NotNull SubSkillType subSkillType) {
-        SubSkillEvent event = new SubSkillEvent(player, subSkillType);
-        mcMMO.p.getServer().getPluginManager().callEvent(event);
-
-        return event;
+        return callSubSkillEvent(requireNonNull(UserManager.getPlayer(player)), subSkillType);
     }
 
     /**
-     * Calls a new SubSkillBlockEvent for this SubSkill and its related block and then returns it
-     * @param player target player
+     * Calls a new SubSkillEvent for this SubSkill and then returns it
+     * @param mmoPlayer target mmoPlayer
      * @param subSkillType target subskill
-     * @param block associated block
      * @return the event after it has been fired
      */
-    public static @NotNull SubSkillBlockEvent callSubSkillBlockEvent(@NotNull Player player, @NotNull SubSkillType subSkillType, @NotNull Block block) {
-        SubSkillBlockEvent event = new SubSkillBlockEvent(player, subSkillType, block);
+    public static @NotNull SubSkillEvent callSubSkillEvent(@NotNull McMMOPlayer mmoPlayer, @NotNull SubSkillType subSkillType) {
+        requireNonNull(mmoPlayer, "mmoPlayer cannot be null");
+        requireNonNull(subSkillType, "subSkillType cannot be null");
+        final SubSkillEvent event = new SubSkillEvent(mmoPlayer, subSkillType);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
         return event;
     }
 
     /**
-     * Calls a new SubSkillEvent for this SubSkill and then returns it
+     * Calls a new SubSkillBlockEvent for this SubSkill and its related block and then returns it
      * @param player target player
-     * @param abstractSubSkill target subskill
+     * @param subSkillType target subskill
+     * @param block associated block
      * @return the event after it has been fired
      */
-    public static SubSkillEvent callSubSkillEvent(Player player, AbstractSubSkill abstractSubSkill) {
-        SubSkillEvent event = new SubSkillEvent(player, abstractSubSkill);
+    public static @NotNull SubSkillBlockEvent callSubSkillBlockEvent(@NotNull Player player, @NotNull SubSkillType subSkillType, @NotNull Block block) {
+        SubSkillBlockEvent event = new SubSkillBlockEvent(player, subSkillType, block);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
         return event;
     }
 
-//    public static Event callFakeArmSwingEvent(@NotNull Player player) {
-//        PlayerAnimationEvent event = new FakePlayerAnimationEvent(player, PlayerAnimationType.ARM_SWING);
-//        mcMMO.p.getServer().getPluginManager().callEvent(event);
-//
-//        return event;
-//    }
-
     public static boolean tryLevelChangeEvent(Player player, PrimarySkillType skill, int levelsChanged, float xpRemoved, boolean isLevelUp, XPGainReason xpGainReason) {
         McMMOPlayerLevelChangeEvent event = isLevelUp ? new McMMOPlayerLevelUpEvent(player, skill, levelsChanged, xpGainReason) : new McMMOPlayerLevelDownEvent(player, skill, levelsChanged, xpGainReason);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
@@ -497,15 +500,25 @@ public final class EventUtils {
         return !isCancelled;
     }
 
+    @Deprecated(forRemoval = true, since = "2.2.010")
     public static McMMOPlayerAbilityDeactivateEvent callAbilityDeactivateEvent(Player player, SuperAbilityType ability) {
-        McMMOPlayerAbilityDeactivateEvent event = new McMMOPlayerAbilityDeactivateEvent(player, mcMMO.p.getSkillTools().getPrimarySkillBySuperAbility(ability));
+        return callAbilityDeactivateEvent(requireNonNull(UserManager.getPlayer(player)), ability);
+    }
+
+    public static McMMOPlayerAbilityDeactivateEvent callAbilityDeactivateEvent(@NotNull McMMOPlayer mmoPlayer, @NotNull SuperAbilityType ability) {
+        final McMMOPlayerAbilityDeactivateEvent event = new McMMOPlayerAbilityDeactivateEvent(mmoPlayer, mcMMO.p.getSkillTools().getPrimarySkillBySuperAbility(ability));
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
         return event;
     }
 
+    @Deprecated(forRemoval = true, since = "2.2.010")
     public static McMMOPlayerFishingTreasureEvent callFishingTreasureEvent(Player player, ItemStack treasureDrop, int treasureXp, Map<Enchantment, Integer> enchants) {
-        McMMOPlayerFishingTreasureEvent event = enchants.isEmpty() ? new McMMOPlayerFishingTreasureEvent(player, treasureDrop, treasureXp) : new McMMOPlayerMagicHunterEvent(player, treasureDrop, treasureXp, enchants);
+        return callFishingTreasureEvent(requireNonNull(UserManager.getPlayer(player)), treasureDrop, treasureXp, enchants);
+    }
+
+    public static McMMOPlayerFishingTreasureEvent callFishingTreasureEvent(McMMOPlayer mmoPlayer, ItemStack treasureDrop, int treasureXp, Map<Enchantment, Integer> enchants) {
+        final McMMOPlayerFishingTreasureEvent event = enchants.isEmpty() ? new McMMOPlayerFishingTreasureEvent(mmoPlayer, treasureDrop, treasureXp) : new McMMOPlayerMagicHunterEvent(mmoPlayer, treasureDrop, treasureXp, enchants);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
         return event;

+ 11 - 16
src/main/java/com/gmail/nossr50/metadata/ItemMetadataService.java → src/main/java/com/gmail/nossr50/util/ItemMetadataUtils.java

@@ -1,4 +1,4 @@
-package com.gmail.nossr50.metadata;
+package com.gmail.nossr50.util;
 
 import com.gmail.nossr50.mcMMO;
 import org.bukkit.inventory.ItemStack;
@@ -9,18 +9,17 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.List;
 
-import static com.gmail.nossr50.metadata.MetadataService.NSK_SUPER_ABILITY_BOOSTED_ITEM;
+import static com.gmail.nossr50.util.MetadataService.NSK_SUPER_ABILITY_BOOSTED_ITEM;
 
-public class ItemMetadataService {
+public final class ItemMetadataUtils {
 
-    public final @NotNull String LEGACY_ABILITY_TOOL_LORE = "mcMMO Ability Tool";
-    public final @NotNull mcMMO pluginRef;
+    public static final @NotNull String LEGACY_ABILITY_TOOL_LORE = "mcMMO Ability Tool";
 
-    public ItemMetadataService(@NotNull mcMMO pluginRef) {
-        this.pluginRef = pluginRef;
+    private ItemMetadataUtils() {
+        // private ctor
     }
 
-    public void setSuperAbilityBoostedItem(@NotNull ItemStack itemStack, int originalDigSpeed) {
+    public static void setSuperAbilityBoostedItem(@NotNull ItemStack itemStack, int originalDigSpeed) {
         if (itemStack.getItemMeta() == null) {
             mcMMO.p.getLogger().severe("Can not assign persistent data to an item with null item metadata");
             return;
@@ -34,7 +33,7 @@ public class ItemMetadataService {
         itemStack.setItemMeta(itemMeta);
     }
 
-    public boolean isSuperAbilityBoosted(@NotNull ItemStack itemStack) {
+    public static boolean isSuperAbilityBoosted(@NotNull ItemStack itemStack) {
         if (itemStack.getItemMeta() == null)
             return false;
 
@@ -48,7 +47,7 @@ public class ItemMetadataService {
         return boostValue != null;
     }
 
-    public int getSuperAbilityToolOriginalDigSpeed(@NotNull ItemStack itemStack) {
+    public static int getSuperAbilityToolOriginalDigSpeed(@NotNull ItemStack itemStack) {
         //Get container from entity
         ItemMeta itemMeta = itemStack.getItemMeta();
 
@@ -67,7 +66,7 @@ public class ItemMetadataService {
         }
     }
 
-    public void removeBonusDigSpeedOnSuperAbilityTool(@NotNull ItemStack itemStack) {
+    public static void removeBonusDigSpeedOnSuperAbilityTool(@NotNull ItemStack itemStack) {
         int originalSpeed = getSuperAbilityToolOriginalDigSpeed(itemStack);
         ItemMeta itemMeta = itemStack.getItemMeta();
 
@@ -89,7 +88,7 @@ public class ItemMetadataService {
         }
     }
 
-    public boolean isLegacyAbilityTool(@NotNull ItemStack itemStack) {
+    public static boolean isLegacyAbilityTool(@NotNull ItemStack itemStack) {
         ItemMeta itemMeta = itemStack.getItemMeta();
 
         if (itemMeta == null)
@@ -102,8 +101,4 @@ public class ItemMetadataService {
 
         return lore.contains(LEGACY_ABILITY_TOOL_LORE);
     }
-
-    public @NotNull String getLegacyAbilityToolLore() {
-        return LEGACY_ABILITY_TOOL_LORE;
-    }
 }

+ 3 - 2
src/main/java/com/gmail/nossr50/util/MetadataConstants.java

@@ -34,8 +34,9 @@ public class MetadataConstants {
     public static final @NotNull String METADATA_KEY_DISARMED_ITEM = "mcMMO: Disarmed Item";
     public static final @NotNull String METADATA_KEY_PLAYER_DATA = "mcMMO: Player Data";
     public static final @NotNull String METADATA_KEY_DATABASE_COMMAND = "mcMMO: Processing Database Command";
-    public static final @NotNull String METADATA_KEY_FURNACE_UUID_MOST_SIG = "furnace_uuid_most_sig";
-    public static final @NotNull String METADATA_KEY_FURNACE_UUID_LEAST_SIG = "furnace_uuid_least_sig";
+    // the value of these two keys have "furnace" to keep supporting legacy data
+    public static final @NotNull String METADATA_KEY_CONTAINER_UUID_MOST_SIG = "furnace_uuid_most_sig";
+    public static final @NotNull String METADATA_KEY_CONTAINER_UUID_LEAST_SIG = "furnace_uuid_least_sig";
     public static final @NotNull String METADATA_KEY_SUPER_ABILITY_BOOSTED_ITEM = "super_ability_boosted";
     public static final @NotNull String METADATA_KEY_MOB_SPAWNER_MOB = "mcmmo_mob_spawner_mob";
     public static final @NotNull String METADATA_KEY_EGG_MOB = "mcmmo_egg_mob";

+ 48 - 0
src/main/java/com/gmail/nossr50/util/MetadataService.java

@@ -0,0 +1,48 @@
+package com.gmail.nossr50.util;
+
+import com.gmail.nossr50.mcMMO;
+import org.bukkit.NamespacedKey;
+import org.jetbrains.annotations.NotNull;
+
+public final class MetadataService {
+    static final @NotNull NamespacedKey NSK_SUPER_ABILITY_BOOSTED_ITEM;
+    static final @NotNull NamespacedKey NSK_MOB_SPAWNER_MOB;
+    static final @NotNull NamespacedKey NSK_EGG_MOB;
+    static final @NotNull NamespacedKey NSK_NETHER_GATE_MOB;
+    static final @NotNull NamespacedKey NSK_COTW_SUMMONED_MOB;
+    static final @NotNull NamespacedKey NSK_PLAYER_BRED_MOB;
+    static final @NotNull NamespacedKey NSK_PLAYER_TAMED_MOB;
+    static final @NotNull NamespacedKey NSK_VILLAGER_TRADE_ORIGIN_ITEM;
+    static final @NotNull NamespacedKey NSK_EXPLOITED_ENDERMEN;
+    static final @NotNull NamespacedKey NSK_CONTAINER_UUID_MOST_SIG;
+    static final @NotNull NamespacedKey NSK_CONTAINER_UUID_LEAST_SIG;
+
+    private MetadataService() {
+        // private ctor
+    }
+
+    static {
+        NSK_SUPER_ABILITY_BOOSTED_ITEM = getNamespacedKey(MetadataConstants.METADATA_KEY_SUPER_ABILITY_BOOSTED_ITEM);
+        NSK_MOB_SPAWNER_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_MOB_SPAWNER_MOB);
+        NSK_EGG_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_EGG_MOB);
+        NSK_NETHER_GATE_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_NETHER_PORTAL_MOB);
+        NSK_COTW_SUMMONED_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_COTW_SUMMONED_MOB);
+        NSK_PLAYER_BRED_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_PLAYER_BRED_MOB);
+        NSK_PLAYER_TAMED_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_PLAYER_TAMED_MOB);
+        NSK_VILLAGER_TRADE_ORIGIN_ITEM = getNamespacedKey(MetadataConstants.METADATA_KEY_VILLAGER_TRADE_ORIGIN_ITEM);
+        NSK_EXPLOITED_ENDERMEN = getNamespacedKey(MetadataConstants.METADATA_KEY_EXPLOITED_ENDERMEN);
+        NSK_CONTAINER_UUID_MOST_SIG = getNamespacedKey(MetadataConstants.METADATA_KEY_CONTAINER_UUID_MOST_SIG);
+        NSK_CONTAINER_UUID_LEAST_SIG = getNamespacedKey(MetadataConstants.METADATA_KEY_CONTAINER_UUID_LEAST_SIG);
+    }
+
+    /**
+     * Helper method to simplify generating namespaced keys
+     *
+     * @param key the {@link String} value of the key
+     *
+     * @return the generated {@link NamespacedKey}
+     */
+    public static @NotNull NamespacedKey getNamespacedKey(@NotNull String key) {
+        return new NamespacedKey(mcMMO.p, key);
+    }
+}

+ 20 - 19
src/main/java/com/gmail/nossr50/metadata/MobMetadataService.java → src/main/java/com/gmail/nossr50/util/MobMetadataUtils.java

@@ -1,9 +1,8 @@
-package com.gmail.nossr50.metadata;
+package com.gmail.nossr50.util;
 
 import com.gmail.nossr50.api.exceptions.IncompleteNamespacedKeyRegister;
 import com.gmail.nossr50.config.PersistentDataConfig;
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.util.MetadataConstants;
+import com.gmail.nossr50.metadata.MobMetaFlagType;
 import org.bukkit.NamespacedKey;
 import org.bukkit.entity.Entity;
 import org.bukkit.entity.LivingEntity;
@@ -15,17 +14,19 @@ import java.util.EnumMap;
 import java.util.HashSet;
 import java.util.WeakHashMap;
 
-import static com.gmail.nossr50.metadata.MetadataService.*;
+import static com.gmail.nossr50.util.MetadataService.*;
 
 //TODO: Use SpawnReason where appropriate instead of MobMetaFlagType
-public class MobMetadataService {
-    private final @NotNull WeakHashMap<Entity, HashSet<MobMetaFlagType>> mobRegistry; //transient data
-    private final @NotNull EnumMap<MobMetaFlagType, NamespacedKey> mobFlagKeyMap; //used for persistent data
-    private final @NotNull mcMMO pluginRef;
-    private boolean isUsingPersistentData = false;
-
-    public MobMetadataService(@NotNull mcMMO pluginRef) {
-        this.pluginRef = pluginRef;
+public final class MobMetadataUtils {
+    private static final @NotNull WeakHashMap<Entity, HashSet<MobMetaFlagType>> mobRegistry; //transient data
+    private static final @NotNull EnumMap<MobMetaFlagType, NamespacedKey> mobFlagKeyMap; //used for persistent data
+    private static boolean isUsingPersistentData = false;
+
+    private MobMetadataUtils() {
+        // private ctor
+    }
+
+    static {
         mobFlagKeyMap = new EnumMap<>(MobMetaFlagType.class);
         mobRegistry = new WeakHashMap<>();
         initMobFlagKeyMap();
@@ -40,7 +41,7 @@ public class MobMetadataService {
      * Registers the namespaced keys required by the API (CB/Spigot)
      * Used primarily for persistent data
      */
-    private void initMobFlagKeyMap() throws IncompleteNamespacedKeyRegister {
+    private static void initMobFlagKeyMap() throws IncompleteNamespacedKeyRegister {
         for (MobMetaFlagType mobMetaFlagType : MobMetaFlagType.values()) {
             switch (mobMetaFlagType) {
                 case MOB_SPAWNER_MOB -> mobFlagKeyMap.put(mobMetaFlagType, NSK_MOB_SPAWNER_MOB);
@@ -63,7 +64,7 @@ public class MobMetadataService {
      *
      * @return true if the mob has metadata values for target {@link MobMetaFlagType}
      */
-    public boolean hasMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
+    public static boolean hasMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
         if (PersistentDataConfig.getInstance().isMobPersistent(flag)) {
             return livingEntity.getPersistentDataContainer().has(mobFlagKeyMap.get(flag), PersistentDataType.BYTE);
         } else {
@@ -82,7 +83,7 @@ public class MobMetadataService {
      *
      * @return true if the mob has any mcMMO mob related metadata values
      */
-    public boolean hasMobFlags(@NotNull LivingEntity livingEntity) {
+    public static boolean hasMobFlags(@NotNull LivingEntity livingEntity) {
         if (isUsingPersistentData) {
             for (MobMetaFlagType metaFlagType : MobMetaFlagType.values()) {
                 if (hasMobFlag(metaFlagType, livingEntity))
@@ -102,7 +103,7 @@ public class MobMetadataService {
      * @param sourceEntity entity to copy from
      * @param targetEntity entity to copy to
      */
-    public void addMobFlags(@NotNull LivingEntity sourceEntity, @NotNull LivingEntity targetEntity) {
+    public static void addMobFlags(@NotNull LivingEntity sourceEntity, @NotNull LivingEntity targetEntity) {
         if (!hasMobFlags(sourceEntity))
             return;
 
@@ -125,7 +126,7 @@ public class MobMetadataService {
      * @param flag         the desired flag to assign
      * @param livingEntity the target living entity
      */
-    public void flagMetadata(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
+    public static void flagMetadata(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
         if (PersistentDataConfig.getInstance().isMobPersistent(flag)) {
             if (!hasMobFlag(flag, livingEntity)) {
                 PersistentDataContainer persistentDataContainer = livingEntity.getPersistentDataContainer();
@@ -144,7 +145,7 @@ public class MobMetadataService {
      * @param flag         desired flag to remove
      * @param livingEntity the target living entity
      */
-    public void removeMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
+    public static void removeMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
         if (PersistentDataConfig.getInstance().isMobPersistent(flag)) {
             if (hasMobFlag(flag, livingEntity)) {
                 PersistentDataContainer persistentDataContainer = livingEntity.getPersistentDataContainer();
@@ -165,7 +166,7 @@ public class MobMetadataService {
      *
      * @param livingEntity target entity
      */
-    public void removeMobFlags(@NotNull LivingEntity livingEntity) {
+    public static void removeMobFlags(@NotNull LivingEntity livingEntity) {
         if (isUsingPersistentData) {
             for (MobMetaFlagType flag : MobMetaFlagType.values()) {
                 removeMobFlag(flag, livingEntity);

+ 12 - 1
src/main/java/com/gmail/nossr50/util/Permissions.java

@@ -1,6 +1,7 @@
 package com.gmail.nossr50.util;
 
 import com.gmail.nossr50.commands.party.PartySubcommandType;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.ItemType;
 import com.gmail.nossr50.datatypes.skills.MaterialType;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
@@ -17,6 +18,7 @@ import org.bukkit.permissions.Permission;
 import org.bukkit.permissions.PermissionDefault;
 import org.bukkit.plugin.PluginManager;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Locale;
 
@@ -171,10 +173,19 @@ public final class Permissions {
     }
 
     public static boolean vanillaXpBoost(Permissible permissible, PrimarySkillType skill) { return permissible.hasPermission("mcmmo.ability." + skill.toString().toLowerCase(Locale.ENGLISH) + ".vanillaxpboost"); }
-    public static boolean isSubSkillEnabled(Permissible permissible, SubSkillType subSkillType) {
+    public static boolean isSubSkillEnabled(@Nullable Permissible permissible, @NotNull SubSkillType subSkillType) {
+        if (permissible == null)
+            return false;
         return permissible.hasPermission(subSkillType.getPermissionNodeAddress());
     }
 
+    public static boolean isSubSkillEnabled(@Nullable McMMOPlayer permissible, @NotNull SubSkillType subSkillType) {
+        if (permissible == null)
+            return false;
+
+        return isSubSkillEnabled(permissible.getPlayer(), subSkillType);
+    }
+
     /* ACROBATICS */
     public static boolean dodge(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.acrobatics.dodge"); }
     public static boolean gracefulRoll(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.acrobatics.gracefulroll"); }

+ 3 - 1
src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java

@@ -18,6 +18,8 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.*;
 
+import static com.gmail.nossr50.util.MobMetadataUtils.removeMobFlags;
+
 public class TransientEntityTracker {
     //These two are updated in step with each other
     private final @NotNull HashMap<UUID, HashMap<CallOfTheWildType, HashSet<TrackedTamingEntity>>> perPlayerTransientEntityMap;
@@ -273,7 +275,7 @@ public class TransientEntityTracker {
         }
 
         //Remove our metadata
-        mcMMO.getMetadataService().getMobMetadataService().removeMobFlags(livingEntity);
+        removeMobFlags(livingEntity);
 
         //Clean from trackers
         unregisterEntity(livingEntity);

+ 3 - 1
src/main/java/com/gmail/nossr50/util/TransientMetadataTools.java

@@ -4,6 +4,8 @@ import com.gmail.nossr50.mcMMO;
 import org.bukkit.entity.LivingEntity;
 import org.jetbrains.annotations.NotNull;
 
+import static com.gmail.nossr50.util.MobMetadataUtils.removeMobFlags;
+
 public class TransientMetadataTools {
     private final mcMMO pluginRef;
 
@@ -30,7 +32,7 @@ public class TransientMetadataTools {
         }
 
         //Cleanup mob metadata
-        mcMMO.getMetadataService().getMobMetadataService().removeMobFlags(entity);
+        removeMobFlags(entity);
 
         //TODO: This loop has some redundancy, this whole method needs to be rewritten
         for(String key : MetadataConstants.MOB_METADATA_KEYS) {

+ 181 - 43
src/main/java/com/gmail/nossr50/util/random/ProbabilityUtil.java

@@ -11,26 +11,37 @@ import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.entity.Player;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.VisibleForTesting;
 
 import java.text.DecimalFormat;
 
+import static java.util.Objects.requireNonNull;
+
 public class ProbabilityUtil {
     public static final @NotNull DecimalFormat percent = new DecimalFormat("##0.00%");
     public static final double LUCKY_MODIFIER = 1.333D;
 
     /**
-     * Return a chance of success in "percentage" format, show to the player in UI elements
+     * Return a chance of success in "percentage" format, shown to the player in UI elements
      *
      * @param player target player
      * @param subSkillType target subskill
      * @param isLucky whether to apply luck modifiers
      *
      * @return "percentage" representation of success
+     * @deprecated use {@link #chanceOfSuccessPercentage(McMMOPlayer, SubSkillType, boolean)} instead
      */
-    public static double chanceOfSuccessPercentage(@NotNull Player player,
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public static double chanceOfSuccessPercentage(@Nullable Player player,
+                                                   @NotNull SubSkillType subSkillType,
+                                                   boolean isLucky) {
+        return chanceOfSuccessPercentage(requireNonNull(UserManager.getPlayer(player)), subSkillType, isLucky);
+    }
+
+    public static double chanceOfSuccessPercentage(@Nullable McMMOPlayer mmoPlayer,
                                                    @NotNull SubSkillType subSkillType,
                                                    boolean isLucky) {
-        Probability probability = getSubSkillProbability(subSkillType, player);
+        Probability probability = getSubSkillProbability(subSkillType, mmoPlayer);
         //Probability values are on a 0-1 scale and need to be "transformed" into a 1-100 scale
         double percentageValue = probability.getValue(); //Doesn't need to be scaled
 
@@ -42,6 +53,13 @@ public class ProbabilityUtil {
         return percentageValue;
     }
 
+    /**
+     * Return a chance of success as a double representing a "percentage".
+     *
+     * @param probability the probability of success
+     * @param isLucky whether to apply luck modifiers
+     * @return a double as a "percentage" representation of success
+     */
     public static double chanceOfSuccessPercentage(@NotNull Probability probability, boolean isLucky) {
         //Probability values are on a 0-1 scale and need to be "transformed" into a 1-100 scale
         double percentageValue = probability.getValue();
@@ -54,6 +72,7 @@ public class ProbabilityUtil {
         return percentageValue;
     }
 
+    @VisibleForTesting
     static Probability getStaticRandomChance(@NotNull SubSkillType subSkillType) throws InvalidStaticChance {
         return switch (subSkillType) {
             case AXES_ARMOR_IMPACT -> Probability.ofPercent(mcMMO.p.getAdvancedConfig().getImpactChance());
@@ -74,19 +93,20 @@ public class ProbabilityUtil {
         return skillProbabilityType;
     }
 
-    static @NotNull Probability ofSubSkill(@Nullable Player player,
-                                           @NotNull SubSkillType subSkillType) {
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    private static @NotNull Probability ofSubSkill(@Nullable Player player, @NotNull SubSkillType subSkillType) {
+        // no null check needed here
+        return ofSubSkill(UserManager.getPlayer(player), subSkillType);
+    }
+
+    private static @NotNull Probability ofSubSkill(@Nullable McMMOPlayer mmoPlayer, @NotNull SubSkillType subSkillType) {
         switch (getProbabilityType(subSkillType)) {
             case DYNAMIC_CONFIGURABLE:
                 double probabilityCeiling;
                 double skillLevel;
                 double maxBonusLevel; // If a skill level is equal to the cap, it has the full probability
 
-                if (player != null) {
-                    McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
-                    if (mmoPlayer == null) {
-                        return Probability.ofPercent(0);
-                    }
+                if (mmoPlayer != null) {
                     skillLevel = mmoPlayer.getSkillLevel(subSkillType.getParentSkill());
                 } else {
                     skillLevel = 0;
@@ -101,10 +121,10 @@ public class ProbabilityUtil {
                 try {
                     return getStaticRandomChance(subSkillType);
                 } catch (InvalidStaticChance invalidStaticChance) {
-                    invalidStaticChance.printStackTrace();
+                    throw new RuntimeException(invalidStaticChance);
                 }
             default:
-                throw new RuntimeException("No case in switch statement for Skill Probability Type!");
+                throw new IllegalStateException("No case in switch statement for Skill Probability Type!");
         }
     }
 
@@ -123,14 +143,43 @@ public class ProbabilityUtil {
      * The outcome of the probability can also be modified by this event that is called
      *
      * @param subSkillType target subskill
-     * @param player target player, can be null (null players are given odds equivalent to a player with no levels or luck)
+     * @param player target player
+     *              can be null (null players are given odds equivalent to a player with no levels or luck)
      * @return true if the Skill RNG succeeds, false if it fails
+     * @deprecated use {@link #isSkillRNGSuccessful(SubSkillType, McMMOPlayer)} instead
      */
-    public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) {
-        final Probability probability = getSkillProbability(subSkillType, player);
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @Nullable Player player) {
+        return isSkillRNGSuccessful(subSkillType, UserManager.getPlayer(player));
+    }
+
+    /**
+     * This is one of several Skill RNG check methods
+     * This helper method is for specific {@link SubSkillType},
+     * which help mcMMO understand where the RNG values used in our calculations come from this {@link SubSkillType}
+     * <p>
+     * 1) Determine where the RNG values come from for the passed {@link SubSkillType}
+     *  NOTE: In the config file, there are values which are static and which are more dynamic,
+     *  this is currently a bit hardcoded and will need to be updated manually
+     * <p>
+     * 2) Determine whether to use Lucky multiplier and influence the outcome
+     * <p>
+     * 3)
+     * Creates a {@link Probability} and pipes it to {@link ProbabilityUtil} which processes the result and returns it
+     * <p>
+     * This also calls a {@link SubSkillEvent} which can be cancelled, if it is cancelled this will return false
+     * The outcome of the probability can also be modified by this event that is called
+     *
+     * @param subSkillType target subskill
+     * @param mmoPlayer target player
+     *                  can be null (null players are given odds equivalent to a player with no levels or luck)
+     * @return true if the Skill RNG succeeds, false if it fails
+     */
+    public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @Nullable McMMOPlayer mmoPlayer) {
+        final Probability probability = getSkillProbability(subSkillType, mmoPlayer);
 
         //Luck
-        boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
+        boolean isLucky = mmoPlayer != null && Permissions.lucky(mmoPlayer.getPlayer(), subSkillType.getParentSkill());
 
         if(isLucky) {
             return probability.evaluate(LUCKY_MODIFIER);
@@ -143,28 +192,49 @@ public class ProbabilityUtil {
      * Returns the {@link Probability} for a specific {@link SubSkillType} for a specific {@link Player}.
      * This does not take into account perks such as lucky for the player.
      * This is affected by other plugins who can listen to the {@link SubSkillEvent} and cancel it or mutate it.
+     * Null players will be treated as zero skill players.
      *
      * @param subSkillType the target subskill
      * @param player the target player
+     *               can be null (null players have the worst odds)
      * @return the probability for this skill
+     * @deprecated use {@link #getSkillProbability(SubSkillType, McMMOPlayer)} instead
      */
-    public static Probability getSkillProbability(@NotNull SubSkillType subSkillType, @NotNull Player player) {
-        //Process probability
-        Probability probability = getSubSkillProbability(subSkillType, player);
+    @Deprecated(forRemoval = true)
+    public static Probability getSkillProbability(@NotNull SubSkillType subSkillType, @Nullable Player player) {
+        return getSkillProbability(subSkillType, UserManager.getPlayer(player));
+    }
 
-        //Send out event
-        SubSkillEvent subSkillEvent = EventUtils.callSubSkillEvent(player, subSkillType);
+    /**
+     * Returns the {@link Probability} for a specific {@link SubSkillType} for a specific {@link Player}.
+     * This does not take into account perks such as lucky for the player.
+     * This is affected by other plugins who can listen to the {@link SubSkillEvent} and cancel it or mutate it.
+     * Null players will be treated as zero skill players.
+     *
+     * @param subSkillType the target subskill
+     * @param mmoPlayer the target player
+     *               can be null (null players have the worst odds)
+     * @return the probability for this skill
+     */
+    public static Probability getSkillProbability(@NotNull SubSkillType subSkillType, @Nullable McMMOPlayer mmoPlayer) {
+        // Process probability
+        Probability probability = getSubSkillProbability(subSkillType, mmoPlayer);
 
-        if(subSkillEvent.isCancelled()) {
-            return Probability.ALWAYS_FAILS;
-        }
+        // Send out event
+        if (mmoPlayer != null) {
+            SubSkillEvent subSkillEvent = EventUtils.callSubSkillEvent(mmoPlayer, subSkillType);
+
+            if(subSkillEvent.isCancelled()) {
+                return Probability.ALWAYS_FAILS;
+            }
 
-        //Result modifier
-        double resultModifier = subSkillEvent.getResultModifier();
+            // Result modifier
+            double resultModifier = subSkillEvent.getResultModifier();
 
-        //Mutate probability
-        if(resultModifier != 1.0D)
-            probability = Probability.ofPercent(probability.getValue() * resultModifier);
+            // Mutate probability
+            if(resultModifier != 1.0D)
+                probability = Probability.ofPercent(probability.getValue() * resultModifier);
+        }
 
         return probability;
     }
@@ -177,12 +247,28 @@ public class ProbabilityUtil {
      * @param player the target player can be null (null players have the worst odds)
      * @param probabilityPercentage the probability of this player succeeding in "percentage" format (0-100 inclusive)
      * @return true if the RNG succeeds, false if it fails
+     * @deprecated use {@link #isStaticSkillRNGSuccessful(PrimarySkillType, McMMOPlayer, double)} instead
      */
+    @Deprecated(forRemoval = true, since = "2.2.010")
     public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType, @Nullable Player player, double probabilityPercentage) {
+        return isStaticSkillRNGSuccessful(primarySkillType, player, Probability.ofPercent(probabilityPercentage));
+    }
+
+    /**
+     * This is one of several Skill RNG check methods
+     * This helper method is specific to static value RNG, which can be influenced by a player's Luck
+     *
+     * @param primarySkillType the related primary skill
+     * @param mmoPlayer the target player can be null (null players have the worst odds)
+     * @param probabilityPercentage the probability of this player succeeding in "percentage" format (0-100 inclusive)
+     * @return true if the RNG succeeds, false if it fails
+     */
+    public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType,
+                                                     @Nullable McMMOPlayer mmoPlayer, double probabilityPercentage) {
         //Grab a probability converted from a "percentage" value
-        Probability probability = Probability.ofPercent(probabilityPercentage);
+        final Probability probability = Probability.ofPercent(probabilityPercentage);
 
-        return isStaticSkillRNGSuccessful(primarySkillType, player, probability);
+        return isStaticSkillRNGSuccessful(primarySkillType, mmoPlayer, probability);
     }
 
     /**
@@ -190,12 +276,32 @@ public class ProbabilityUtil {
      * This helper method is specific to static value RNG, which can be influenced by a player's Luck
      *
      * @param primarySkillType the related primary skill
-     * @param player the target player, can be null (null players have the worst odds)
+     * @param player the target player
+     *              can be null (null players have the worst odds)
      * @param probability the probability of this player succeeding
      * @return true if the RNG succeeds, false if it fails
+     * @deprecated use {@link #isStaticSkillRNGSuccessful(PrimarySkillType, McMMOPlayer, Probability)} instead, this
+     * method is redundant and will be removed.
+     */
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType,
+                                                     @Nullable Player player, @NotNull Probability probability) {
+        return isStaticSkillRNGSuccessful(primarySkillType, UserManager.getPlayer(player), probability);
+    }
+
+    /**
+     * This is one of several Skill RNG check methods
+     * This helper method is specific to static value RNG, which can be influenced by a mmoPlayer's Luck
+     *
+     * @param primarySkillType the related primary skill
+     * @param mmoPlayer the target mmoPlayer
+     *              can be null (null players have the worst odds)
+     * @param probability the probability of this mmoPlayer succeeding
+     * @return true if the RNG succeeds, false if it fails
      */
-    public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType, @Nullable Player player, @NotNull Probability probability) {
-        boolean isLucky = player != null && Permissions.lucky(player, primarySkillType);
+    public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType,
+                                                     @Nullable McMMOPlayer mmoPlayer, @NotNull Probability probability) {
+        boolean isLucky = mmoPlayer != null && Permissions.lucky(mmoPlayer.getPlayer(), primarySkillType);
 
         if(isLucky) {
             return probability.evaluate(LUCKY_MODIFIER);
@@ -209,25 +315,57 @@ public class ProbabilityUtil {
      * @param subSkillType target subskill
      * @param player target player
      * @return true if the skill succeeds (wasn't cancelled by any other plugin)
+     * @deprecated use {@link #isNonRNGSkillActivationSuccessful(SubSkillType, McMMOPlayer)} instead
      */
+    @Deprecated(forRemoval = true, since = "2.2.010")
     public static boolean isNonRNGSkillActivationSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) {
-        return !EventUtils.callSubSkillEvent(player, subSkillType).isCancelled();
+        return isNonRNGSkillActivationSuccessful(subSkillType, requireNonNull(UserManager.getPlayer(player)));
     }
 
     /**
-     * Grab the {@link Probability} for a specific {@link SubSkillType} for a specific {@link Player}
-     *
+     * Skills activate without RNG, this allows other plugins to prevent that activation
      * @param subSkillType target subskill
-     * @param player target player
-     * @return the Probability of this skill succeeding
+     * @param mmoPlayer target player
+     * @return true if the skill succeeds (wasn't cancelled by any other plugin)
+     */
+    public static boolean isNonRNGSkillActivationSuccessful(@NotNull SubSkillType subSkillType,
+                                                            @NotNull McMMOPlayer mmoPlayer) {
+        return !EventUtils.callSubSkillEvent(mmoPlayer, subSkillType).isCancelled();
+    }
+
+    /**
+     * Retrieves the {@link Probability} of success for a specified {@link SubSkillType} for a given {@link Player}.
+     *
+     * @param subSkillType The targeted subskill.
+     * @param player The player in question.
+     *               If null, the method treats it as a player with no levels or luck and calculates the probability
+     *               accordingly.
+     * @return The probability that the specified skill will succeed.
+     * @deprecated use {@link #getSubSkillProbability(SubSkillType, McMMOPlayer)} instead
      */
-    public static @NotNull Probability getSubSkillProbability(@NotNull SubSkillType subSkillType, @Nullable Player player) {
+    @Deprecated(forRemoval = true, since = "2.2.010")
+    public static @NotNull Probability getSubSkillProbability(@NotNull SubSkillType subSkillType,
+                                                              @Nullable Player player) {
         return ProbabilityUtil.ofSubSkill(player, subSkillType);
     }
 
-    public static @NotNull String[] getRNGDisplayValues(@NotNull Player player, @NotNull SubSkillType subSkill) {
-        double firstValue = chanceOfSuccessPercentage(player, subSkill, false);
-        double secondValue = chanceOfSuccessPercentage(player, subSkill, true);
+    /**
+     * Retrieves the {@link Probability} of success for a specified {@link SubSkillType} for a given {@link Player}.
+     *
+     * @param subSkillType The targeted subskill.
+     * @param mmoPlayer The player in question.
+     *               If null, the method treats it as a player with no levels or luck and calculates the probability
+     *               accordingly.
+     * @return The probability that the specified skill will succeed.
+     */
+    public static @NotNull Probability getSubSkillProbability(@NotNull SubSkillType subSkillType,
+                                                              @Nullable McMMOPlayer mmoPlayer) {
+        return ProbabilityUtil.ofSubSkill(mmoPlayer, subSkillType);
+    }
+
+    public static @NotNull String[] getRNGDisplayValues(@Nullable McMMOPlayer mmoPlayer, @NotNull SubSkillType subSkill) {
+        double firstValue = chanceOfSuccessPercentage(mmoPlayer, subSkill, false);
+        double secondValue = chanceOfSuccessPercentage(mmoPlayer, subSkill, true);
 
         return new String[]{percent.format(firstValue), percent.format(secondValue)};
     }

+ 8 - 11
src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java

@@ -9,7 +9,6 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.metadata.MobMetaFlagType;
-import com.gmail.nossr50.metadata.MobMetadataService;
 import com.gmail.nossr50.runnables.skills.AwardCombatXpTask;
 import com.gmail.nossr50.skills.acrobatics.AcrobaticsManager;
 import com.gmail.nossr50.skills.archery.ArcheryManager;
@@ -37,14 +36,12 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
 
+import static com.gmail.nossr50.util.MobMetadataUtils.hasMobFlag;
+
 public final class CombatUtils {
 
     private CombatUtils() {}
 
-    private static @NotNull MobMetadataService getMobMetadataService() {
-        return mcMMO.getMetadataService().getMobMetadataService();
-    }
-
     public static boolean isDamageLikelyFromNormalCombat(@NotNull DamageCause damageCause) {
         return switch (damageCause) {
             case ENTITY_ATTACK, ENTITY_SWEEP_ATTACK, PROJECTILE -> true;
@@ -926,17 +923,17 @@ public final class CombatUtils {
                 }
             }
 
-            if(getMobMetadataService().hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, target)) {
+            if(hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, target)) {
                 baseXP = 0;
-            } else if(getMobMetadataService().hasMobFlag(MobMetaFlagType.MOB_SPAWNER_MOB, target) || target.hasMetadata("ES")) {
+            } else if(hasMobFlag(MobMetaFlagType.MOB_SPAWNER_MOB, target) || target.hasMetadata("ES")) {
                 baseXP *= ExperienceConfig.getInstance().getSpawnedMobXpMultiplier();
-            } else if(getMobMetadataService().hasMobFlag(MobMetaFlagType.NETHER_PORTAL_MOB, target)) {
+            } else if(hasMobFlag(MobMetaFlagType.NETHER_PORTAL_MOB, target)) {
                 baseXP *= ExperienceConfig.getInstance().getNetherPortalXpMultiplier();
-            } else if(getMobMetadataService().hasMobFlag(MobMetaFlagType.EGG_MOB, target)) {
+            } else if(hasMobFlag(MobMetaFlagType.EGG_MOB, target)) {
                 baseXP *= ExperienceConfig.getInstance().getEggXpMultiplier();
-            } else if (getMobMetadataService().hasMobFlag(MobMetaFlagType.PLAYER_BRED_MOB, target)) {
+            } else if (hasMobFlag(MobMetaFlagType.PLAYER_BRED_MOB, target)) {
                 baseXP *= ExperienceConfig.getInstance().getBredMobXpMultiplier();
-            } else if(getMobMetadataService().hasMobFlag(MobMetaFlagType.PLAYER_TAMED_MOB, target)) {
+            } else if(hasMobFlag(MobMetaFlagType.PLAYER_TAMED_MOB, target)) {
                 baseXP *= ExperienceConfig.getInstance().getTamedMobXpMultiplier();
             }
 

+ 16 - 0
src/main/java/com/gmail/nossr50/util/skills/PerksUtils.java

@@ -1,6 +1,7 @@
 package com.gmail.nossr50.util.skills;
 
 import com.gmail.nossr50.config.experience.ExperienceConfig;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.events.skills.SkillActivationPerkEvent;
 import com.gmail.nossr50.util.Permissions;
@@ -106,4 +107,19 @@ public final class PerksUtils {
 
         return NORMAL_SKILL_ACTIVATION_CHANCE;
     }
+
+    /**
+     * Calculate activation chance for a skill.
+     *
+     * @param mmoPlayer Player to check the activation chance for
+     * @param skill PrimarySkillType to check the activation chance of
+     * @return the activation chance with "lucky perk" accounted for
+     */
+    public static int handleLuckyPerks(McMMOPlayer mmoPlayer, PrimarySkillType skill) {
+        if (Permissions.lucky(mmoPlayer.getPlayer(), skill)) {
+            return LUCKY_SKILL_ACTIVATION_CHANCE;
+        }
+
+        return NORMAL_SKILL_ACTIVATION_CHANCE;
+    }
 }

+ 6 - 7
src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java

@@ -10,7 +10,7 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.metadata.ItemMetadataService;
+import com.gmail.nossr50.util.ItemMetadataUtils;
 import com.gmail.nossr50.util.ItemUtils;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Permissions;
@@ -32,6 +32,7 @@ import org.jetbrains.annotations.Nullable;
 
 import java.util.Iterator;
 
+import static com.gmail.nossr50.util.ItemMetadataUtils.*;
 import static com.gmail.nossr50.util.PotionEffectMapper.getHaste;
 
 public final class SkillUtils {
@@ -152,7 +153,7 @@ public final class SkillUtils {
             ItemUtils.addDigSpeedToItem(heldItem, heldItem.getEnchantmentLevel(mcMMO.p.getEnchantmentMapper().getEfficiency()));
 
             //1.13.2+ will have persistent metadata for this item
-            mcMMO.getMetadataService().getItemMetadataService().setSuperAbilityBoostedItem(heldItem, originalDigSpeed);
+            ItemMetadataUtils.setSuperAbilityBoostedItem(heldItem, originalDigSpeed);
         } else {
             int duration = 0;
             int amplifier = 0;
@@ -209,9 +210,7 @@ public final class SkillUtils {
 
 
         //1.13.2+ will have persistent metadata for this itemStack
-        ItemMetadataService itemMetadataService = mcMMO.getMetadataService().getItemMetadataService();
-
-        if(itemMetadataService.isLegacyAbilityTool(itemStack)) {
+        if(isLegacyAbilityTool(itemStack)) {
             ItemMeta itemMeta = itemStack.getItemMeta();
 
             if(itemMeta != null) {
@@ -223,8 +222,8 @@ public final class SkillUtils {
             }
         }
 
-        if(itemMetadataService.isSuperAbilityBoosted(itemStack)) {
-            itemMetadataService.removeBonusDigSpeedOnSuperAbilityTool(itemStack);
+        if(isSuperAbilityBoosted(itemStack)) {
+            removeBonusDigSpeedOnSuperAbilityTool(itemStack);
         }
     }
 

+ 0 - 102
src/main/java/com/gmail/nossr50/util/skills/SmeltingTracker.java

@@ -1,102 +0,0 @@
-package com.gmail.nossr50.util.skills;
-
-import com.gmail.nossr50.datatypes.player.McMMOPlayer;
-import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.util.player.UserManager;
-import org.bukkit.Bukkit;
-import org.bukkit.ChatColor;
-import org.bukkit.OfflinePlayer;
-import org.bukkit.block.Furnace;
-import org.bukkit.entity.Player;
-import org.bukkit.inventory.FurnaceInventory;
-import org.bukkit.inventory.Inventory;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.UUID;
-
-public class SmeltingTracker {
-
-//    private final HashMap<Furnace, OfflinePlayer> furnaceOwners;
-
-    private void changeFurnaceOwnership(Furnace furnace, Player player) {
-
-        McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
-
-        /*
-            Debug output
-         */
-        printOwnershipGainDebug(furnace, mcMMOPlayer);
-
-        printOwnershipLossDebug(furnace);
-
-        setFurnaceOwner(furnace, player);
-    }
-
-    private void setFurnaceOwner(Furnace furnace, Player player) {
-        mcMMO.getMetadataService().getBlockMetadataService().setFurnaceOwner(furnace, player.getUniqueId());
-    }
-
-    private void printOwnershipGainDebug(Furnace furnace, McMMOPlayer mcMMOPlayer) {
-        if(mcMMOPlayer != null) {
-            if(mcMMOPlayer.isDebugMode()) {
-                mcMMOPlayer.getPlayer().sendMessage("Furnace ownership " +
-                        ChatColor.GREEN +"gained " + ChatColor.RESET +
-                        "at location: " + furnace.getLocation().toString());
-            }
-
-        }
-    }
-
-    private void printOwnershipLossDebug(Furnace furnace) {
-        OfflinePlayer furnaceOwner = getFurnaceOwner(furnace);
-
-        if(furnaceOwner != null && furnaceOwner.isOnline()) {
-            McMMOPlayer furnaceOwnerProfile = UserManager.getPlayer(furnaceOwner.getPlayer());
-
-            if(furnaceOwnerProfile != null) {
-                if(furnaceOwnerProfile.isDebugMode()) {
-                    furnaceOwnerProfile.getPlayer().sendMessage("Furnace ownership " +
-                            ChatColor.RED + "lost " + ChatColor.RESET +
-                            "at location: " + furnace.getLocation().toString());
-                }
-            }
-        }
-    }
-
-    public @Nullable OfflinePlayer getFurnaceOwner(Furnace furnace) {
-        UUID uuid = mcMMO.getMetadataService().getBlockMetadataService().getFurnaceOwner(furnace);
-
-        if(uuid != null) {
-            return Bukkit.getOfflinePlayer(uuid);
-        } else {
-            return null;
-        }
-    }
-
-    @Nullable
-    public Furnace getFurnaceFromInventory(Inventory inventory) {
-        if (!(inventory instanceof FurnaceInventory)) {
-            return null;
-        }
-
-        return (Furnace) inventory.getHolder();
-    }
-
-    public boolean isFurnaceOwned(Furnace furnace) {
-        return getFurnaceOwner(furnace) != null;
-    }
-
-    public void processFurnaceOwnership(Furnace furnace, Player player) {
-        if(!mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.SMELTING))
-            return;
-
-        //Don't swap ownership if its the same player
-        if(getFurnaceOwner(furnace) != null) {
-            if(getFurnaceOwner(furnace).getUniqueId().equals(player.getUniqueId()))
-                return;
-        }
-
-        changeFurnaceOwnership(furnace, player);
-    }
-}

+ 3 - 1
src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java

@@ -10,6 +10,7 @@ import com.gmail.nossr50.listeners.InteractionManager;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.skills.RankUtils;
 import net.kyori.adventure.audience.Audience;
 import net.kyori.adventure.text.Component;
@@ -345,7 +346,8 @@ public class TextComponentFactory {
             componentBuilder.append(Component.newline());
 
             //Finally, add details to the tooltip
-            abstractSubSkill.addStats(componentBuilder, player);
+            // TODO: pass in McMMOPlayer instead
+            abstractSubSkill.addStats(componentBuilder, UserManager.getPlayer(player));
         }
 
         return componentBuilder.build();

+ 0 - 1
src/test/java/com/gmail/nossr50/MMOTestEnvironment.java

@@ -21,7 +21,6 @@ import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemFactory;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.PlayerInventory;
-import org.bukkit.inventory.meta.ItemMeta;
 import org.bukkit.plugin.PluginManager;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;

+ 2 - 1
src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java

@@ -62,7 +62,8 @@ class ProbabilityUtilTest extends MMOTestEnvironment {
         when(advancedConfig.getMaximumProbability(UNARMED_ARROW_DEFLECT)).thenReturn(20D);
         when(advancedConfig.getMaxBonusLevel(UNARMED_ARROW_DEFLECT)).thenReturn(0);
 
-        final Probability probability = ProbabilityUtil.getSkillProbability(UNARMED_ARROW_DEFLECT, player);
+        @SuppressWarnings("all")
+        final Probability probability = ProbabilityUtil.getSkillProbability(UNARMED_ARROW_DEFLECT, mmoPlayer);
         assertEquals(0.2D, probability.getValue());
         assertProbabilityExpectations(20, probability);
     }