Browse Source

Expanding McMMOItemSpawnEvent & Fixing a bug with Tree Feller drops

nossr50 4 years ago
parent
commit
65fba3e20e

+ 11 - 0
Changelog.txt

@@ -1,3 +1,14 @@
+Version 2.1.152
+    Updated hu_HU locale (thanks andris)
+    Fixed a bug where Tree Feller would sometimes double drop blocks inappropriately
+    Added some code to prevent a possible NPE when spawning items in a world that got unloaded
+    (API) New ENUM ItemSpawnReason which gives context for why mcMMO is dropping an item
+    (API) McMMOItemSpawnEvent::getItemSpawnReason() was added
+    (API) Many instances of spawning items that didn't used to create and call an McMMOItemSpawnEvent now do
+
+    NOTES:
+    I really should stop letting my OCD compel me to rewrite code all the time.
+
 Version 2.1.151
 Version 2.1.151
     Added new config for chat options named 'chat.yml'
     Added new config for chat options named 'chat.yml'
     Added 'Chat.Channels.Party.Spies.Automatically_Enable_Spying' to chat.yml which when enabled will start users who have the chat spy permission in chat spying mode
     Added 'Chat.Channels.Party.Spies.Automatically_Enable_Spying' to chat.yml which when enabled will start users who have the chat spy permission in chat spying mode

+ 17 - 0
src/main/java/com/gmail/nossr50/api/ItemSpawnReason.java

@@ -0,0 +1,17 @@
+package com.gmail.nossr50.api;
+
+public enum ItemSpawnReason {
+    ARROW_RETRIEVAL_ACTIVATED, //Players sometimes can retrieve arrows instead of losing them when hitting a mob
+    EXCAVATION_TREASURE, //Any drops when excavation treasures activate fall under this
+    FISHING_EXTRA_FISH, //A config setting allows more fish to be found when fishing, the extra fish are part of this
+    FISHING_SHAKE_TREASURE, //When using a fishing rod on a mob and finding a treasure via Shake
+    HYLIAN_LUCK_TREASURE, //When finding a treasure in grass via hylian luck
+    BLAST_MINING_DEBRIS_NON_ORES, //The non-ore debris that are dropped from blast mining
+    BLAST_MINING_ORES, //The ore(s) which may include player placed ores being dropped from blast mining
+    BLAST_MINING_ORES_BONUS_DROP, //Any bonus ores that drop from a result of a players Mining skills
+    UNARMED_DISARMED_ITEM, //When you disarm an opponent and they drop their weapon
+    SALVAGE_ENCHANTMENT_BOOK, //When you salvage an enchanted item and get the enchantment back in book form
+    SALVAGE_MATERIALS, //When you salvage an item and get materials back
+    TREE_FELLER_DISPLACED_BLOCK,
+    BONUS_DROPS, //Can be from Mining, Woodcutting, Herbalism, etc
+}

+ 1 - 1
src/main/java/com/gmail/nossr50/datatypes/skills/SuperAbilityType.java

@@ -226,7 +226,7 @@ public enum SuperAbilityType {
                 return BlockUtils.affectedBySuperBreaker(blockState);
                 return BlockUtils.affectedBySuperBreaker(blockState);
 
 
             case TREE_FELLER:
             case TREE_FELLER:
-                return BlockUtils.isLog(blockState);
+                return BlockUtils.hasWoodcuttingXP(blockState);
 
 
             default:
             default:
                 return false;
                 return false;

+ 19 - 7
src/main/java/com/gmail/nossr50/events/items/McMMOItemSpawnEvent.java

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.events.items;
 package com.gmail.nossr50.events.items;
 
 
+import com.gmail.nossr50.api.ItemSpawnReason;
 import org.bukkit.Location;
 import org.bukkit.Location;
 import org.bukkit.event.Cancellable;
 import org.bukkit.event.Cancellable;
 import org.bukkit.event.Event;
 import org.bukkit.event.Event;
@@ -14,38 +15,49 @@ public class McMMOItemSpawnEvent extends Event implements Cancellable {
     private Location location;
     private Location location;
     private ItemStack itemStack;
     private ItemStack itemStack;
     private boolean cancelled;
     private boolean cancelled;
+    private final ItemSpawnReason itemSpawnReason;
 
 
-    public McMMOItemSpawnEvent(Location location, ItemStack itemStack) {
+    public McMMOItemSpawnEvent(@NotNull Location location, @NotNull ItemStack itemStack, @NotNull ItemSpawnReason itemSpawnReason) {
         this.location = location;
         this.location = location;
         this.itemStack = itemStack;
         this.itemStack = itemStack;
+        this.itemSpawnReason = itemSpawnReason;
         this.cancelled = false;
         this.cancelled = false;
     }
     }
 
 
+    /**
+     * The reason an item is being spawned by mcMMO
+     * @see ItemSpawnReason
+     * @return the item drop reason
+     */
+    public ItemSpawnReason getItemSpawnReason() {
+        return itemSpawnReason;
+    }
+
     /**
     /**
      * @return Location where the item will be dropped
      * @return Location where the item will be dropped
      */
      */
-    public Location getLocation() {
+    public @NotNull Location getLocation() {
         return location;
         return location;
     }
     }
 
 
     /**
     /**
      * @param location Location where to drop the item
      * @param location Location where to drop the item
      */
      */
-    public void setLocation(Location location) {
+    public void setLocation(@NotNull Location location) {
         this.location = location;
         this.location = location;
     }
     }
 
 
     /**
     /**
      * @return ItemStack that will be dropped
      * @return ItemStack that will be dropped
      */
      */
-    public ItemStack getItemStack() {
+    public @NotNull ItemStack getItemStack() {
         return itemStack;
         return itemStack;
     }
     }
 
 
     /**
     /**
      * @param itemStack ItemStack to drop
      * @param itemStack ItemStack to drop
      */
      */
-    public void setItemStack(ItemStack itemStack) {
+    public void setItemStack(@NotNull ItemStack itemStack) {
         this.itemStack = itemStack;
         this.itemStack = itemStack;
     }
     }
 
 
@@ -61,14 +73,14 @@ public class McMMOItemSpawnEvent extends Event implements Cancellable {
     }
     }
 
 
     /** Rest of file is required boilerplate for custom events **/
     /** Rest of file is required boilerplate for custom events **/
-    private static final HandlerList handlers = new HandlerList();
+    private static final @NotNull HandlerList handlers = new HandlerList();
 
 
     @Override
     @Override
     public @NotNull HandlerList getHandlers() {
     public @NotNull HandlerList getHandlers() {
         return handlers;
         return handlers;
     }
     }
 
 
-    public static HandlerList getHandlerList() {
+    public static @NotNull HandlerList getHandlerList() {
         return handlers;
         return handlers;
     }
     }
 }
 }

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

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.listeners;
 package com.gmail.nossr50.listeners;
 
 
+import com.gmail.nossr50.api.ItemSpawnReason;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.HiddenConfig;
 import com.gmail.nossr50.config.HiddenConfig;
 import com.gmail.nossr50.config.WorldBlacklist;
 import com.gmail.nossr50.config.WorldBlacklist;
@@ -19,10 +20,7 @@ import com.gmail.nossr50.skills.mining.MiningManager;
 import com.gmail.nossr50.skills.repair.Repair;
 import com.gmail.nossr50.skills.repair.Repair;
 import com.gmail.nossr50.skills.salvage.Salvage;
 import com.gmail.nossr50.skills.salvage.Salvage;
 import com.gmail.nossr50.skills.woodcutting.WoodcuttingManager;
 import com.gmail.nossr50.skills.woodcutting.WoodcuttingManager;
-import com.gmail.nossr50.util.BlockUtils;
-import com.gmail.nossr50.util.EventUtils;
-import com.gmail.nossr50.util.ItemUtils;
-import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.*;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import com.gmail.nossr50.util.sounds.SoundManager;
 import com.gmail.nossr50.util.sounds.SoundManager;
@@ -98,7 +96,7 @@ public class BlockListener implements Listener {
                     int bonusCount = bonusDropMeta.asInt();
                     int bonusCount = bonusDropMeta.asInt();
 
 
                     for (int i = 0; i < bonusCount; i++) {
                     for (int i = 0; i < bonusCount; i++) {
-                        event.getBlock().getWorld().dropItemNaturally(event.getBlockState().getLocation(), is);
+                        Misc.spawnItemNaturally(event.getBlockState().getLocation(), is, ItemSpawnReason.BONUS_DROPS);
                     }
                     }
                 }
                 }
             }
             }
@@ -356,13 +354,17 @@ public class BlockListener implements Listener {
         }
         }
 
 
         /* WOOD CUTTING */
         /* WOOD CUTTING */
-        else if (BlockUtils.isLog(blockState) && ItemUtils.isAxe(heldItem) && PrimarySkillType.WOODCUTTING.getPermissions(player) && !mcMMO.getPlaceStore().isTrue(blockState)) {
+        else if (BlockUtils.hasWoodcuttingXP(blockState) && ItemUtils.isAxe(heldItem) && PrimarySkillType.WOODCUTTING.getPermissions(player) && !mcMMO.getPlaceStore().isTrue(blockState)) {
             WoodcuttingManager woodcuttingManager = mcMMOPlayer.getWoodcuttingManager();
             WoodcuttingManager woodcuttingManager = mcMMOPlayer.getWoodcuttingManager();
             if (woodcuttingManager.canUseTreeFeller(heldItem)) {
             if (woodcuttingManager.canUseTreeFeller(heldItem)) {
                 woodcuttingManager.processTreeFeller(blockState);
                 woodcuttingManager.processTreeFeller(blockState);
             }
             }
             else {
             else {
-                woodcuttingManager.woodcuttingBlockCheck(blockState);
+                //Check for XP
+                woodcuttingManager.processWoodcuttingBlockXP(blockState);
+
+                //Check for bonus drops
+                woodcuttingManager.processHarvestLumber(blockState);
             }
             }
         }
         }
 
 
@@ -491,7 +493,7 @@ public class BlockListener implements Listener {
             if (mcMMOPlayer.getToolPreparationMode(ToolType.HOE) && ItemUtils.isHoe(heldItem) && (BlockUtils.affectedByGreenTerra(blockState) || BlockUtils.canMakeMossy(blockState)) && Permissions.greenTerra(player)) {
             if (mcMMOPlayer.getToolPreparationMode(ToolType.HOE) && ItemUtils.isHoe(heldItem) && (BlockUtils.affectedByGreenTerra(blockState) || BlockUtils.canMakeMossy(blockState)) && Permissions.greenTerra(player)) {
                 mcMMOPlayer.checkAbilityActivation(PrimarySkillType.HERBALISM);
                 mcMMOPlayer.checkAbilityActivation(PrimarySkillType.HERBALISM);
             }
             }
-            else if (mcMMOPlayer.getToolPreparationMode(ToolType.AXE) && ItemUtils.isAxe(heldItem) && BlockUtils.isLog(blockState) && Permissions.treeFeller(player)) {
+            else if (mcMMOPlayer.getToolPreparationMode(ToolType.AXE) && ItemUtils.isAxe(heldItem) && BlockUtils.hasWoodcuttingXP(blockState) && Permissions.treeFeller(player)) {
                 mcMMOPlayer.checkAbilityActivation(PrimarySkillType.WOODCUTTING);
                 mcMMOPlayer.checkAbilityActivation(PrimarySkillType.WOODCUTTING);
             }
             }
             else if (mcMMOPlayer.getToolPreparationMode(ToolType.PICKAXE) && ItemUtils.isPickaxe(heldItem) && BlockUtils.affectedBySuperBreaker(blockState) && Permissions.superBreaker(player)) {
             else if (mcMMOPlayer.getToolPreparationMode(ToolType.PICKAXE) && ItemUtils.isPickaxe(heldItem) && BlockUtils.affectedBySuperBreaker(blockState) && Permissions.superBreaker(player)) {
@@ -525,7 +527,7 @@ public class BlockListener implements Listener {
          *
          *
          * We don't need to check permissions here because they've already been checked for the ability to even activate.
          * We don't need to check permissions here because they've already been checked for the ability to even activate.
          */
          */
-        if (mcMMOPlayer.getAbilityMode(SuperAbilityType.TREE_FELLER) && BlockUtils.isLog(blockState) && Config.getInstance().getTreeFellerSoundsEnabled()) {
+        if (mcMMOPlayer.getAbilityMode(SuperAbilityType.TREE_FELLER) && BlockUtils.hasWoodcuttingXP(blockState) && Config.getInstance().getTreeFellerSoundsEnabled()) {
             SoundManager.sendSound(player, blockState.getLocation(), SoundType.FIZZ);
             SoundManager.sendSound(player, blockState.getLocation(), SoundType.FIZZ);
         }
         }
     }
     }
@@ -596,7 +598,7 @@ public class BlockListener implements Listener {
                 }
                 }
             }
             }
         }
         }
-        else if (mcMMOPlayer.getWoodcuttingManager().canUseLeafBlower(heldItem) && BlockUtils.isLeaves(blockState) && EventUtils.simulateBlockBreak(block, player, true)) {
+        else if (mcMMOPlayer.getWoodcuttingManager().canUseLeafBlower(heldItem) && BlockUtils.isNonWoodPartOfTree(blockState) && EventUtils.simulateBlockBreak(block, player, true)) {
             event.setInstaBreak(true);
             event.setInstaBreak(true);
             SoundManager.sendSound(player, block.getLocation(), SoundType.POP);
             SoundManager.sendSound(player, block.getLocation(), SoundType.POP);
         }
         }

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

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.skills.archery;
 package com.gmail.nossr50.skills.archery;
 
 
+import com.gmail.nossr50.api.ItemSpawnReason;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
@@ -9,6 +10,7 @@ import org.bukkit.Material;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.entity.Player;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Iterator;
@@ -50,12 +52,12 @@ public class Archery {
      *
      *
      * @param livingEntity The entity hit by the arrows
      * @param livingEntity The entity hit by the arrows
      */
      */
-    public static void arrowRetrievalCheck(LivingEntity livingEntity) {
+    public static void arrowRetrievalCheck(@NotNull LivingEntity livingEntity) {
         for (Iterator<TrackedEntity> entityIterator = trackedEntities.iterator(); entityIterator.hasNext();) {
         for (Iterator<TrackedEntity> entityIterator = trackedEntities.iterator(); entityIterator.hasNext();) {
             TrackedEntity trackedEntity = entityIterator.next();
             TrackedEntity trackedEntity = entityIterator.next();
 
 
             if (trackedEntity.getID() == livingEntity.getUniqueId()) {
             if (trackedEntity.getID() == livingEntity.getUniqueId()) {
-                Misc.dropItems(livingEntity.getLocation(), new ItemStack(Material.ARROW), trackedEntity.getArrowCount());
+                Misc.spawnItems(livingEntity.getLocation(), new ItemStack(Material.ARROW), trackedEntity.getArrowCount(), ItemSpawnReason.ARROW_RETRIEVAL_ACTIVATED);
                 entityIterator.remove();
                 entityIterator.remove();
                 return;
                 return;
             }
             }

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

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.skills.excavation;
 package com.gmail.nossr50.skills.excavation;
 
 
+import com.gmail.nossr50.api.ItemSpawnReason;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.datatypes.experience.XPGainReason;
 import com.gmail.nossr50.datatypes.experience.XPGainReason;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
@@ -51,7 +52,7 @@ public class ExcavationManager extends SkillManager {
                         }
                         }
 
 
                         xp += treasure.getXp();
                         xp += treasure.getXp();
-                        Misc.dropItem(location, treasure.getDrop());
+                        Misc.spawnItem(location, treasure.getDrop(), ItemSpawnReason.EXCAVATION_TREASURE);
                     }
                     }
                 }
                 }
             }
             }

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

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.skills.fishing;
 package com.gmail.nossr50.skills.fishing;
 
 
+import com.gmail.nossr50.api.ItemSpawnReason;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
@@ -318,7 +319,7 @@ public class FishingManager extends SkillManager {
                 }
                 }
 
 
                 if (Config.getInstance().getFishingExtraFish()) {
                 if (Config.getInstance().getFishingExtraFish()) {
-                    Misc.dropItem(player.getEyeLocation(), fishingCatch.getItemStack());
+                    Misc.spawnItem(player.getEyeLocation(), fishingCatch.getItemStack(), ItemSpawnReason.FISHING_EXTRA_FISH);
                 }
                 }
 
 
                 fishingCatch.setItemStack(treasureDrop);
                 fishingCatch.setItemStack(treasureDrop);
@@ -426,7 +427,7 @@ public class FishingManager extends SkillManager {
                 return;
                 return;
             }
             }
 
 
-            Misc.dropItem(target.getLocation(), drop);
+            Misc.spawnItem(target.getLocation(), drop, ItemSpawnReason.FISHING_SHAKE_TREASURE);
             CombatUtils.dealDamage(target, Math.min(Math.max(target.getMaxHealth() / 4, 1), 10), EntityDamageEvent.DamageCause.CUSTOM, getPlayer()); // Make it so you can shake a mob no more than 4 times.
             CombatUtils.dealDamage(target, Math.min(Math.max(target.getMaxHealth() / 4, 1), 10), EntityDamageEvent.DamageCause.CUSTOM, getPlayer()); // Make it so you can shake a mob no more than 4 times.
             applyXpGain(ExperienceConfig.getInstance().getFishingShakeXP(), XPGainReason.PVE);
             applyXpGain(ExperienceConfig.getInstance().getFishingShakeXP(), XPGainReason.PVE);
         }
         }

+ 2 - 1
src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.skills.herbalism;
 package com.gmail.nossr50.skills.herbalism;
 
 
+import com.gmail.nossr50.api.ItemSpawnReason;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.config.treasure.TreasureConfig;
 import com.gmail.nossr50.config.treasure.TreasureConfig;
@@ -626,7 +627,7 @@ public class HerbalismManager extends SkillManager {
                     return false;
                     return false;
                 }
                 }
                 blockState.setType(Material.AIR);
                 blockState.setType(Material.AIR);
-                Misc.dropItem(location, treasure.getDrop());
+                Misc.spawnItem(location, treasure.getDrop(), ItemSpawnReason.HYLIAN_LUCK_TREASURE);
                 NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Herbalism.HylianLuck");
                 NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Herbalism.HylianLuck");
                 return true;
                 return true;
             }
             }

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

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.skills.mining;
 package com.gmail.nossr50.skills.mining;
 
 
+import com.gmail.nossr50.api.ItemSpawnReason;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
@@ -188,7 +189,7 @@ public class MiningManager extends SkillManager {
         //Drop "debris" based on skill modifiers
         //Drop "debris" based on skill modifiers
         for(BlockState blockState : notOres) {
         for(BlockState blockState : notOres) {
             if(RandomUtils.nextFloat() < debrisYield) {
             if(RandomUtils.nextFloat() < debrisYield) {
-                Misc.dropItem(Misc.getBlockCenter(blockState), new ItemStack(blockState.getType())); // Initial block that would have been dropped
+                Misc.spawnItem(Misc.getBlockCenter(blockState), new ItemStack(blockState.getType()), ItemSpawnReason.BLAST_MINING_DEBRIS_NON_ORES); // Initial block that would have been dropped
             }
             }
         }
         }
 
 
@@ -196,12 +197,12 @@ public class MiningManager extends SkillManager {
             if (RandomUtils.nextFloat() < (yield + oreBonus)) {
             if (RandomUtils.nextFloat() < (yield + oreBonus)) {
                 xp += Mining.getBlockXp(blockState);
                 xp += Mining.getBlockXp(blockState);
 
 
-                Misc.dropItem(Misc.getBlockCenter(blockState), new ItemStack(blockState.getType())); // Initial block that would have been dropped
+                Misc.spawnItem(Misc.getBlockCenter(blockState), new ItemStack(blockState.getType()), ItemSpawnReason.BLAST_MINING_ORES); // Initial block that would have been dropped
 
 
                 if (!mcMMO.getPlaceStore().isTrue(blockState)) {
                 if (!mcMMO.getPlaceStore().isTrue(blockState)) {
                     for (int i = 1; i < dropMultiplier; i++) {
                     for (int i = 1; i < dropMultiplier; i++) {
 //                        Bukkit.broadcastMessage("Bonus Drop on Ore: "+blockState.getType().toString());
 //                        Bukkit.broadcastMessage("Bonus Drop on Ore: "+blockState.getType().toString());
-                        Misc.dropItem(Misc.getBlockCenter(blockState), new ItemStack(blockState.getType())); // Initial block that would have been dropped
+                        Misc.spawnItem(Misc.getBlockCenter(blockState), new ItemStack(blockState.getType()), ItemSpawnReason.BLAST_MINING_ORES_BONUS_DROP); // Initial block that would have been dropped
                     }
                     }
                 }
                 }
             }
             }

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

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.skills.salvage;
 package com.gmail.nossr50.skills.salvage;
 
 
+import com.gmail.nossr50.api.ItemSpawnReason;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
@@ -159,10 +160,10 @@ public class SalvageManager extends SkillManager {
         anvilLoc.add(0, .1, 0);
         anvilLoc.add(0, .1, 0);
 
 
         if (enchantBook != null) {
         if (enchantBook != null) {
-            Misc.spawnItemTowardsLocation(anvilLoc.clone(), playerLoc.clone(), enchantBook, vectorSpeed);
+            Misc.spawnItemTowardsLocation(anvilLoc.clone(), playerLoc.clone(), enchantBook, vectorSpeed, ItemSpawnReason.SALVAGE_ENCHANTMENT_BOOK);
         }
         }
 
 
-        Misc.spawnItemTowardsLocation(anvilLoc.clone(), playerLoc.clone(), salvageResults, vectorSpeed);
+        Misc.spawnItemTowardsLocation(anvilLoc.clone(), playerLoc.clone(), salvageResults, vectorSpeed, ItemSpawnReason.SALVAGE_MATERIALS);
 
 
         // BWONG BWONG BWONG - CLUNK!
         // BWONG BWONG BWONG - CLUNK!
         if (Config.getInstance().getSalvageAnvilUseSoundsEnabled()) {
         if (Config.getInstance().getSalvageAnvilUseSoundsEnabled()) {

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

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.skills.unarmed;
 package com.gmail.nossr50.skills.unarmed;
 
 
+import com.gmail.nossr50.api.ItemSpawnReason;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.datatypes.interactions.NotificationType;
 import com.gmail.nossr50.datatypes.interactions.NotificationType;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
@@ -109,7 +110,7 @@ public class UnarmedManager extends SkillManager {
             if(UserManager.getPlayer(defender) == null)
             if(UserManager.getPlayer(defender) == null)
                 return;
                 return;
 
 
-            Item item = Misc.dropItem(defender.getLocation(), defender.getInventory().getItemInMainHand());
+            Item item = Misc.spawnItem(defender.getLocation(), defender.getInventory().getItemInMainHand(), ItemSpawnReason.UNARMED_DISARMED_ITEM);
 
 
             if (item != null && AdvancedConfig.getInstance().getDisarmProtected()) {
             if (item != null && AdvancedConfig.getInstance().getDisarmProtected()) {
                 item.setMetadata(mcMMO.disarmedItemKey, UserManager.getPlayer(defender).getPlayerMetadata());
                 item.setMetadata(mcMMO.disarmedItemKey, UserManager.getPlayer(defender).getPlayerMetadata());

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

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.skills.woodcutting;
 package com.gmail.nossr50.skills.woodcutting;
 
 
+import com.gmail.nossr50.api.ItemSpawnReason;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.datatypes.experience.XPGainReason;
 import com.gmail.nossr50.datatypes.experience.XPGainReason;
@@ -27,6 +28,7 @@ import org.bukkit.event.player.PlayerItemDamageEvent;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.meta.Damageable;
 import org.bukkit.inventory.meta.Damageable;
 import org.bukkit.inventory.meta.ItemMeta;
 import org.bukkit.inventory.meta.ItemMeta;
+import org.jetbrains.annotations.NotNull;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.HashSet;
@@ -65,10 +67,11 @@ public class WoodcuttingManager extends SkillManager {
                 && ItemUtils.isAxe(heldItem);
                 && ItemUtils.isAxe(heldItem);
     }
     }
 
 
-    private boolean canGetDoubleDrops() {
+    private boolean checkHarvestLumberActivation(@NotNull Material material) {
         return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
         return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
                 && RankUtils.hasReachedRank(1, getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
                 && RankUtils.hasReachedRank(1, getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
-                && RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.WOODCUTTING_HARVEST_LUMBER, getPlayer());
+                && RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.WOODCUTTING_HARVEST_LUMBER, getPlayer())
+                && Config.getInstance().getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, material);
     }
     }
 
 
     /**
     /**
@@ -76,20 +79,14 @@ public class WoodcuttingManager extends SkillManager {
      *
      *
      * @param blockState Block being broken
      * @param blockState Block being broken
      */
      */
-    public void woodcuttingBlockCheck(BlockState blockState) {
-        int xp = getExperienceFromLog(blockState);
-
-        switch (blockState.getType()) {
-            case BROWN_MUSHROOM_BLOCK:
-            case RED_MUSHROOM_BLOCK:
-                break;
-
-            default:
-                if (canGetDoubleDrops()) {
-                    checkForDoubleDrop(blockState);
-                }
+    public void processHarvestLumber(@NotNull BlockState blockState) {
+        if (checkHarvestLumberActivation(blockState.getType())) {
+            spawnHarvestLumberBonusDrops(blockState);
         }
         }
+    }
 
 
+    public void processWoodcuttingBlockXP(@NotNull BlockState blockState) {
+        int xp = getExperienceFromLog(blockState);
         applyXpGain(xp, XPGainReason.PVE);
         applyXpGain(xp, XPGainReason.PVE);
     }
     }
 
 
@@ -206,7 +203,7 @@ public class WoodcuttingManager extends SkillManager {
      * @param player the player holding the item
      * @param player the player holding the item
      * @return True if the tool can sustain the durability loss
      * @return True if the tool can sustain the durability loss
      */
      */
-    private static boolean handleDurabilityLoss(Set<BlockState> treeFellerBlocks, ItemStack inHand, Player player) {
+    private static boolean handleDurabilityLoss(@NotNull Set<BlockState> treeFellerBlocks, @NotNull ItemStack inHand, @NotNull Player player) {
         //Treat the NBT tag for unbreakable and the durability enchant differently
         //Treat the NBT tag for unbreakable and the durability enchant differently
         ItemMeta meta = inHand.getItemMeta();
         ItemMeta meta = inHand.getItemMeta();
 
 
@@ -218,7 +215,7 @@ public class WoodcuttingManager extends SkillManager {
         Material type = inHand.getType();
         Material type = inHand.getType();
 
 
         for (BlockState blockState : treeFellerBlocks) {
         for (BlockState blockState : treeFellerBlocks) {
-            if (BlockUtils.isLog(blockState)) {
+            if (BlockUtils.hasWoodcuttingXP(blockState)) {
                 durabilityLoss += Config.getInstance().getAbilityToolDamage();
                 durabilityLoss += Config.getInstance().getAbilityToolDamage();
             }
             }
         }
         }
@@ -249,7 +246,7 @@ public class WoodcuttingManager extends SkillManager {
      * @return true if and only if the given blockState was a Log not already
      * @return true if and only if the given blockState was a Log not already
      *     in treeFellerBlocks.
      *     in treeFellerBlocks.
      */
      */
-    private boolean processTreeFellerTargetBlock(BlockState blockState, List<BlockState> futureCenterBlocks, Set<BlockState> treeFellerBlocks) {
+    private boolean processTreeFellerTargetBlock(@NotNull BlockState blockState, @NotNull List<BlockState> futureCenterBlocks, @NotNull Set<BlockState> treeFellerBlocks) {
         if (treeFellerBlocks.contains(blockState) || mcMMO.getPlaceStore().isTrue(blockState)) {
         if (treeFellerBlocks.contains(blockState) || mcMMO.getPlaceStore().isTrue(blockState)) {
             return false;
             return false;
         }
         }
@@ -259,12 +256,12 @@ public class WoodcuttingManager extends SkillManager {
             treeFellerReachedThreshold = true;
             treeFellerReachedThreshold = true;
         }
         }
 
 
-        if (BlockUtils.isLog(blockState)) {
+        if (BlockUtils.hasWoodcuttingXP(blockState)) {
             treeFellerBlocks.add(blockState);
             treeFellerBlocks.add(blockState);
             futureCenterBlocks.add(blockState);
             futureCenterBlocks.add(blockState);
             return true;
             return true;
         }
         }
-        else if (BlockUtils.isLeaves(blockState)) {
+        else if (BlockUtils.isNonWoodPartOfTree(blockState)) {
             treeFellerBlocks.add(blockState);
             treeFellerBlocks.add(blockState);
             return false;
             return false;
         }
         }
@@ -276,7 +273,7 @@ public class WoodcuttingManager extends SkillManager {
      *
      *
      * @param treeFellerBlocks List of blocks to be dropped
      * @param treeFellerBlocks List of blocks to be dropped
      */
      */
-    private void dropTreeFellerLootFromBlocks(Set<BlockState> treeFellerBlocks) {
+    private void dropTreeFellerLootFromBlocks(@NotNull Set<BlockState> treeFellerBlocks) {
         Player player = getPlayer();
         Player player = getPlayer();
         int xp = 0;
         int xp = 0;
         int processedLogCount = 0;
         int processedLogCount = 0;
@@ -289,25 +286,22 @@ public class WoodcuttingManager extends SkillManager {
                 break; // TODO: Shouldn't we use continue instead?
                 break; // TODO: Shouldn't we use continue instead?
             }
             }
 
 
-            Material material = blockState.getType();
+            /*
+             * Handle Drops & XP
+             */
 
 
-            //TODO: Update this to drop the correct items/blocks via NMS
-            if (material == Material.BROWN_MUSHROOM_BLOCK || material == Material.RED_MUSHROOM_BLOCK) {
+            if (BlockUtils.hasWoodcuttingXP(blockState)) {
+                //Add XP
                 xp += processTreeFellerXPGains(blockState, processedLogCount);
                 xp += processTreeFellerXPGains(blockState, processedLogCount);
-                Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops());
-            } else if (mcMMO.getModManager().isCustomLeaf(blockState)) {
-                Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops());
-            } else {
-                if (BlockUtils.isLog(blockState)) {
-                    if (canGetDoubleDrops()) {
-                        checkForDoubleDrop(blockState);
-                    }
-                    xp += processTreeFellerXPGains(blockState, processedLogCount);
-                    Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops());
-                }
-                if (BlockUtils.isLeaves(blockState)) {
-                    Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops());
-                }
+
+                //Drop displaced block
+                Misc.spawnItemsFromCollection(Misc.getBlockCenter(blockState), block.getDrops(), ItemSpawnReason.TREE_FELLER_DISPLACED_BLOCK);
+
+                //Bonus Drops / Harvest lumber checks
+                processHarvestLumber(blockState);
+            } else if (BlockUtils.isNonWoodPartOfTree(blockState)) {
+                //Drop displaced non-woodcutting XP blocks
+                Misc.spawnItemsFromCollection(Misc.getBlockCenter(blockState), block.getDrops(), ItemSpawnReason.TREE_FELLER_DISPLACED_BLOCK, 1);
             }
             }
 
 
             blockState.setType(Material.AIR);
             blockState.setType(Material.AIR);
@@ -367,13 +361,11 @@ public class WoodcuttingManager extends SkillManager {
     }
     }
 
 
     /**
     /**
-     * Checks for double drops
+     * Spawns harvest lumber bonus drops
      *
      *
      * @param blockState Block being broken
      * @param blockState Block being broken
      */
      */
-    protected static void checkForDoubleDrop(BlockState blockState) {
-        if (Config.getInstance().getWoodcuttingDoubleDropsEnabled(blockState.getBlockData())) {
-            Misc.dropItems(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops());
-        }
+    protected static void spawnHarvestLumberBonusDrops(@NotNull BlockState blockState) {
+        Misc.spawnItemsFromCollection(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops(), ItemSpawnReason.BONUS_DROPS);
     }
     }
 }
 }

+ 5 - 7
src/main/java/com/gmail/nossr50/util/BlockUtils.java

@@ -66,7 +66,7 @@ public final class BlockUtils {
      * @return true if the block awards XP, false otherwise
      * @return true if the block awards XP, false otherwise
      */
      */
     public static boolean shouldBeWatched(BlockState blockState) {
     public static boolean shouldBeWatched(BlockState blockState) {
-        return affectedByGigaDrillBreaker(blockState) || affectedByGreenTerra(blockState) || affectedBySuperBreaker(blockState) || isLog(blockState)
+        return affectedByGigaDrillBreaker(blockState) || affectedByGreenTerra(blockState) || affectedBySuperBreaker(blockState) || hasWoodcuttingXP(blockState)
                 || Config.getInstance().getDoubleDropsEnabled(PrimarySkillType.MINING, blockState.getType())
                 || Config.getInstance().getDoubleDropsEnabled(PrimarySkillType.MINING, blockState.getType())
                 || Config.getInstance().getDoubleDropsEnabled(PrimarySkillType.EXCAVATION, blockState.getType())
                 || Config.getInstance().getDoubleDropsEnabled(PrimarySkillType.EXCAVATION, blockState.getType())
                 || Config.getInstance().getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, blockState.getType())
                 || Config.getInstance().getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, blockState.getType())
@@ -165,10 +165,8 @@ public final class BlockUtils {
      * @param blockState The {@link BlockState} of the block to check
      * @param blockState The {@link BlockState} of the block to check
      * @return true if the block is a log, false otherwise
      * @return true if the block is a log, false otherwise
      */
      */
-    public static boolean isLog(BlockState blockState) {
-        if (ExperienceConfig.getInstance().doesBlockGiveSkillXP(PrimarySkillType.WOODCUTTING, blockState.getBlockData()))
-            return true;
-        return mcMMO.getModManager().isCustomLog(blockState);
+    public static boolean hasWoodcuttingXP(BlockState blockState) {
+        return ExperienceConfig.getInstance().doesBlockGiveSkillXP(PrimarySkillType.WOODCUTTING, blockState.getBlockData());
     }
     }
 
 
     /**
     /**
@@ -177,8 +175,8 @@ public final class BlockUtils {
      * @param blockState The {@link BlockState} of the block to check
      * @param blockState The {@link BlockState} of the block to check
      * @return true if the block is a leaf, false otherwise
      * @return true if the block is a leaf, false otherwise
      */
      */
-    public static boolean isLeaves(BlockState blockState) {
-        return mcMMO.getMaterialMapStore().isLeavesWhiteListed(blockState.getType());
+    public static boolean isNonWoodPartOfTree(BlockState blockState) {
+        return mcMMO.getMaterialMapStore().isTreeFellerDestructible(blockState.getType());
     }
     }
 
 
     /**
     /**

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

@@ -46,6 +46,7 @@ import org.bukkit.event.player.PlayerFishEvent;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.plugin.Plugin;
 import org.bukkit.plugin.Plugin;
 import org.bukkit.plugin.PluginManager;
 import org.bukkit.plugin.PluginManager;
+import org.jetbrains.annotations.NotNull;
 
 
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
@@ -73,7 +74,7 @@ public final class EventUtils {
      * @param event The {@link Event} in question
      * @param event The {@link Event} in question
      * @return Whether this {@link Event} has been faked by mcMMO and should not be processed normally.
      * @return Whether this {@link Event} has been faked by mcMMO and should not be processed normally.
      */
      */
-    public static boolean isFakeEvent(Event event) {
+    public static boolean isFakeEvent(@NotNull Event event) {
         return event instanceof FakeEvent;
         return event instanceof FakeEvent;
     }
     }
 
 
@@ -84,7 +85,7 @@ public final class EventUtils {
      * @param event this event
      * @param event this event
      * @return true if damage is NOT from an unnatural mcMMO skill (such as bleed DOTs)
      * @return true if damage is NOT from an unnatural mcMMO skill (such as bleed DOTs)
      */
      */
-    public static boolean isDamageFromMcMMOComplexBehaviour(Event event) {
+    public static boolean isDamageFromMcMMOComplexBehaviour(@NotNull Event event) {
         return event instanceof FakeEntityDamageEvent;
         return event instanceof FakeEntityDamageEvent;
     }
     }
 
 
@@ -94,7 +95,7 @@ public final class EventUtils {
      * @param entity target entity
      * @param entity target entity
      * @return the associated McMMOPlayer for this entity
      * @return the associated McMMOPlayer for this entity
      */
      */
-    public static McMMOPlayer getMcMMOPlayer(Entity entity)
+    public static McMMOPlayer getMcMMOPlayer(@NotNull Entity entity)
     {
     {
         return UserManager.getPlayer((Player)entity);
         return UserManager.getPlayer((Player)entity);
     }
     }
@@ -112,7 +113,7 @@ public final class EventUtils {
      * @param entityDamageEvent
      * @param entityDamageEvent
      * @return
      * @return
      */
      */
-    public static boolean isRealPlayerDamaged(EntityDamageEvent entityDamageEvent)
+    public static boolean isRealPlayerDamaged(@NotNull EntityDamageEvent entityDamageEvent)
     {
     {
         //Make sure the damage is above 0
         //Make sure the damage is above 0
         double damage = entityDamageEvent.getFinalDamage();
         double damage = entityDamageEvent.getFinalDamage();
@@ -167,14 +168,14 @@ public final class EventUtils {
      * Others
      * Others
      */
      */
 
 
-    public static McMMOPlayerAbilityActivateEvent callPlayerAbilityActivateEvent(Player player, PrimarySkillType skill) {
+    public static @NotNull McMMOPlayerAbilityActivateEvent callPlayerAbilityActivateEvent(@NotNull Player player, @NotNull PrimarySkillType skill) {
         McMMOPlayerAbilityActivateEvent event = new McMMOPlayerAbilityActivateEvent(player, skill);
         McMMOPlayerAbilityActivateEvent event = new McMMOPlayerAbilityActivateEvent(player, skill);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
 
         return event;
         return event;
     }
     }
 
 
-    public static McMMOPlayerProfileLoadEvent callPlayerProfileLoadEvent(Player player, PlayerProfile profile){
+    public static @NotNull McMMOPlayerProfileLoadEvent callPlayerProfileLoadEvent(@NotNull Player player, @NotNull PlayerProfile profile){
         McMMOPlayerProfileLoadEvent event = new McMMOPlayerProfileLoadEvent(player, profile);
         McMMOPlayerProfileLoadEvent event = new McMMOPlayerProfileLoadEvent(player, profile);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
 

+ 136 - 163
src/main/java/com/gmail/nossr50/util/MaterialMapStore.java

@@ -1,6 +1,7 @@
 package com.gmail.nossr50.util;
 package com.gmail.nossr50.util;
 
 
 import org.bukkit.Material;
 import org.bukkit.Material;
+import org.jetbrains.annotations.NotNull;
 
 
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.HashSet;
@@ -15,46 +16,46 @@ import java.util.Locale;
  */
  */
 public class MaterialMapStore {
 public class MaterialMapStore {
 
 
-    private final HashSet<String> abilityBlackList;
-    private final HashSet<String> toolBlackList;
-    private final HashSet<String> mossyWhiteList;
-    private final HashSet<String> leavesWhiteList;
-    private final HashSet<String> herbalismAbilityBlackList;
-    private final HashSet<String> blockCrackerWhiteList;
-    private final HashSet<String> canMakeShroomyWhiteList;
-    private final HashSet<String> multiBlockPlant;
-    private final HashSet<String> foodItemWhiteList;
-    private final HashSet<String> glassBlocks;
-
-    private final HashSet<String> netheriteArmor;
-    private final HashSet<String> netheriteTools;
-    private final HashSet<String> woodTools;
-    private final HashSet<String> stoneTools;
-    private final HashSet<String> leatherArmor;
-    private final HashSet<String> ironArmor;
-    private final HashSet<String> ironTools;
-    private final HashSet<String> stringTools;
-    private final HashSet<String> goldArmor;
-    private final HashSet<String> goldTools;
-    private final HashSet<String> chainmailArmor;
-    private final HashSet<String> diamondArmor;
-    private final HashSet<String> diamondTools;
-    private final HashSet<String> armors;
-
-    private final HashSet<String> swords;
-    private final HashSet<String> axes;
-    private final HashSet<String> hoes;
-    private final HashSet<String> shovels;
-    private final HashSet<String> pickAxes;
-    private final HashSet<String> tridents;
-    private final HashSet<String> bows;
-    private final HashSet<String> tools;
-
-    private final HashSet<String> enchantables;
-
-    private final HashSet<String> ores;
-
-    private final HashMap<String, Integer> tierValue;
+    private final @NotNull HashSet<String> abilityBlackList;
+    private final @NotNull HashSet<String> toolBlackList;
+    private final @NotNull HashSet<String> mossyWhiteList;
+    private final @NotNull HashSet<String> treeFellerDestructibleWhiteList;
+    private final @NotNull HashSet<String> herbalismAbilityBlackList;
+    private final @NotNull HashSet<String> blockCrackerWhiteList;
+    private final @NotNull HashSet<String> canMakeShroomyWhiteList;
+    private final @NotNull HashSet<String> multiBlockPlant;
+    private final @NotNull HashSet<String> foodItemWhiteList;
+    private final @NotNull HashSet<String> glassBlocks;
+
+    private final @NotNull HashSet<String> netheriteArmor;
+    private final @NotNull HashSet<String> netheriteTools;
+    private final @NotNull HashSet<String> woodTools;
+    private final @NotNull HashSet<String> stoneTools;
+    private final @NotNull HashSet<String> leatherArmor;
+    private final @NotNull HashSet<String> ironArmor;
+    private final @NotNull HashSet<String> ironTools;
+    private final @NotNull HashSet<String> stringTools;
+    private final @NotNull HashSet<String> goldArmor;
+    private final @NotNull HashSet<String> goldTools;
+    private final @NotNull HashSet<String> chainmailArmor;
+    private final @NotNull HashSet<String> diamondArmor;
+    private final @NotNull HashSet<String> diamondTools;
+    private final @NotNull HashSet<String> armors;
+
+    private final @NotNull HashSet<String> swords;
+    private final @NotNull HashSet<String> axes;
+    private final @NotNull HashSet<String> hoes;
+    private final @NotNull HashSet<String> shovels;
+    private final @NotNull HashSet<String> pickAxes;
+    private final @NotNull HashSet<String> tridents;
+    private final @NotNull HashSet<String> bows;
+    private final @NotNull HashSet<String> tools;
+
+    private final @NotNull HashSet<String> enchantables;
+
+    private final @NotNull HashSet<String> ores;
+
+    private final @NotNull HashMap<String, Integer> tierValue;
 
 
 
 
     public MaterialMapStore()
     public MaterialMapStore()
@@ -62,7 +63,7 @@ public class MaterialMapStore {
         abilityBlackList = new HashSet<>();
         abilityBlackList = new HashSet<>();
         toolBlackList = new HashSet<>();
         toolBlackList = new HashSet<>();
         mossyWhiteList = new HashSet<>();
         mossyWhiteList = new HashSet<>();
-        leavesWhiteList = new HashSet<>();
+        treeFellerDestructibleWhiteList = new HashSet<>();
         herbalismAbilityBlackList = new HashSet<>();
         herbalismAbilityBlackList = new HashSet<>();
         blockCrackerWhiteList = new HashSet<>();
         blockCrackerWhiteList = new HashSet<>();
         canMakeShroomyWhiteList = new HashSet<>();
         canMakeShroomyWhiteList = new HashSet<>();
@@ -104,66 +105,66 @@ public class MaterialMapStore {
         fillVanillaMaterialRegisters();
         fillVanillaMaterialRegisters();
     }
     }
 
 
-    public boolean isMultiBlockPlant(Material material)
+    private void fillVanillaMaterialRegisters()
+    {
+        fillAbilityBlackList();
+        fillToolBlackList();
+        fillMossyWhiteList();
+        fillTreeFellerDestructibleWhiteList();
+        fillHerbalismAbilityBlackList();
+        fillBlockCrackerWhiteList();
+        fillShroomyWhiteList();
+        fillMultiBlockPlantSet();
+        fillFoodWhiteList();
+        fillGlassBlockWhiteList();
+        fillArmors();
+        fillTools();
+        fillEnchantables();
+        fillOres();
+
+        fillTierMap();
+    }
+
+    public boolean isMultiBlockPlant(@NotNull Material material)
     {
     {
         return multiBlockPlant.contains(material.getKey().getKey());
         return multiBlockPlant.contains(material.getKey().getKey());
     }
     }
 
 
-    public boolean isAbilityActivationBlackListed(Material material)
+    public boolean isAbilityActivationBlackListed(@NotNull Material material)
     {
     {
         return abilityBlackList.contains(material.getKey().getKey());
         return abilityBlackList.contains(material.getKey().getKey());
     }
     }
 
 
-    public boolean isToolActivationBlackListed(Material material)
+    public boolean isToolActivationBlackListed(@NotNull Material material)
     {
     {
         return toolBlackList.contains(material.getKey().getKey());
         return toolBlackList.contains(material.getKey().getKey());
     }
     }
 
 
-    public boolean isMossyWhiteListed(Material material)
+    public boolean isMossyWhiteListed(@NotNull Material material)
     {
     {
         return mossyWhiteList.contains(material.getKey().getKey());
         return mossyWhiteList.contains(material.getKey().getKey());
     }
     }
 
 
-    public boolean isLeavesWhiteListed(Material material)
+    public boolean isTreeFellerDestructible(@NotNull Material material)
     {
     {
-        return leavesWhiteList.contains(material.getKey().getKey());
+        return treeFellerDestructibleWhiteList.contains(material.getKey().getKey());
     }
     }
 
 
-    public boolean isHerbalismAbilityWhiteListed(Material material)
+    public boolean isHerbalismAbilityWhiteListed(@NotNull Material material)
     {
     {
         return herbalismAbilityBlackList.contains(material.getKey().getKey());
         return herbalismAbilityBlackList.contains(material.getKey().getKey());
     }
     }
 
 
-    public boolean isBlockCrackerWhiteListed(Material material)
+    public boolean isBlockCrackerWhiteListed(@NotNull Material material)
     {
     {
         return blockCrackerWhiteList.contains(material.getKey().getKey());
         return blockCrackerWhiteList.contains(material.getKey().getKey());
     }
     }
 
 
-    public boolean isShroomyWhiteListed(Material material)
+    public boolean isShroomyWhiteListed(@NotNull Material material)
     {
     {
         return canMakeShroomyWhiteList.contains(material.getKey().getKey());
         return canMakeShroomyWhiteList.contains(material.getKey().getKey());
     }
     }
 
 
-    private void fillVanillaMaterialRegisters()
-    {
-        fillAbilityBlackList();
-        fillToolBlackList();
-        fillMossyWhiteList();
-        fillLeavesWhiteList();
-        fillHerbalismAbilityBlackList();
-        fillBlockCrackerWhiteList();
-        fillShroomyWhiteList();
-        fillMultiBlockPlantSet();
-        fillFoodWhiteList();
-        fillGlassBlockWhiteList();
-        fillArmors();
-        fillTools();
-        fillEnchantables();
-        fillOres();
-
-        fillTierMap();
-    }
-
     private void fillTierMap() {
     private void fillTierMap() {
         for(String id : leatherArmor) {
         for(String id : leatherArmor) {
             tierValue.put(id, 1);
             tierValue.put(id, 1);
@@ -418,26 +419,7 @@ public class MaterialMapStore {
         ironTools.add("iron_shovel");
         ironTools.add("iron_shovel");
 
 
         //Used for repair, remove in 2.2
         //Used for repair, remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
-        //TODO: Remove in 2.2
+        //TODO: Remove in config update
         ironTools.add("bucket");
         ironTools.add("bucket");
         ironTools.add("flint_and_steel");
         ironTools.add("flint_and_steel");
         ironTools.add("shears");
         ironTools.add("shears");
@@ -555,7 +537,7 @@ public class MaterialMapStore {
      * @param material target material
      * @param material target material
      * @return true if it is used for armor
      * @return true if it is used for armor
      */
      */
-    public boolean isArmor(Material material) {
+    public boolean isArmor(@NotNull Material material) {
         return isArmor(material.getKey().getKey());
         return isArmor(material.getKey().getKey());
     }
     }
 
 
@@ -564,207 +546,196 @@ public class MaterialMapStore {
      * @param id target item id
      * @param id target item id
      * @return true if the item id matches armor
      * @return true if the item id matches armor
      */
      */
-    public boolean isArmor(String id) {
+    public boolean isArmor(@NotNull String id) {
         return armors.contains(id);
         return armors.contains(id);
     }
     }
 
 
-    public boolean isTool(Material material) {
+    public boolean isTool(@NotNull Material material) {
         return isTool(material.getKey().getKey());
         return isTool(material.getKey().getKey());
     }
     }
 
 
-    public boolean isTool(String id) {
+    public boolean isTool(@NotNull String id) {
         return tools.contains(id);
         return tools.contains(id);
     }
     }
 
 
-    public boolean isEnchantable(Material material) {
+    public boolean isEnchantable(@NotNull Material material) {
         return isEnchantable(material.getKey().getKey());
         return isEnchantable(material.getKey().getKey());
     }
     }
 
 
-    public boolean isEnchantable(String id) {
+    public boolean isEnchantable(@NotNull String id) {
         return enchantables.contains(id);
         return enchantables.contains(id);
     }
     }
 
 
-    public boolean isOre(Material material) {
+    public boolean isOre(@NotNull Material material) {
         return isOre(material.getKey().getKey());
         return isOre(material.getKey().getKey());
     }
     }
 
 
-    public boolean isOre(String id) {
+    public boolean isOre(@NotNull String id) {
         return ores.contains(id);
         return ores.contains(id);
     }
     }
 
 
-    public boolean isBow(Material material) {
+    public boolean isBow(@NotNull Material material) {
         return isBow(material.getKey().getKey());
         return isBow(material.getKey().getKey());
     }
     }
 
 
-    public boolean isBow(String id) {
+    public boolean isBow(@NotNull String id) {
         return bows.contains(id);
         return bows.contains(id);
     }
     }
 
 
-    public boolean isLeatherArmor(Material material) {
+    public boolean isLeatherArmor(@NotNull Material material) {
         return isLeatherArmor(material.getKey().getKey());
         return isLeatherArmor(material.getKey().getKey());
     }
     }
 
 
-    public boolean isLeatherArmor(String id) {
+    public boolean isLeatherArmor(@NotNull String id) {
         return leatherArmor.contains(id);
         return leatherArmor.contains(id);
     }
     }
 
 
-    public boolean isIronArmor(Material material) {
+    public boolean isIronArmor(@NotNull Material material) {
         return isIronArmor(material.getKey().getKey());
         return isIronArmor(material.getKey().getKey());
     }
     }
 
 
-    public boolean isIronArmor(String id) {
+    public boolean isIronArmor(@NotNull String id) {
         return ironArmor.contains(id);
         return ironArmor.contains(id);
     }
     }
 
 
-    public boolean isGoldArmor(Material material) {
+    public boolean isGoldArmor(@NotNull Material material) {
         return isGoldArmor(material.getKey().getKey());
         return isGoldArmor(material.getKey().getKey());
     }
     }
 
 
-    public boolean isGoldArmor(String id) {
+    public boolean isGoldArmor(@NotNull String id) {
         return goldArmor.contains(id);
         return goldArmor.contains(id);
     }
     }
 
 
-    public boolean isDiamondArmor(Material material) {
+    public boolean isDiamondArmor(@NotNull Material material) {
         return isDiamondArmor(material.getKey().getKey());
         return isDiamondArmor(material.getKey().getKey());
     }
     }
 
 
-    public boolean isDiamondArmor(String id) {
+    public boolean isDiamondArmor(@NotNull String id) {
         return diamondArmor.contains(id);
         return diamondArmor.contains(id);
     }
     }
 
 
-    public boolean isChainmailArmor(Material material) {
+    public boolean isChainmailArmor(@NotNull Material material) {
         return isChainmailArmor(material.getKey().getKey());
         return isChainmailArmor(material.getKey().getKey());
     }
     }
 
 
-    public boolean isChainmailArmor(String id) {
+    public boolean isChainmailArmor(@NotNull String id) {
         return chainmailArmor.contains(id);
         return chainmailArmor.contains(id);
     }
     }
 
 
-    public boolean isNetheriteArmor(Material material) {
+    public boolean isNetheriteArmor(@NotNull Material material) {
         return isNetheriteArmor(material.getKey().getKey());
         return isNetheriteArmor(material.getKey().getKey());
     }
     }
 
 
-    public boolean isNetheriteArmor(String id) {
+    public boolean isNetheriteArmor(@NotNull String id) {
         return netheriteArmor.contains(id);
         return netheriteArmor.contains(id);
     }
     }
 
 
-    public boolean isWoodTool(Material material) {
+    public boolean isWoodTool(@NotNull Material material) {
         return isWoodTool(material.getKey().getKey());
         return isWoodTool(material.getKey().getKey());
     }
     }
 
 
-    public boolean isWoodTool(String id) {
+    public boolean isWoodTool(@NotNull String id) {
         return woodTools.contains(id);
         return woodTools.contains(id);
     }
     }
 
 
-    public boolean isStoneTool(Material material) {
+    public boolean isStoneTool(@NotNull Material material) {
         return isStoneTool(material.getKey().getKey());
         return isStoneTool(material.getKey().getKey());
     }
     }
 
 
-    public boolean isStoneTool(String id) {
+    public boolean isStoneTool(@NotNull String id) {
         return stoneTools.contains(id);
         return stoneTools.contains(id);
     }
     }
 
 
-    public boolean isIronTool(Material material) {
+    public boolean isIronTool(@NotNull Material material) {
         return isIronTool(material.getKey().getKey());
         return isIronTool(material.getKey().getKey());
     }
     }
 
 
-    public boolean isIronTool(String id) {
+    public boolean isIronTool(@NotNull String id) {
         return ironTools.contains(id);
         return ironTools.contains(id);
     }
     }
 
 
-    public boolean isGoldTool(Material material) {
+    public boolean isGoldTool(@NotNull Material material) {
         return isGoldTool(material.getKey().getKey());
         return isGoldTool(material.getKey().getKey());
     }
     }
 
 
-    public boolean isGoldTool(String id) {
+    public boolean isGoldTool(@NotNull String id) {
         return goldTools.contains(id);
         return goldTools.contains(id);
     }
     }
 
 
-    public boolean isDiamondTool(Material material) {
+    public boolean isDiamondTool(@NotNull Material material) {
         return isDiamondTool(material.getKey().getKey());
         return isDiamondTool(material.getKey().getKey());
     }
     }
 
 
-    public boolean isDiamondTool(String id) {
+    public boolean isDiamondTool(@NotNull String id) {
         return diamondTools.contains(id);
         return diamondTools.contains(id);
     }
     }
 
 
-    public boolean isSword(Material material) {
+    public boolean isSword(@NotNull Material material) {
         return isSword(material.getKey().getKey());
         return isSword(material.getKey().getKey());
     }
     }
 
 
-    public boolean isSword(String id) {
+    public boolean isSword(@NotNull String id) {
         return swords.contains(id);
         return swords.contains(id);
     }
     }
 
 
-    public boolean isAxe(Material material) {
+    public boolean isAxe(@NotNull Material material) {
         return isAxe(material.getKey().getKey());
         return isAxe(material.getKey().getKey());
     }
     }
 
 
-    public boolean isAxe(String id) {
+    public boolean isAxe(@NotNull String id) {
         return axes.contains(id);
         return axes.contains(id);
     }
     }
 
 
-    public boolean isPickAxe(Material material) {
+    public boolean isPickAxe(@NotNull Material material) {
         return isPickAxe(material.getKey().getKey());
         return isPickAxe(material.getKey().getKey());
     }
     }
 
 
-    public boolean isPickAxe(String id) {
+    public boolean isPickAxe(@NotNull String id) {
         return pickAxes.contains(id);
         return pickAxes.contains(id);
     }
     }
 
 
-    public boolean isShovel(Material material) {
+    public boolean isShovel(@NotNull Material material) {
         return isShovel(material.getKey().getKey());
         return isShovel(material.getKey().getKey());
     }
     }
 
 
-    public boolean isShovel(String id) {
+    public boolean isShovel(@NotNull String id) {
         return shovels.contains(id);
         return shovels.contains(id);
     }
     }
 
 
-    public boolean isHoe(Material material) {
+    public boolean isHoe(@NotNull Material material) {
         return isHoe(material.getKey().getKey());
         return isHoe(material.getKey().getKey());
     }
     }
 
 
-    public boolean isHoe(String id) {
+    public boolean isHoe(@NotNull String id) {
         return hoes.contains(id);
         return hoes.contains(id);
     }
     }
 
 
-    public boolean isNetheriteTool(Material material) {
+    public boolean isNetheriteTool(@NotNull Material material) {
         return isNetheriteTool(material.getKey().getKey());
         return isNetheriteTool(material.getKey().getKey());
     }
     }
 
 
-    public boolean isNetheriteTool(String id) {
+    public boolean isNetheriteTool(@NotNull String id) {
         return netheriteTools.contains(id);
         return netheriteTools.contains(id);
     }
     }
 
 
-    public boolean isStringTool(Material material) {
+    public boolean isStringTool(@NotNull Material material) {
         return isStringTool(material.getKey().getKey());
         return isStringTool(material.getKey().getKey());
     }
     }
 
 
-    public boolean isStringTool(String id) {
+    public boolean isStringTool(@NotNull String id) {
         return stringTools.contains(id);
         return stringTools.contains(id);
     }
     }
 
 
-    public boolean isGlass(Material material) {
+    public boolean isGlass(@NotNull Material material) {
         return glassBlocks.contains(material.getKey().getKey());
         return glassBlocks.contains(material.getKey().getKey());
     }
     }
 
 
-    public boolean isFood(Material material) {
+    public boolean isFood(@NotNull Material material) {
         return foodItemWhiteList.contains(material.getKey().getKey());
         return foodItemWhiteList.contains(material.getKey().getKey());
     }
     }
 
 
     private void fillMultiBlockPlantSet()
     private void fillMultiBlockPlantSet()
     {
     {
-        //Single Block Plants
-//        plantBlockSet.add("melon");
-//        plantBlockSet.add("pumpkin");
-//        plantBlockSet.add("potatoes");
-//        plantBlockSet.add("carrots");
-//        plantBlockSet.add("beetroots");
-//        plantBlockSet.add("nether_wart");
-//        plantBlockSet.add("grass");
-//        plantBlockSet.add("fern");
-//        plantBlockSet.add("large_fern");
-
         //Multi-Block Plants
         //Multi-Block Plants
         multiBlockPlant.add("cactus");
         multiBlockPlant.add("cactus");
         multiBlockPlant.add("chorus_plant");
         multiBlockPlant.add("chorus_plant");
@@ -802,16 +773,18 @@ public class MaterialMapStore {
         herbalismAbilityBlackList.add("farmland");
         herbalismAbilityBlackList.add("farmland");
     }
     }
 
 
-    private void fillLeavesWhiteList()
+    private void fillTreeFellerDestructibleWhiteList()
     {
     {
-        leavesWhiteList.add("oak_leaves");
-        leavesWhiteList.add("acacia_leaves");
-        leavesWhiteList.add("birch_leaves");
-        leavesWhiteList.add("dark_oak_leaves");
-        leavesWhiteList.add("jungle_leaves");
-        leavesWhiteList.add("spruce_leaves");
-        leavesWhiteList.add("nether_wart_block");
-        leavesWhiteList.add("warped_wart_block");
+        treeFellerDestructibleWhiteList.add("oak_leaves");
+        treeFellerDestructibleWhiteList.add("acacia_leaves");
+        treeFellerDestructibleWhiteList.add("birch_leaves");
+        treeFellerDestructibleWhiteList.add("dark_oak_leaves");
+        treeFellerDestructibleWhiteList.add("jungle_leaves");
+        treeFellerDestructibleWhiteList.add("spruce_leaves");
+        treeFellerDestructibleWhiteList.add("nether_wart_block");
+        treeFellerDestructibleWhiteList.add("warped_wart_block");
+        treeFellerDestructibleWhiteList.add("brown_mushroom_block");
+        treeFellerDestructibleWhiteList.add("red_mushroom_block");
     }
     }
 
 
     private void fillMossyWhiteList()
     private void fillMossyWhiteList()
@@ -1090,24 +1063,24 @@ public class MaterialMapStore {
         toolBlackList.add("respawn_anchor");
         toolBlackList.add("respawn_anchor");
     }
     }
 
 
-    public HashSet<String> getNetheriteArmor() {
+    public @NotNull HashSet<String> getNetheriteArmor() {
         return netheriteArmor;
         return netheriteArmor;
     }
     }
 
 
-    public HashSet<String> getNetheriteTools() {
+    public @NotNull HashSet<String> getNetheriteTools() {
         return netheriteTools;
         return netheriteTools;
     }
     }
 
 
 
 
-    public int getTier(Material material) {
+    public int getTier(@NotNull Material material) {
         return getTier(material.getKey().getKey());
         return getTier(material.getKey().getKey());
     }
     }
 
 
-    public int getTier(String id) {
+    public int getTier(@NotNull String id) {
         return tierValue.getOrDefault(id, 1); //1 for unknown items
         return tierValue.getOrDefault(id, 1); //1 for unknown items
     }
     }
 
 
-    private void addToHashSet(String string, HashSet<String> stringHashSet)
+    private void addToHashSet(@NotNull String string, @NotNull HashSet<String> stringHashSet)
     {
     {
         stringHashSet.add(string.toLowerCase(Locale.ENGLISH));
         stringHashSet.add(string.toLowerCase(Locale.ENGLISH));
     }
     }

+ 49 - 25
src/main/java/com/gmail/nossr50/util/Misc.java

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.util;
 package com.gmail.nossr50.util;
 
 
+import com.gmail.nossr50.api.ItemSpawnReason;
 import com.gmail.nossr50.events.items.McMMOItemSpawnEvent;
 import com.gmail.nossr50.events.items.McMMOItemSpawnEvent;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
 import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask;
@@ -11,6 +12,8 @@ import org.bukkit.block.BlockState;
 import org.bukkit.entity.*;
 import org.bukkit.entity.*;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.util.Vector;
 import org.bukkit.util.Vector;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Locale;
 import java.util.Locale;
@@ -18,7 +21,7 @@ import java.util.Random;
 import java.util.Set;
 import java.util.Set;
 
 
 public final class Misc {
 public final class Misc {
-    private static final Random random = new Random();
+    private static final @NotNull Random random = new Random();
 
 
     public static final int TIME_CONVERSION_FACTOR = 1000;
     public static final int TIME_CONVERSION_FACTOR = 1000;
     public static final int TICK_CONVERSION_FACTOR = 20;
     public static final int TICK_CONVERSION_FACTOR = 20;
@@ -37,7 +40,7 @@ public final class Misc {
     public static final float LEVELUP_PITCH    = 0.5F;  // Reduced to differentiate between vanilla level-up
     public static final float LEVELUP_PITCH    = 0.5F;  // Reduced to differentiate between vanilla level-up
     public static final float LEVELUP_VOLUME   = 0.75F * Config.getInstance().getMasterVolume(); // Use max volume always*/
     public static final float LEVELUP_VOLUME   = 0.75F * Config.getInstance().getMasterVolume(); // Use max volume always*/
 
 
-    public static final Set<String> modNames = ImmutableSet.of("LOTR", "BUILDCRAFT", "ENDERIO", "ENHANCEDBIOMES", "IC2", "METALLURGY", "FORESTRY", "GALACTICRAFT", "RAILCRAFT", "TWILIGHTFOREST", "THAUMCRAFT", "GRAVESTONEMOD", "GROWTHCRAFT", "ARCTICMOBS", "DEMONMOBS", "INFERNOMOBS", "SWAMPMOBS", "MARICULTURE", "MINESTRAPPOLATION");
+    public static final @NotNull Set<String> modNames = ImmutableSet.of("LOTR", "BUILDCRAFT", "ENDERIO", "ENHANCEDBIOMES", "IC2", "METALLURGY", "FORESTRY", "GALACTICRAFT", "RAILCRAFT", "TWILIGHTFOREST", "THAUMCRAFT", "GRAVESTONEMOD", "GROWTHCRAFT", "ARCTICMOBS", "DEMONMOBS", "INFERNOMOBS", "SWAMPMOBS", "MARICULTURE", "MINESTRAPPOLATION");
 
 
     private Misc() {}
     private Misc() {}
 
 
@@ -54,7 +57,7 @@ public final class Misc {
      * @param entity target entity
      * @param entity target entity
      * @return true if the entity is not a Villager and is not a "NPC"
      * @return true if the entity is not a Villager and is not a "NPC"
      */
      */
-    public static boolean isNPCEntityExcludingVillagers(Entity entity) {
+    public static boolean isNPCEntityExcludingVillagers(@NotNull Entity entity) {
         return (!isVillager(entity)
         return (!isVillager(entity)
                 && isNPCIncludingVillagers(entity)); //Compatibility with some mod..
                 && isNPCIncludingVillagers(entity)); //Compatibility with some mod..
     }
     }
@@ -73,7 +76,7 @@ public final class Misc {
         return entityType.equalsIgnoreCase("wandering_trader") || entity instanceof Villager;
         return entityType.equalsIgnoreCase("wandering_trader") || entity instanceof Villager;
     }
     }
 
 
-    public static boolean isNPCIncludingVillagers(Entity entity) {
+    public static boolean isNPCIncludingVillagers(@Nullable Entity entity) {
         return (entity == null
         return (entity == null
                 || (hasNPCMetadataTag(entity))
                 || (hasNPCMetadataTag(entity))
                 || (isNPCClassType(entity))
                 || (isNPCClassType(entity))
@@ -88,7 +91,7 @@ public final class Misc {
      * @param maxDistance The max distance apart
      * @param maxDistance The max distance apart
      * @return true if the distance between {@code first} and {@code second} is less than {@code maxDistance}, false otherwise
      * @return true if the distance between {@code first} and {@code second} is less than {@code maxDistance}, false otherwise
      */
      */
-    public static boolean isNear(Location first, Location second, double maxDistance) {
+    public static boolean isNear(@NotNull Location first, @NotNull Location second, double maxDistance) {
         return (first.getWorld() == second.getWorld()) && (first.distanceSquared(second) < (maxDistance * maxDistance) || maxDistance == 0);
         return (first.getWorld() == second.getWorld()) && (first.distanceSquared(second) < (maxDistance * maxDistance) || maxDistance == 0);
     }
     }
 
 
@@ -102,9 +105,25 @@ public final class Misc {
         return blockState.getLocation().add(0.5, 0.5, 0.5);
         return blockState.getLocation().add(0.5, 0.5, 0.5);
     }
     }
 
 
-    public static void dropItems(Location location, Collection<ItemStack> drops) {
+    public static void spawnItemsFromCollection(@NotNull Location location, @NotNull Collection<ItemStack> drops, @NotNull ItemSpawnReason itemSpawnReason) {
         for (ItemStack drop : drops) {
         for (ItemStack drop : drops) {
-            dropItem(location, drop);
+            spawnItem(location, drop, itemSpawnReason);
+        }
+    }
+
+    /**
+     * Drops only the first n items in a collection
+     * Size should always be a positive integer above 0
+     *
+     * @param location target drop location
+     * @param drops collection to iterate over
+     * @param sizeLimit the number of drops to process
+     */
+    public static void spawnItemsFromCollection(@NotNull Location location, @NotNull Collection<ItemStack> drops, @NotNull ItemSpawnReason itemSpawnReason, int sizeLimit) {
+        ItemStack[] arrayDrops = drops.toArray(new ItemStack[0]);
+
+        for(int i = 0; i < sizeLimit-1; i++) {
+            spawnItem(location, arrayDrops[i], itemSpawnReason);
         }
         }
     }
     }
 
 
@@ -115,9 +134,9 @@ public final class Misc {
      * @param is The items to drop
      * @param is The items to drop
      * @param quantity The amount of items to drop
      * @param quantity The amount of items to drop
      */
      */
-    public static void dropItems(Location location, ItemStack is, int quantity) {
+    public static void spawnItems(@NotNull Location location, @NotNull ItemStack is, int quantity, @NotNull ItemSpawnReason itemSpawnReason) {
         for (int i = 0; i < quantity; i++) {
         for (int i = 0; i < quantity; i++) {
-            dropItem(location, is);
+            spawnItem(location, is, itemSpawnReason);
         }
         }
     }
     }
 
 
@@ -126,15 +145,16 @@ public final class Misc {
      *
      *
      * @param location The location to drop the item at
      * @param location The location to drop the item at
      * @param itemStack The item to drop
      * @param itemStack The item to drop
+     * @param itemSpawnReason the reason for the item drop
      * @return Dropped Item entity or null if invalid or cancelled
      * @return Dropped Item entity or null if invalid or cancelled
      */
      */
-    public static Item dropItem(Location location, ItemStack itemStack) {
-        if (itemStack.getType() == Material.AIR) {
+    public static @Nullable Item spawnItem(@NotNull Location location, @NotNull ItemStack itemStack, @NotNull ItemSpawnReason itemSpawnReason) {
+        if (itemStack.getType() == Material.AIR || location.getWorld() == null) {
             return null;
             return null;
         }
         }
 
 
         // We can't get the item until we spawn it and we want to make it cancellable, so we have a custom event.
         // We can't get the item until we spawn it and we want to make it cancellable, so we have a custom event.
-        McMMOItemSpawnEvent event = new McMMOItemSpawnEvent(location, itemStack);
+        McMMOItemSpawnEvent event = new McMMOItemSpawnEvent(location, itemStack, itemSpawnReason);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
 
         if (event.isCancelled()) {
         if (event.isCancelled()) {
@@ -149,22 +169,23 @@ public final class Misc {
      *
      *
      * @param location The location to drop the item at
      * @param location The location to drop the item at
      * @param itemStack The item to drop
      * @param itemStack The item to drop
+     * @param itemSpawnReason the reason for the item drop
      * @return Dropped Item entity or null if invalid or cancelled
      * @return Dropped Item entity or null if invalid or cancelled
      */
      */
-    public static Item dropItem(Location location, ItemStack itemStack, int count) {
-        if (itemStack.getType() == Material.AIR) {
+    public static @Nullable Item spawnItemNaturally(@NotNull Location location, @NotNull ItemStack itemStack, @NotNull ItemSpawnReason itemSpawnReason) {
+        if (itemStack.getType() == Material.AIR || location.getWorld() == null) {
             return null;
             return null;
         }
         }
 
 
         // We can't get the item until we spawn it and we want to make it cancellable, so we have a custom event.
         // We can't get the item until we spawn it and we want to make it cancellable, so we have a custom event.
-        McMMOItemSpawnEvent event = new McMMOItemSpawnEvent(location, itemStack);
+        McMMOItemSpawnEvent event = new McMMOItemSpawnEvent(location, itemStack, itemSpawnReason);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
 
         if (event.isCancelled()) {
         if (event.isCancelled()) {
             return null;
             return null;
         }
         }
 
 
-        return location.getWorld().dropItem(location, itemStack);
+        return location.getWorld().dropItemNaturally(location, itemStack);
     }
     }
 
 
     /**
     /**
@@ -175,9 +196,9 @@ public final class Misc {
      * @param speed the speed that the item should travel
      * @param speed the speed that the item should travel
      * @param quantity The amount of items to drop
      * @param quantity The amount of items to drop
      */
      */
-    public static void spawnItemsTowardsLocation(Location fromLocation, Location toLocation, ItemStack is, int quantity, double speed) {
+    public static void spawnItemsTowardsLocation(@NotNull Location fromLocation, @NotNull Location toLocation, @NotNull ItemStack is, int quantity, double speed, @NotNull ItemSpawnReason itemSpawnReason) {
         for (int i = 0; i < quantity; i++) {
         for (int i = 0; i < quantity; i++) {
-            spawnItemTowardsLocation(fromLocation, toLocation, is, speed);
+            spawnItemTowardsLocation(fromLocation, toLocation, is, speed, itemSpawnReason);
         }
         }
     }
     }
 
 
@@ -191,7 +212,7 @@ public final class Misc {
      * @param speed the speed that the item should travel
      * @param speed the speed that the item should travel
      * @return Dropped Item entity or null if invalid or cancelled
      * @return Dropped Item entity or null if invalid or cancelled
      */
      */
-    public static Item spawnItemTowardsLocation(Location fromLocation, Location toLocation, ItemStack itemToSpawn, double speed) {
+    public static @Nullable Item spawnItemTowardsLocation(@NotNull Location fromLocation, @NotNull Location toLocation, @NotNull ItemStack itemToSpawn, double speed, @NotNull ItemSpawnReason itemSpawnReason) {
         if (itemToSpawn.getType() == Material.AIR) {
         if (itemToSpawn.getType() == Material.AIR) {
             return null;
             return null;
         }
         }
@@ -201,12 +222,15 @@ public final class Misc {
         Location spawnLocation = fromLocation.clone();
         Location spawnLocation = fromLocation.clone();
         Location targetLocation = toLocation.clone();
         Location targetLocation = toLocation.clone();
 
 
+        if(spawnLocation.getWorld() == null)
+            return null;
+
         // We can't get the item until we spawn it and we want to make it cancellable, so we have a custom event.
         // We can't get the item until we spawn it and we want to make it cancellable, so we have a custom event.
-        McMMOItemSpawnEvent event = new McMMOItemSpawnEvent(spawnLocation, clonedItem);
+        McMMOItemSpawnEvent event = new McMMOItemSpawnEvent(spawnLocation, clonedItem, itemSpawnReason);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
 
         //Something cancelled the event so back out
         //Something cancelled the event so back out
-        if (event.isCancelled() || event.getItemStack() == null) {
+        if (event.isCancelled()) {
             return null;
             return null;
         }
         }
 
 
@@ -224,7 +248,7 @@ public final class Misc {
         return spawnedItem;
         return spawnedItem;
     }
     }
 
 
-    public static void profileCleanup(String playerName) {
+    public static void profileCleanup(@NotNull String playerName) {
         Player player = mcMMO.p.getServer().getPlayerExact(playerName);
         Player player = mcMMO.p.getServer().getPlayerExact(playerName);
 
 
         if (player != null) {
         if (player != null) {
@@ -239,7 +263,7 @@ public final class Misc {
         }
         }
     }
     }
 
 
-    public static String getModName(String materialName) {
+    public static String getModName(@NotNull String materialName) {
         for (String mod : modNames) {
         for (String mod : modNames) {
             if (materialName.contains(mod)) {
             if (materialName.contains(mod)) {
                 return mod;
                 return mod;
@@ -258,7 +282,7 @@ public final class Misc {
     /**
     /**
      * Gets a random location near the specified location
      * Gets a random location near the specified location
      */
      */
-    public static Location getLocationOffset(Location location, double strength) {
+    public static Location getLocationOffset(@NotNull Location location, double strength) {
         double blockX = location.getBlockX();
         double blockX = location.getBlockX();
         double blockZ = location.getBlockZ();
         double blockZ = location.getBlockZ();
 
 
@@ -272,7 +296,7 @@ public final class Misc {
         return new Location(location.getWorld(), blockX, location.getY(), blockZ);
         return new Location(location.getWorld(), blockX, location.getY(), blockZ);
     }
     }
 
 
-    public static Random getRandom() {
+    public static @NotNull Random getRandom() {
         return random;
         return random;
     }
     }
 }
 }

+ 0 - 4
src/main/java/com/gmail/nossr50/util/ModManager.java

@@ -136,10 +136,6 @@ public class ModManager {
         return Config.getInstance().getBlockModsEnabled() && customLogs.contains(state.getType());
         return Config.getInstance().getBlockModsEnabled() && customLogs.contains(state.getType());
     }
     }
 
 
-    public boolean isCustomLeaf(BlockState state) {
-        return Config.getInstance().getBlockModsEnabled() && customLeaves.contains(state.getType());
-    }
-
     public boolean isCustomAbilityBlock(BlockState state) {
     public boolean isCustomAbilityBlock(BlockState state) {
         return Config.getInstance().getBlockModsEnabled() && customAbilityBlocks.contains(state.getType());
         return Config.getInstance().getBlockModsEnabled() && customAbilityBlocks.contains(state.getType());
     }
     }