Răsfoiți Sursa

merge master + singleton removal on ParticleEffectUtils

nossr50 5 ani în urmă
părinte
comite
b0c7e0368a
26 a modificat fișierele cu 683 adăugiri și 112 ștergeri
  1. 15 0
      Changelog.txt
  2. 1 0
      mcmmo-core/src/main/java/com/gmail/nossr50/config/sound/ConfigSound.java
  3. 1 0
      mcmmo-core/src/main/java/com/gmail/nossr50/core/MetadataConstants.java
  4. 1 0
      mcmmo-core/src/main/java/com/gmail/nossr50/datatypes/meta/OldName.java
  5. 17 0
      mcmmo-core/src/main/java/com/gmail/nossr50/datatypes/meta/RecentlyReplantedCropMeta.java
  6. 1 0
      mcmmo-core/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java
  7. 11 18
      mcmmo-core/src/main/java/com/gmail/nossr50/datatypes/skills/behaviours/HerbalismBehaviour.java
  8. 1 1
      mcmmo-core/src/main/java/com/gmail/nossr50/dumpster/HolidayManager.java
  9. 330 0
      mcmmo-core/src/main/java/com/gmail/nossr50/dumpster/SalvageManager.java
  10. 12 15
      mcmmo-core/src/main/java/com/gmail/nossr50/listeners/BlockListener.java
  11. 8 5
      mcmmo-core/src/main/java/com/gmail/nossr50/listeners/EntityListener.java
  12. 9 4
      mcmmo-core/src/main/java/com/gmail/nossr50/mcMMO.java
  13. 1 1
      mcmmo-core/src/main/java/com/gmail/nossr50/runnables/skills/BleedTimerTask.java
  14. 101 0
      mcmmo-core/src/main/java/com/gmail/nossr50/runnables/skills/DelayedCropReplant.java
  15. 1 1
      mcmmo-core/src/main/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsManager.java
  16. 1 1
      mcmmo-core/src/main/java/com/gmail/nossr50/skills/axes/AxesManager.java
  17. 115 38
      mcmmo-core/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java
  18. 3 3
      mcmmo-core/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java
  19. 1 1
      mcmmo-core/src/main/java/com/gmail/nossr50/skills/taming/TrackedTamingEntity.java
  20. 9 2
      mcmmo-core/src/main/java/com/gmail/nossr50/util/player/UserManager.java
  21. 7 8
      mcmmo-core/src/main/java/com/gmail/nossr50/util/skills/CombatTools.java
  22. 22 11
      mcmmo-core/src/main/java/com/gmail/nossr50/util/skills/ParticleEffectUtils.java
  23. 7 0
      mcmmo-core/src/main/java/com/gmail/nossr50/util/sounds/SoundManager.java
  24. 1 0
      mcmmo-core/src/main/java/com/gmail/nossr50/util/sounds/SoundType.java
  25. 3 3
      mcmmo-core/src/main/resources/plugin.yml
  26. 4 0
      mcmmo-core/src/main/resources/sounds.yml

+ 15 - 0
Changelog.txt

@@ -203,6 +203,21 @@ Version 2.2.0
     Added API method to check if a skill was being level capped
     Added API method to check if a skill was being level capped
     Added 'UndefinedSkillBehaviour' for trying to use a method that has no behaviour defined for the provided skill
     Added 'UndefinedSkillBehaviour' for trying to use a method that has no behaviour defined for the provided skill
 
 
+Version 2.1.115
+    Cocoa plants now require GT of at least 2 to start at the second stage of growth
+    Green Terra now boosts growth on Green Thumb by 1 stage (doesn't go above the maximum value though)
+    Green Thumb now requires a hoe to activate
+    You can sneak to break plants with a hoe in your hand (or just put the hoe away)
+    Hoes no longer give free replants
+    There is now a feature in place to prevent breaking a newly automatically replanted (via green thumb) crop from being breakable for a few seconds after it appears
+    Using a hoe on non-fully grown crops will replant them as a convenience feature for those who can't bother to wait for all of their plants to grow (put away the hoe to break non-fully grown crops)
+    Fixed a bug where Salvage always gave the best results
+    Fixed an issue with arrows causing exceptions with players not yet having data loaded
+    Spectral arrows are now tracked by mcMMO
+    Use minimum level of salvageable properly
+    Fix Axes Critical Strikes default permissions ( new fixed permission: mcmmo.ability.axes.criticalstrikes )
+    Fix potential null pointer exception for salvage
+
 Version 2.1.114
 Version 2.1.114
     Fix some more locale usages, should aim to further prevent issues with oddball locales
     Fix some more locale usages, should aim to further prevent issues with oddball locales
     Fixed a bug where newer versions of MySQL did not like our rank command
     Fixed a bug where newer versions of MySQL did not like our rank command

+ 1 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/config/sound/ConfigSound.java

@@ -30,6 +30,7 @@ public class ConfigSound {
         SOUND_SETTINGS_MAP_DEFAULT.put(SoundType.TIRED, new SoundSetting(1.0, 1.7));
         SOUND_SETTINGS_MAP_DEFAULT.put(SoundType.TIRED, new SoundSetting(1.0, 1.7));
         SOUND_SETTINGS_MAP_DEFAULT.put(SoundType.BLEED, new SoundSetting(2.0, 2.0));
         SOUND_SETTINGS_MAP_DEFAULT.put(SoundType.BLEED, new SoundSetting(2.0, 2.0));
         SOUND_SETTINGS_MAP_DEFAULT.put(SoundType.GLASS, new SoundSetting(1.0, 1.0));
         SOUND_SETTINGS_MAP_DEFAULT.put(SoundType.GLASS, new SoundSetting(1.0, 1.0));
+        SOUND_SETTINGS_MAP_DEFAULT.put(SoundType.ITEM_CONSUMED, new SoundSetting(1.0, 2.0));
     }
     }
 
 
     @Setting(value = "Sound-Settings", comment = "Adjust sound settings for various mcMMO sounds here." +
     @Setting(value = "Sound-Settings", comment = "Adjust sound settings for various mcMMO sounds here." +

+ 1 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/core/MetadataConstants.java

@@ -8,6 +8,7 @@ import org.bukkit.metadata.FixedMetadataValue;
 public class MetadataConstants {
 public class MetadataConstants {
 
 
     /* Metadata Values */
     /* Metadata Values */
+    public static final String REPLANT_META_KEY = "mcMMO: Recently Replanted";
     public static final String FISH_HOOK_REF_METAKEY = "mcMMO: Fish Hook Tracker";
     public static final String FISH_HOOK_REF_METAKEY = "mcMMO: Fish Hook Tracker";
     public static final String DODGE_TRACKER        = "mcMMO: Dodge Tracker";
     public static final String DODGE_TRACKER        = "mcMMO: Dodge Tracker";
     public static final String CUSTOM_DAMAGE_METAKEY = "mcMMO: Custom Damage";
     public static final String CUSTOM_DAMAGE_METAKEY = "mcMMO: Custom Damage";

+ 1 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/datatypes/meta/OldName.java

@@ -11,4 +11,5 @@ public class OldName extends FixedMetadataValue {
     public OldName(String oldName, mcMMO plugin) {
     public OldName(String oldName, mcMMO plugin) {
         super(plugin, oldName);
         super(plugin, oldName);
     }
     }
+
 }
 }

+ 17 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/datatypes/meta/RecentlyReplantedCropMeta.java

@@ -0,0 +1,17 @@
+package com.gmail.nossr50.datatypes.meta;
+
+import org.bukkit.metadata.FixedMetadataValue;
+import org.bukkit.plugin.Plugin;
+
+public class RecentlyReplantedCropMeta extends FixedMetadataValue {
+
+    /**
+     * Initializes a FixedMetadataValue with an Object
+     *
+     * @param owningPlugin the {@link Plugin} that created this metadata value
+     */
+    public RecentlyReplantedCropMeta(Plugin owningPlugin, Boolean recentlyPlanted) {
+        super(owningPlugin, recentlyPlanted);
+    }
+
+}

+ 1 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java

@@ -39,6 +39,7 @@ import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.metadata.FixedMetadataValue;
 import org.bukkit.metadata.FixedMetadataValue;
 import org.bukkit.plugin.Plugin;
 import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
 
 
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;

+ 11 - 18
mcmmo-core/src/main/java/com/gmail/nossr50/datatypes/skills/behaviours/HerbalismBehaviour.java

@@ -23,7 +23,8 @@ public class HerbalismBehaviour {
     /**
     /**
      * Convert blocks affected by the Green Thumb & Green Terra abilities.
      * Convert blocks affected by the Green Thumb & Green Terra abilities.
      *
      *
-     * @param blockState The {@link BlockState} to check ability activation for
+     * @param blockState
+     *            The {@link BlockState} to check ability activation for
      * @return true if the ability was successful, false otherwise
      * @return true if the ability was successful, false otherwise
      */
      */
     public boolean convertGreenTerraBlocks(BlockState blockState) {
     public boolean convertGreenTerraBlocks(BlockState blockState) {
@@ -36,16 +37,16 @@ public class HerbalismBehaviour {
                 blockState.setType(Material.MOSSY_STONE_BRICKS);
                 blockState.setType(Material.MOSSY_STONE_BRICKS);
                 return true;
                 return true;
 
 
-            case DIRT:
-            case GRASS_PATH:
+            case DIRT :
+            case GRASS_PATH :
                 blockState.setType(Material.GRASS_BLOCK);
                 blockState.setType(Material.GRASS_BLOCK);
                 return true;
                 return true;
 
 
-            case COBBLESTONE:
+            case COBBLESTONE :
                 blockState.setType(Material.MOSSY_COBBLESTONE);
                 blockState.setType(Material.MOSSY_COBBLESTONE);
                 return true;
                 return true;
 
 
-            default:
+            default :
                 return false;
                 return false;
         }
         }
     }
     }
@@ -53,29 +54,21 @@ public class HerbalismBehaviour {
     /**
     /**
      * Convert blocks affected by the Green Thumb & Green Terra abilities.
      * Convert blocks affected by the Green Thumb & Green Terra abilities.
      *
      *
-     * @param blockState The {@link BlockState} to check ability activation for
+     * @param blockState
+     *            The {@link BlockState} to check ability activation for
      * @return true if the ability was successful, false otherwise
      * @return true if the ability was successful, false otherwise
      */
      */
     public boolean convertShroomThumb(BlockState blockState) {
     public boolean convertShroomThumb(BlockState blockState) {
         switch (blockState.getType()) {
         switch (blockState.getType()) {
-            case DIRT:
+            case DIRT :
             case GRASS_BLOCK:
             case GRASS_BLOCK:
-            case GRASS_PATH:
+            case GRASS_PATH :
                 blockState.setType(Material.MYCELIUM);
                 blockState.setType(Material.MYCELIUM);
                 return true;
                 return true;
 
 
-            default:
+            default :
                 return false;
                 return false;
         }
         }
     }
     }
 
 
-    /**
-     * Check if the block has a recently grown crop from Green Thumb
-     *
-     * @param blockState The {@link BlockState} to check green thumb regrown for
-     * @return true if the block is recently regrown, false otherwise
-     */
-    public boolean isRecentlyRegrown(BlockState blockState) {
-        return blockState.hasMetadata(MetadataConstants.GREEN_THUMB_METAKEY) && !pluginRef.getSkillTools().cooldownExpired(blockState.getMetadata(MetadataConstants.GREEN_THUMB_METAKEY).get(0).asInt(), 1);
-    }
 }
 }

+ 1 - 1
mcmmo-core/src/main/java/com/gmail/nossr50/dumpster/HolidayManager.java

@@ -370,7 +370,7 @@
 //        int levelTotal = Misc.getRandom().nextInt(1 + pluginRef.getUserManager().getPlayer(player).getSkillLevel(PrimarySkillType.MINING)) + 1;
 //        int levelTotal = Misc.getRandom().nextInt(1 + pluginRef.getUserManager().getPlayer(player).getSkillLevel(PrimarySkillType.MINING)) + 1;
 //        pluginRef.getSoundManager().sendSound(player, player.getLocation(), SoundType.LEVEL_UP);
 //        pluginRef.getSoundManager().sendSound(player, player.getLocation(), SoundType.LEVEL_UP);
 //        mcMMO.getNotificationManager().sendPlayerInformation(player, NotificationType.HOLIDAY, "Holiday.AprilFools.Levelup", StringUtils.getCapitalized(fakeSkillType.toString()), String.valueOf(levelTotal));
 //        mcMMO.getNotificationManager().sendPlayerInformation(player, NotificationType.HOLIDAY, "Holiday.AprilFools.Levelup", StringUtils.getCapitalized(fakeSkillType.toString()), String.valueOf(levelTotal));
-////        ParticleEffectUtils.fireworkParticleShower(player, ALL_COLORS.get(Misc.getRandom().nextInt(ALL_COLORS.size())));
+////        pluginRef.getParticleEffectUtils().fireworkParticleShower(player, ALL_COLORS.get(Misc.getRandom().nextInt(ALL_COLORS.size())));
 //    }
 //    }
 //
 //
 //    public void registerAprilCommand() {
 //    public void registerAprilCommand() {

+ 330 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/dumpster/SalvageManager.java

@@ -0,0 +1,330 @@
+//package com.gmail.nossr50.skills.salvage;
+//
+//import com.gmail.nossr50.config.AdvancedConfig;
+//import com.gmail.nossr50.config.Config;
+//import com.gmail.nossr50.config.experience.ExperienceConfig;
+//import com.gmail.nossr50.datatypes.interactions.NotificationType;
+//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.locale.LocaleLoader;
+//import com.gmail.nossr50.mcMMO;
+//import com.gmail.nossr50.skills.SkillManager;
+//import com.gmail.nossr50.skills.salvage.salvageables.Salvageable;
+//import com.gmail.nossr50.util.EventUtils;
+//import com.gmail.nossr50.util.Misc;
+//import com.gmail.nossr50.util.Permissions;
+//import com.gmail.nossr50.util.StringUtils;
+//import com.gmail.nossr50.util.player.NotificationManager;
+//import com.gmail.nossr50.util.random.RandomChanceSkillStatic;
+//import com.gmail.nossr50.util.random.RandomChanceUtil;
+//import com.gmail.nossr50.util.skills.RankUtils;
+//import com.gmail.nossr50.util.skills.SkillUtils;
+//import com.gmail.nossr50.util.sounds.SoundManager;
+//import com.gmail.nossr50.util.sounds.SoundType;
+//import org.bukkit.Location;
+//import org.bukkit.Material;
+//import org.bukkit.enchantments.Enchantment;
+//import org.bukkit.entity.Player;
+//import org.bukkit.inventory.ItemStack;
+//import org.bukkit.inventory.meta.EnchantmentStorageMeta;
+//
+//import java.util.Map;
+//import java.util.Map.Entry;
+//
+//public class SalvageManager extends SkillManager {
+//    private boolean placedAnvil;
+//    private int     lastClick;
+//
+//    public SalvageManager(McMMOPlayer mcMMOPlayer) {
+//        super(mcMMOPlayer, PrimarySkillType.SALVAGE);
+//    }
+//
+//    /**
+//     * Handles notifications for placing an anvil.
+//     */
+//    public void placedAnvilCheck() {
+//        Player player = getPlayer();
+//
+//        if (getPlacedAnvil()) {
+//            return;
+//        }
+//
+//        if (Config.getInstance().getSalvageAnvilMessagesEnabled()) {
+//            NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Salvage.Listener.Anvil");
+//        }
+//
+//        if (Config.getInstance().getSalvageAnvilPlaceSoundsEnabled()) {
+//            SoundManager.sendSound(player, player.getLocation(), SoundType.ANVIL);
+//        }
+//
+//        togglePlacedAnvil();
+//    }
+//
+//    public void handleSalvage(Location location, ItemStack item) {
+//        Player player = getPlayer();
+//
+//        Salvageable salvageable = mcMMO.getSalvageableManager().getSalvageable(item.getType());
+//
+//        if (item.getItemMeta() != null && item.getItemMeta().isUnbreakable()) {
+//            NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Anvil.Unbreakable");
+//            return;
+//        }
+//
+//        // Permissions checks on material and item types
+//        if (!Permissions.salvageItemType(player, salvageable.getSalvageItemType())) {
+//            NotificationManager.sendPlayerInformation(player, NotificationType.NO_PERMISSION, "mcMMO.NoPermission");
+//            return;
+//        }
+//
+//        if (!Permissions.salvageMaterialType(player, salvageable.getSalvageMaterialType())) {
+//            NotificationManager.sendPlayerInformation(player, NotificationType.NO_PERMISSION, "mcMMO.NoPermission");
+//            return;
+//        }
+//
+//        /*int skillLevel = getSkillLevel();*/
+//        int minimumSalvageableLevel = salvageable.getMinimumLevel();
+//
+//        // Level check
+//        if (getSkillLevel() < minimumSalvageableLevel) {
+//            NotificationManager.sendPlayerInformation(player, NotificationType.REQUIREMENTS_NOT_MET, "Salvage.Skills.Adept.Level", String.valueOf(RankUtils.getUnlockLevel(SubSkillType.SALVAGE_ARCANE_SALVAGE)), StringUtils.getPrettyItemString(item.getType()));
+//            return;
+//        }
+//
+//        int potentialSalvageYield = Salvage.calculateSalvageableAmount(item.getDurability(), salvageable.getMaximumDurability(), salvageable.getMaximumQuantity());
+//
+//        if (potentialSalvageYield <= 0) {
+//            NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Salvage.Skills.TooDamaged");
+//            return;
+//        }
+//
+//        potentialSalvageYield = Math.min(potentialSalvageYield, getSalvageLimit()); // Always get at least something back, if you're capable of salvaging it.
+//
+//        player.getInventory().setItemInMainHand(new ItemStack(Material.AIR));
+//        location.add(0.5, 1, 0.5);
+//
+//        Map<Enchantment, Integer> enchants = item.getEnchantments();
+//
+//        ItemStack enchantBook = null;
+//        if (!enchants.isEmpty()) {
+//            enchantBook = arcaneSalvageCheck(enchants);
+//        }
+//
+//        //Lottery on Salvageable Amount
+//
+//        int lotteryResults = 1;
+//        int chanceOfSuccess = 99;
+//
+//        for(int x = 0; x < potentialSalvageYield-1; x++) {
+//
+//            if(RandomChanceUtil.rollDice(chanceOfSuccess, 100)) {
+//                chanceOfSuccess-=3;
+//                chanceOfSuccess = Math.max(chanceOfSuccess, 90);
+//
+//                lotteryResults+=1;
+//            }
+//        }
+//
+//        if(lotteryResults == potentialSalvageYield && potentialSalvageYield != 1 && RankUtils.isPlayerMaxRankInSubSkill(player, SubSkillType.SALVAGE_ARCANE_SALVAGE)) {
+//            NotificationManager.sendPlayerInformationChatOnly(player, "Salvage.Skills.Lottery.Perfect", String.valueOf(lotteryResults), StringUtils.getPrettyItemString(item.getType()));
+//        } else if(salvageable.getMaximumQuantity() == 1 || getSalvageLimit() >= salvageable.getMaximumQuantity()) {
+//            NotificationManager.sendPlayerInformationChatOnly(player,  "Salvage.Skills.Lottery.Normal", String.valueOf(lotteryResults), StringUtils.getPrettyItemString(item.getType()));
+//        } else {
+//            NotificationManager.sendPlayerInformationChatOnly(player,  "Salvage.Skills.Lottery.Untrained", String.valueOf(lotteryResults), StringUtils.getPrettyItemString(item.getType()));
+//        }
+//
+//        ItemStack salvageResults = new ItemStack(salvageable.getSalvageMaterial(), lotteryResults);
+//
+//        //Call event
+//        if (EventUtils.callSalvageCheckEvent(player, item, salvageResults, enchantBook).isCancelled()) {
+//            return;
+//        }
+//
+//        Location anvilLoc = location.clone();
+//        Location playerLoc = player.getLocation().clone();
+//        double distance = anvilLoc.distance(playerLoc);
+//
+//        double speedLimit = .6;
+//        double minSpeed = .3;
+//
+//        //Clamp the speed and vary it by distance
+//        double vectorSpeed = Math.min(speedLimit, Math.max(minSpeed, distance * .2));
+//
+//        //Add a very small amount of height
+//        anvilLoc.add(0, .1, 0);
+//
+//        if (enchantBook != null) {
+//            Misc.spawnItemTowardsLocation(anvilLoc.clone(), playerLoc.clone(), enchantBook, vectorSpeed);
+//        }
+//
+//        Misc.spawnItemTowardsLocation(anvilLoc.clone(), playerLoc.clone(), salvageResults, vectorSpeed);
+//
+//        // BWONG BWONG BWONG - CLUNK!
+//        if (Config.getInstance().getSalvageAnvilUseSoundsEnabled()) {
+//            SoundManager.sendSound(player, player.getLocation(), SoundType.ITEM_BREAK);
+//        }
+//
+//        NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Salvage.Skills.Success");
+//    }
+//
+//    /*public double getMaxSalvagePercentage() {
+//        return Math.min((((Salvage.salvageMaxPercentage / Salvage.salvageMaxPercentageLevel) * getSkillLevel()) / 100.0D), Salvage.salvageMaxPercentage / 100.0D);
+//    }*/
+//
+//    public int getSalvageLimit() {
+//        return (RankUtils.getRank(getPlayer(), SubSkillType.SALVAGE_SCRAP_COLLECTOR));
+//    }
+//
+//    /**
+//     * Gets the Arcane Salvage rank
+//     *
+//     * @return the current Arcane Salvage rank
+//     */
+//    public int getArcaneSalvageRank() {
+//        return RankUtils.getRank(getPlayer(), SubSkillType.SALVAGE_ARCANE_SALVAGE);
+//    }
+//
+//    /*public double getExtractFullEnchantChance() {
+//        int skillLevel = getSkillLevel();
+//
+//        for (Tier tier : Tier.values()) {
+//            if (skillLevel >= tier.getLevel()) {
+//                return tier.getExtractFullEnchantChance();
+//            }
+//        }
+//
+//        return 0;
+//    }
+//
+//    public double getExtractPartialEnchantChance() {
+//        int skillLevel = getSkillLevel();
+//
+//        for (Tier tier : Tier.values()) {
+//            if (skillLevel >= tier.getLevel()) {
+//                return tier.getExtractPartialEnchantChance();
+//            }
+//        }
+//
+//        return 0;
+//    }*/
+//
+//    public double getExtractFullEnchantChance() {
+//        if(Permissions.hasSalvageEnchantBypassPerk(getPlayer()))
+//            return 100.0D;
+//
+//        return AdvancedConfig.getInstance().getArcaneSalvageExtractFullEnchantsChance(getArcaneSalvageRank());
+//    }
+//
+//    public double getExtractPartialEnchantChance() {
+//        return AdvancedConfig.getInstance().getArcaneSalvageExtractPartialEnchantsChance(getArcaneSalvageRank());
+//    }
+//
+//    private ItemStack arcaneSalvageCheck(Map<Enchantment, Integer> enchants) {
+//        Player player = getPlayer();
+//
+//        if (!RankUtils.hasUnlockedSubskill(player, SubSkillType.SALVAGE_ARCANE_SALVAGE) || !Permissions.arcaneSalvage(player)) {
+//            NotificationManager.sendPlayerInformationChatOnly(player, "Salvage.Skills.ArcaneFailed");
+//            return null;
+//        }
+//
+//        ItemStack book = new ItemStack(Material.ENCHANTED_BOOK);
+//        EnchantmentStorageMeta enchantMeta = (EnchantmentStorageMeta) book.getItemMeta();
+//
+//        boolean downgraded = false;
+//        int arcaneFailureCount = 0;
+//
+//        for (Entry<Enchantment, Integer> enchant : enchants.entrySet()) {
+//
+//            int enchantLevel = enchant.getValue();
+//
+//            if(!ExperienceConfig.getInstance().allowUnsafeEnchantments()) {
+//                if(enchantLevel > enchant.getKey().getMaxLevel()) {
+//                    enchantLevel = enchant.getKey().getMaxLevel();
+//                }
+//            }
+//
+//            if (!Salvage.arcaneSalvageEnchantLoss
+//                    || Permissions.hasSalvageEnchantBypassPerk(player)
+//                    || RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(getExtractFullEnchantChance(), getPlayer(), SubSkillType.SALVAGE_ARCANE_SALVAGE))) {
+//                enchantMeta.addStoredEnchant(enchant.getKey(), enchantLevel, true);
+//            }
+//            else if (enchantLevel > 1
+//                    && Salvage.arcaneSalvageDowngrades
+//                    && RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(getExtractPartialEnchantChance(), getPlayer(), SubSkillType.SALVAGE_ARCANE_SALVAGE))) {
+//                enchantMeta.addStoredEnchant(enchant.getKey(), enchantLevel - 1, true);
+//                downgraded = true;
+//            } else {
+//                arcaneFailureCount++;
+//            }
+//        }
+//
+//        if(failedAllEnchants(arcaneFailureCount, enchants.entrySet().size()))
+//        {
+//            NotificationManager.sendPlayerInformationChatOnly(player,  "Salvage.Skills.ArcaneFailed");
+//            return null;
+//        } else if(downgraded)
+//        {
+//            NotificationManager.sendPlayerInformationChatOnly(player,  "Salvage.Skills.ArcanePartial");
+//        }
+//
+//        book.setItemMeta(enchantMeta);
+//        return book;
+//    }
+//
+//    private boolean failedAllEnchants(int arcaneFailureCount, int size) {
+//        return arcaneFailureCount == size;
+//    }
+//
+//    /**
+//     * Check if the player has tried to use an Anvil before.
+//     * @param actualize
+//     *
+//     * @return true if the player has confirmed using an Anvil
+//     */
+//    public boolean checkConfirmation(boolean actualize) {
+//        Player player = getPlayer();
+//        long lastUse = getLastAnvilUse();
+//
+//        if (!SkillUtils.cooldownExpired(lastUse, 3) || !Config.getInstance().getSalvageConfirmRequired()) {
+//            return true;
+//        }
+//
+//        if (!actualize) {
+//            return false;
+//        }
+//
+//        actualizeLastAnvilUse();
+//
+//        NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Skills.ConfirmOrCancel", LocaleLoader.getString("Salvage.Pretty.Name"));
+//
+//        return false;
+//    }
+//
+//    /*
+//     * Salvage Anvil Placement
+//     */
+//
+//    public boolean getPlacedAnvil() {
+//        return placedAnvil;
+//    }
+//
+//    public void togglePlacedAnvil() {
+//        placedAnvil = !placedAnvil;
+//    }
+//
+//    /*
+//     * Salvage Anvil Usage
+//     */
+//
+//    public int getLastAnvilUse() {
+//        return lastClick;
+//    }
+//
+//    public void setLastAnvilUse(int value) {
+//        lastClick = value;
+//    }
+//
+//    public void actualizeLastAnvilUse() {
+//        lastClick = (int) (System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR);
+//    }
+//}

+ 12 - 15
mcmmo-core/src/main/java/com/gmail/nossr50/listeners/BlockListener.java

@@ -397,27 +397,24 @@ public class BlockListener implements Listener {
             return;
             return;
         }
         }
 
 
+        McMMOPlayer mcMMOPlayer = pluginRef.getUserManager().getPlayer(player);
         BlockState blockState = event.getBlock().getState();
         BlockState blockState = event.getBlock().getState();
         ItemStack heldItem = player.getInventory().getItemInMainHand();
         ItemStack heldItem = player.getInventory().getItemInMainHand();
 
 
-        if (pluginRef.getDynamicSettingsManager().getSkillBehaviourManager().getHerbalismBehaviour().isRecentlyRegrown(blockState)) {
-            event.setCancelled(true);
-            return;
-        }
 
 
         if (pluginRef.getItemTools().isSword(heldItem)) {
         if (pluginRef.getItemTools().isSword(heldItem)) {
-            HerbalismManager herbalismManager = pluginRef.getUserManager().getPlayer(player).getHerbalismManager();
-
-            if (herbalismManager.canUseHylianLuck()) {
-                if (herbalismManager.processHylianLuck(blockState)) {
-                    blockState.update(true);
-                    event.setCancelled(true);
-                } else if (blockState.getType() == Material.FLOWER_POT) {
-                    blockState.setType(Material.AIR);
-                    blockState.update(true);
-                    event.setCancelled(true);
+                HerbalismManager herbalismManager = mcMMOPlayer.getHerbalismManager();
+
+                if (herbalismManager.canUseHylianLuck()) {
+                    if (herbalismManager.processHylianLuck(blockState)) {
+                        blockState.update(true);
+                        event.setCancelled(true);
+                    } else if (blockState.getType() == Material.FLOWER_POT) {
+                        blockState.setType(Material.AIR);
+                        blockState.update(true);
+                        event.setCancelled(true);
+                    }
                 }
                 }
-            }
         }
         }
         /*else if (!heldItem.containsEnchantment(Enchantment.SILK_TOUCH)) {
         /*else if (!heldItem.containsEnchantment(Enchantment.SILK_TOUCH)) {
             SmeltingManager smeltingManager = pluginRef.getUserManager().getPlayer(player).getSmeltingManager();
             SmeltingManager smeltingManager = pluginRef.getUserManager().getPlayer(player).getSmeltingManager();

+ 8 - 5
mcmmo-core/src/main/java/com/gmail/nossr50/listeners/EntityListener.java

@@ -352,12 +352,15 @@ public class EntityListener implements Listener {
                     }
                     }
 
 
                     //Deflect checks
                     //Deflect checks
-                    UnarmedManager unarmedManager = pluginRef.getUserManager().getPlayer(defendingPlayer).getUnarmedManager();
+                    final McMMOPlayer mcMMOPlayer = pluginRef.getUserManager().getPlayer(defendingPlayer);
+                    if (mcMMOPlayer != null) {
+                        UnarmedManager unarmedManager = mcMMOPlayer.getUnarmedManager();
 
 
-                    if (unarmedManager.canDeflect()) {
-                        if(unarmedManager.deflectCheck()) {
-                            event.setCancelled(true);
-                            return;
+                        if (unarmedManager.canDeflect()) {
+                            if (unarmedManager.deflectCheck()) {
+                                event.setCancelled(true);
+                                return;
+                            }
                         }
                         }
                     }
                     }
                 } else {
                 } else {

+ 9 - 4
mcmmo-core/src/main/java/com/gmail/nossr50/mcMMO.java

@@ -40,10 +40,7 @@ import com.gmail.nossr50.util.player.PlayerLevelTools;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.random.RandomChanceTools;
 import com.gmail.nossr50.util.random.RandomChanceTools;
 import com.gmail.nossr50.util.scoreboards.ScoreboardManager;
 import com.gmail.nossr50.util.scoreboards.ScoreboardManager;
-import com.gmail.nossr50.util.skills.CombatTools;
-import com.gmail.nossr50.util.skills.PerkUtils;
-import com.gmail.nossr50.util.skills.RankTools;
-import com.gmail.nossr50.util.skills.SkillTools;
+import com.gmail.nossr50.util.skills.*;
 import com.gmail.nossr50.util.sounds.SoundManager;
 import com.gmail.nossr50.util.sounds.SoundManager;
 import com.gmail.nossr50.worldguard.WorldGuardManager;
 import com.gmail.nossr50.worldguard.WorldGuardManager;
 import com.gmail.nossr50.worldguard.WorldGuardUtils;
 import com.gmail.nossr50.worldguard.WorldGuardUtils;
@@ -93,6 +90,7 @@ public class mcMMO extends JavaPlugin {
     private WorldGuardManager worldGuardManager;
     private WorldGuardManager worldGuardManager;
 
 
     /* Not-Managers but my naming scheme sucks */
     /* Not-Managers but my naming scheme sucks */
+    private ParticleEffectUtils particleEffectUtils;
     private DatabaseManagerFactory databaseManagerFactory;
     private DatabaseManagerFactory databaseManagerFactory;
     private ChunkManagerFactory chunkManagerFactory;
     private ChunkManagerFactory chunkManagerFactory;
     private CommandTools commandTools;
     private CommandTools commandTools;
@@ -311,6 +309,9 @@ public class mcMMO extends JavaPlugin {
 
 
         //Init PerkUtils
         //Init PerkUtils
         perkUtils = new PerkUtils(this);
         perkUtils = new PerkUtils(this);
+
+        //Init particle effect utils
+        particleEffectUtils = new ParticleEffectUtils(this);
     }
     }
 
 
     @Override
     @Override
@@ -870,4 +871,8 @@ public class mcMMO extends JavaPlugin {
     public PlatformManager getPlatformManager() {
     public PlatformManager getPlatformManager() {
         return platformManager;
         return platformManager;
     }
     }
+
+    public ParticleEffectUtils getParticleEffectUtils() {
+        return particleEffectUtils;
+    }
 }
 }

+ 1 - 1
mcmmo-core/src/main/java/com/gmail/nossr50/runnables/skills/BleedTimerTask.java

@@ -172,7 +172,7 @@ public class BleedTimerTask extends BukkitRunnable {
                 //Play Bleed Sound
                 //Play Bleed Sound
                 pluginRef.getSoundManager().worldSendSound(target.getWorld(), target.getLocation(), SoundType.BLEED);
                 pluginRef.getSoundManager().worldSendSound(target.getWorld(), target.getLocation(), SoundType.BLEED);
 
 
-                ParticleEffectUtils.playBleedEffect(target);
+                pluginRef.getParticleEffectUtils().playBleedEffect(target);
             }
             }
 
 
             //Lower Bleed Ticks
             //Lower Bleed Ticks

+ 101 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/runnables/skills/DelayedCropReplant.java

@@ -0,0 +1,101 @@
+package com.gmail.nossr50.runnables.skills;
+
+import com.gmail.nossr50.core.MetadataConstants;
+import com.gmail.nossr50.datatypes.meta.RecentlyReplantedCropMeta;
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.skills.ParticleEffectUtils;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.block.BlockState;
+import org.bukkit.block.data.Ageable;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.scheduler.BukkitRunnable;
+
+public class DelayedCropReplant extends BukkitRunnable {
+
+    private final int desiredCropAge;
+    private final Location cropLocation;
+    private final Material cropMaterial;
+    private boolean wasImmaturePlant;
+    private final BlockBreakEvent blockBreakEvent;
+    private final mcMMO pluginRef;
+
+    /**
+     * Replants a crop after a delay setting the age to desiredCropAge
+     * @param cropState target {@link BlockState}
+     * @param desiredCropAge desired age of the crop
+     */
+    public DelayedCropReplant(mcMMO pluginRef, BlockBreakEvent blockBreakEvent, BlockState cropState, int desiredCropAge, boolean wasImmaturePlant) {
+        this.pluginRef = pluginRef;
+        //The plant was either immature or something cancelled the event, therefor we need to treat it differently
+        this.blockBreakEvent = blockBreakEvent;
+        this.wasImmaturePlant = wasImmaturePlant;
+        this.cropMaterial = cropState.getType();
+        this.desiredCropAge = desiredCropAge;
+        this.cropLocation = cropState.getLocation();
+    }
+
+    @Override
+    public void run() {
+        Block cropBlock = cropLocation.getBlock();
+        BlockState currentState = cropBlock.getState();
+
+        //Remove the metadata marking the block as recently replanted
+        new markPlantAsOld(blockBreakEvent.getBlock().getLocation()).runTaskLater(pluginRef, 20*5);
+
+        if(blockBreakEvent.isCancelled()) {
+            wasImmaturePlant = true;
+        }
+
+        //Two kinds of air in Minecraft
+        if(currentState.getType().equals(cropMaterial) || currentState.getType().equals(Material.AIR) || currentState.getType().equals(Material.CAVE_AIR)) {
+//            if(currentState.getBlock().getRelative(BlockFace.DOWN))
+            //The space is not currently occupied by a block so we can fill it
+            cropBlock.setType(cropMaterial);
+
+            //Get new state (necessary?)
+            BlockState newState = cropBlock.getState();
+//            newState.update();
+
+            Ageable ageable = (Ageable) newState.getBlockData();
+
+            //Crop age should always be 0 if the plant was immature
+            if(wasImmaturePlant) {
+                ageable.setAge(0);
+            } else {
+                //Otherwise make the plant the desired age
+                ageable.setAge(desiredCropAge);
+            }
+
+            //Age the crop
+            newState.setBlockData(ageable);
+            newState.update(true);
+
+            //Play an effect
+            pluginRef.getParticleEffectUtils().playGreenThumbEffect(cropLocation);
+
+        }
+
+    }
+
+    private class markPlantAsOld extends BukkitRunnable {
+
+        private final Location cropLoc;
+
+        public markPlantAsOld(Location cropLoc) {
+            this.cropLoc = cropLoc;
+        }
+
+        @Override
+        public void run() {
+            Block cropBlock = cropLoc.getBlock();
+            if(cropBlock.getMetadata(MetadataConstants.REPLANT_META_KEY).size() > 0)
+                cropBlock.setMetadata(MetadataConstants.REPLANT_META_KEY, new RecentlyReplantedCropMeta(pluginRef, false));
+
+            pluginRef.getParticleEffectUtils().playFluxEffect(cropLocation);
+        }
+    }
+
+}

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

@@ -87,7 +87,7 @@ public class AcrobaticsManager extends SkillManager {
         Player player = getPlayer();
         Player player = getPlayer();
 
 
         if (!isFatal(modifiedDamage) && pluginRef.getRandomChanceTools().isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.ACROBATICS_DODGE, player)) {
         if (!isFatal(modifiedDamage) && pluginRef.getRandomChanceTools().isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.ACROBATICS_DODGE, player)) {
-            ParticleEffectUtils.playDodgeEffect(player);
+            pluginRef.getParticleEffectUtils().playDodgeEffect(player);
 
 
             if (mcMMOPlayer.useChatNotifications()) {
             if (mcMMOPlayer.useChatNotifications()) {
                 pluginRef.getNotificationManager().sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Acrobatics.Combat.Proc");
                 pluginRef.getNotificationManager().sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Acrobatics.Combat.Proc");

+ 1 - 1
mcmmo-core/src/main/java/com/gmail/nossr50/skills/axes/AxesManager.java

@@ -143,7 +143,7 @@ public class AxesManager extends SkillManager {
 
 
         Player player = getPlayer();
         Player player = getPlayer();
 
 
-        ParticleEffectUtils.playGreaterImpactEffect(target);
+        pluginRef.getParticleEffectUtils().playGreaterImpactEffect(target);
         target.setVelocity(player.getLocation().getDirection().normalize().multiply(pluginRef.getConfigManager().getConfigAxes().getGreaterImpactKnockBackModifier()));
         target.setVelocity(player.getLocation().getDirection().normalize().multiply(pluginRef.getConfigManager().getConfigAxes().getGreaterImpactKnockBackModifier()));
 
 
         if (mcMMOPlayer.useChatNotifications()) {
         if (mcMMOPlayer.useChatNotifications()) {

+ 115 - 38
mcmmo-core/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java

@@ -5,6 +5,7 @@ import com.gmail.nossr50.datatypes.BlockSnapshot;
 import com.gmail.nossr50.datatypes.experience.XPGainReason;
 import com.gmail.nossr50.datatypes.experience.XPGainReason;
 import com.gmail.nossr50.datatypes.experience.XPGainSource;
 import com.gmail.nossr50.datatypes.experience.XPGainSource;
 import com.gmail.nossr50.datatypes.interactions.NotificationType;
 import com.gmail.nossr50.datatypes.interactions.NotificationType;
+import com.gmail.nossr50.datatypes.meta.RecentlyReplantedCropMeta;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
@@ -12,11 +13,15 @@ import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
 import com.gmail.nossr50.datatypes.skills.ToolType;
 import com.gmail.nossr50.datatypes.skills.ToolType;
 import com.gmail.nossr50.datatypes.skills.behaviours.HerbalismBehaviour;
 import com.gmail.nossr50.datatypes.skills.behaviours.HerbalismBehaviour;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.runnables.skills.DelayedCropReplant;
 import com.gmail.nossr50.runnables.skills.DelayedHerbalismXPCheckTask;
 import com.gmail.nossr50.runnables.skills.DelayedHerbalismXPCheckTask;
 import com.gmail.nossr50.runnables.skills.HerbalismBlockUpdaterTask;
 import com.gmail.nossr50.runnables.skills.HerbalismBlockUpdaterTask;
 import com.gmail.nossr50.skills.SkillManager;
 import com.gmail.nossr50.skills.SkillManager;
 import com.gmail.nossr50.util.StringUtils;
 import com.gmail.nossr50.util.StringUtils;
 import com.gmail.nossr50.util.skills.SkillActivationType;
 import com.gmail.nossr50.util.skills.SkillActivationType;
+import com.gmail.nossr50.util.sounds.SoundManager;
+import com.gmail.nossr50.util.sounds.SoundType;
+import org.bukkit.Location;
 import org.bukkit.Material;
 import org.bukkit.Material;
 import org.bukkit.block.Block;
 import org.bukkit.block.Block;
 import org.bukkit.block.BlockFace;
 import org.bukkit.block.BlockFace;
@@ -134,6 +139,23 @@ public class HerbalismManager extends SkillManager {
             return;
             return;
         }
         }
 
 
+        //Check if the plant was recently replanted
+        if(blockBreakEvent.getBlock().getBlockData() instanceof Ageable) {
+            Ageable ageableCrop = (Ageable) blockBreakEvent.getBlock().getBlockData();
+
+            if(blockBreakEvent.getBlock().getMetadata(MetadataConstants.REPLANT_META_KEY).size() >= 1) {
+                if(blockBreakEvent.getBlock().getMetadata(MetadataConstants.REPLANT_META_KEY).get(0).asBoolean()) {
+                    if(isAgeableMature(ageableCrop)) {
+                        blockBreakEvent.getBlock().removeMetadata(MetadataConstants.REPLANT_META_KEY, pluginRef);
+                    } else {
+                        //Crop is recently replanted to back out of destroying it
+                        blockBreakEvent.setCancelled(true);
+                        return;
+                    }
+                }
+            }
+        }
+
         /*
         /*
          * There are single-block plants and multi-block plants in Minecraft
          * There are single-block plants and multi-block plants in Minecraft
          * In order to give out proper rewards, we need to collect all blocks that would be broken from this event
          * In order to give out proper rewards, we need to collect all blocks that would be broken from this event
@@ -142,6 +164,9 @@ public class HerbalismManager extends SkillManager {
         //Grab all broken blocks
         //Grab all broken blocks
         HashSet<Block> brokenBlocks = getBrokenHerbalismBlocks(blockBreakEvent);
         HashSet<Block> brokenBlocks = getBrokenHerbalismBlocks(blockBreakEvent);
 
 
+        if(brokenBlocks.size() == 0)
+            return;
+
         //Handle rewards, xp, ability interactions, etc
         //Handle rewards, xp, ability interactions, etc
         processHerbalismOnBlocksBroken(blockBreakEvent, brokenBlocks);
         processHerbalismOnBlocksBroken(blockBreakEvent, brokenBlocks);
     }
     }
@@ -153,10 +178,19 @@ public class HerbalismManager extends SkillManager {
      */
      */
     private void processHerbalismOnBlocksBroken(BlockBreakEvent blockBreakEvent, HashSet<Block> brokenPlants) {
     private void processHerbalismOnBlocksBroken(BlockBreakEvent blockBreakEvent, HashSet<Block> brokenPlants) {
         BlockState originalBreak = blockBreakEvent.getBlock().getState();
         BlockState originalBreak = blockBreakEvent.getBlock().getState();
+        boolean greenThumbActivated = false;
 
 
         //TODO: The design of Green Terra needs to change, this is a mess
         //TODO: The design of Green Terra needs to change, this is a mess
         if(pluginRef.getPermissionTools().greenThumbPlant(getPlayer(), originalBreak.getType())) {
         if(pluginRef.getPermissionTools().greenThumbPlant(getPlayer(), originalBreak.getType())) {
-            processGreenThumbPlants(originalBreak, isGreenTerraActive());
+            if(!getPlayer().isSneaking()) {
+                greenThumbActivated = processGreenThumbPlants(originalBreak, blockBreakEvent, isGreenTerraActive());
+            }
+
+        }
+
+        //When replanting a immature crop we cancel the block break event and back out
+        if(greenThumbActivated) {
+            return;
         }
         }
 
 
         /*
         /*
@@ -339,9 +373,11 @@ public class HerbalismManager extends SkillManager {
                 //Calculate XP
                 //Calculate XP
                 if(plantData instanceof Ageable) {
                 if(plantData instanceof Ageable) {
                     Ageable plantAgeable = (Ageable) plantData;
                     Ageable plantAgeable = (Ageable) plantData;
+
                     if(isAgeableMature(plantAgeable) || isBizarreAgeable(plantData)) {
                     if(isAgeableMature(plantAgeable) || isBizarreAgeable(plantData)) {
                         xpToReward += pluginRef.getDynamicSettingsManager().getExperienceManager().getHerbalismXp(brokenBlockNewState.getType());
                         xpToReward += pluginRef.getDynamicSettingsManager().getExperienceManager().getHerbalismXp(brokenBlockNewState.getType());
                     }
                     }
+
                 } else {
                 } else {
                     xpToReward += pluginRef.getDynamicSettingsManager().getExperienceManager().getHerbalismXp(brokenPlantBlock.getType());
                     xpToReward += pluginRef.getDynamicSettingsManager().getExperienceManager().getHerbalismXp(brokenPlantBlock.getType());
                 }
                 }
@@ -437,8 +473,7 @@ public class HerbalismManager extends SkillManager {
     }
     }
 
 
     private HashSet<Block> getBrokenChorusBlocks(BlockState originalBreak) {
     private HashSet<Block> getBrokenChorusBlocks(BlockState originalBreak) {
-        HashSet<Block> traversedBlocks = grabChorusTreeBrokenBlocksRecursive(originalBreak.getBlock(), new HashSet<>());
-        return traversedBlocks;
+        return grabChorusTreeBrokenBlocksRecursive(originalBreak.getBlock(), new HashSet<>());
     }
     }
 
 
     private HashSet<Block> grabChorusTreeBrokenBlocksRecursive(Block currentBlock, HashSet<Block> traversed) {
     private HashSet<Block> grabChorusTreeBrokenBlocksRecursive(Block currentBlock, HashSet<Block> traversed) {
@@ -565,6 +600,7 @@ public class HerbalismManager extends SkillManager {
      * @param blockState The {@link BlockState} to check ability activation for
      * @param blockState The {@link BlockState} to check ability activation for
      * @return true if the ability was successful, false otherwise
      * @return true if the ability was successful, false otherwise
      */
      */
+    //TODO: Fix hylian luck? Do we give a #*$%?
     public boolean processHylianLuck(BlockState blockState) {
     public boolean processHylianLuck(BlockState blockState) {
 //        if (!pluginRef.getRandomChanceTools().isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.HERBALISM_HYLIAN_LUCK, getPlayer())) {
 //        if (!pluginRef.getRandomChanceTools().isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.HERBALISM_HYLIAN_LUCK, getPlayer())) {
 //            return false;
 //            return false;
@@ -631,15 +667,38 @@ public class HerbalismManager extends SkillManager {
         return herbalismBehaviour.convertShroomThumb(blockState);
         return herbalismBehaviour.convertShroomThumb(blockState);
     }
     }
 
 
+    /**
+     * Starts the delayed replant task and turns
+     * @param desiredCropAge the desired age of the crop
+     * @param blockBreakEvent the {@link BlockBreakEvent} this crop was involved in
+     * @param cropState the {@link BlockState} of the crop
+     */
+    private void startReplantTask(int desiredCropAge, BlockBreakEvent blockBreakEvent, BlockState cropState, boolean isImmature) {
+        //Mark the plant as recently replanted to avoid accidental breakage
+        new DelayedCropReplant(pluginRef, blockBreakEvent, cropState, desiredCropAge, isImmature).runTaskLater(pluginRef, 20 * 2);
+        blockBreakEvent.getBlock().setMetadata(MetadataConstants.REPLANT_META_KEY, new RecentlyReplantedCropMeta(pluginRef, true));
+    }
+
     /**
     /**
      * Process the Green Thumb ability for plants.
      * Process the Green Thumb ability for plants.
      *
      *
      * @param blockState The {@link BlockState} to check ability activation for
      * @param blockState The {@link BlockState} to check ability activation for
      * @param greenTerra boolean to determine if greenTerra is active or not
      * @param greenTerra boolean to determine if greenTerra is active or not
      */
      */
-    private void processGreenThumbPlants(BlockState blockState, boolean greenTerra) {
-        if (!pluginRef.getBlockTools().isFullyGrown(blockState))
-            return;
+    private boolean processGreenThumbPlants(BlockState blockState, BlockBreakEvent blockBreakEvent, boolean greenTerra) {
+        if(!pluginRef.getItemTools().isHoe(blockBreakEvent.getPlayer().getInventory().getItemInMainHand())) {
+            return false;
+        }
+
+        BlockData blockData = blockState.getBlockData();
+
+        if (!(blockData instanceof Ageable)) {
+            return false;
+        }
+
+        Ageable ageable = (Ageable) blockData;
+
+        //If the ageable is NOT mature and the player is NOT using a hoe, abort
 
 
         Player player = getPlayer();
         Player player = getPlayer();
         PlayerInventory playerInventory = player.getInventory();
         PlayerInventory playerInventory = player.getInventory();
@@ -671,36 +730,49 @@ public class HerbalismManager extends SkillManager {
                 break;
                 break;
 
 
             default:
             default:
-                return;
+                return false;
         }
         }
 
 
         ItemStack seedStack = new ItemStack(seed);
         ItemStack seedStack = new ItemStack(seed);
 
 
         if (!greenTerra && !pluginRef.getRandomChanceTools().checkRandomChanceExecutionSuccess(player, SubSkillType.HERBALISM_GREEN_THUMB)) {
         if (!greenTerra && !pluginRef.getRandomChanceTools().checkRandomChanceExecutionSuccess(player, SubSkillType.HERBALISM_GREEN_THUMB)) {
-            return;
+            return false;
         }
         }
 
 
-        if (!processGrowingPlants(blockState, greenTerra)) {
-            return;
-        }
 
 
-        if (!pluginRef.getItemTools().isHoe(getPlayer().getInventory().getItemInMainHand())) {
-            if (!playerInventory.containsAtLeast(seedStack, 1)) {
-                return;
-            }
+        if (!playerInventory.containsAtLeast(seedStack, 1)) {
+            return false;
+        }
 
 
-            playerInventory.removeItem(seedStack);
-            player.updateInventory(); // Needed until replacement available
+        if (!processGrowingPlants(blockState, ageable, blockBreakEvent, greenTerra)) {
+            return false;
         }
         }
 
 
-        new HerbalismBlockUpdaterTask(blockState).runTaskLater(pluginRef, 0);
+        playerInventory.removeItem(seedStack);
+        player.updateInventory(); // Needed until replacement available
+        //Play sound
+        pluginRef.getSoundManager().sendSound(player, player.getLocation(), SoundType.ITEM_CONSUMED);
+        return true;
+//        new HerbalismBlockUpdaterTask(blockState).runTaskLater(mcMMO.p, 0);
     }
     }
 
 
-    private boolean processGrowingPlants(BlockState blockState, boolean greenTerra) {
-        int greenThumbStage = getGreenThumbStage();
+    private boolean processGrowingPlants(BlockState blockState, Ageable ageable, BlockBreakEvent blockBreakEvent, boolean greenTerra) {
+        //This check is needed
+        if(isBizarreAgeable(ageable)) {
+            return false;
+        }
 
 
-        blockState.setMetadata(MetadataConstants.GREEN_THUMB_METAKEY, new FixedMetadataValue(pluginRef, (int) (System.currentTimeMillis() / pluginRef.getMiscTools().TIME_CONVERSION_FACTOR)));
-        Ageable crops = (Ageable) blockState.getBlockData();
+        int finalAge = 0;
+        int greenThumbStage = getGreenThumbStage(greenTerra);
+
+        //Immature plants will start over at 0
+        if(!isAgeableMature(ageable)) {
+//            blockBreakEvent.setCancelled(true);
+            startReplantTask(0, blockBreakEvent, blockState, true);
+//            blockState.setType(Material.AIR);
+            blockBreakEvent.setDropItems(false);
+            return true;
+        }
 
 
         switch (blockState.getType()) {
         switch (blockState.getType()) {
 
 
@@ -708,42 +780,47 @@ public class HerbalismManager extends SkillManager {
             case CARROTS:
             case CARROTS:
             case WHEAT:
             case WHEAT:
 
 
-                if (greenTerra) {
-                    crops.setAge(3);
-                } else {
-                    crops.setAge(greenThumbStage);
-                }
+                    finalAge = getGreenThumbStage(greenTerra);
                 break;
                 break;
 
 
             case BEETROOTS:
             case BEETROOTS:
             case NETHER_WART:
             case NETHER_WART:
 
 
                 if (greenTerra || greenThumbStage > 2) {
                 if (greenTerra || greenThumbStage > 2) {
-                    crops.setAge(2);
-                } else if (greenThumbStage == 2) {
-                    crops.setAge(1);
-                } else {
-                    crops.setAge(0);
+                    finalAge = 2;
+                }
+                else if (greenThumbStage == 2) {
+                    finalAge = 1;
+                }
+                else {
+                    finalAge = 0;
                 }
                 }
                 break;
                 break;
 
 
             case COCOA:
             case COCOA:
 
 
-                if (greenTerra || getGreenThumbStage() > 1) {
-                    crops.setAge(1);
-                } else {
-                    crops.setAge(0);
+                if (getGreenThumbStage(greenTerra) >= 2) {
+                    finalAge = 1;
+                }
+                else {
+                    finalAge = 0;
                 }
                 }
                 break;
                 break;
 
 
             default:
             default:
                 return false;
                 return false;
         }
         }
-        blockState.setBlockData(crops);
+
+        //Start the delayed replant
+        startReplantTask(finalAge, blockBreakEvent, blockState, false);
         return true;
         return true;
     }
     }
 
 
-    private int getGreenThumbStage() {
+    private int getGreenThumbStage(boolean greenTerraActive) {
+        if(greenTerraActive)
+            return Math.min(pluginRef.getRankTools().getHighestRank(SubSkillType.HERBALISM_GREEN_THUMB),
+                    pluginRef.getRankTools().getRank(getPlayer(), SubSkillType.HERBALISM_GREEN_THUMB) + 1);
+
         return pluginRef.getRankTools().getRank(getPlayer(), SubSkillType.HERBALISM_GREEN_THUMB);
         return pluginRef.getRankTools().getRank(getPlayer(), SubSkillType.HERBALISM_GREEN_THUMB);
     }
     }
 }
 }

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

@@ -208,7 +208,7 @@ public class TamingManager extends SkillManager {
         if(!pluginRef.getRandomChanceTools().checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(pluginRef, pluginRef.getDynamicSettingsManager().getSkillPropertiesManager().getStaticChance(SubSkillType.TAMING_PUMMEL), getPlayer(), SubSkillType.TAMING_PUMMEL)))
         if(!pluginRef.getRandomChanceTools().checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(pluginRef, pluginRef.getDynamicSettingsManager().getSkillPropertiesManager().getStaticChance(SubSkillType.TAMING_PUMMEL), getPlayer(), SubSkillType.TAMING_PUMMEL)))
             return;
             return;
 
 
-        ParticleEffectUtils.playGreaterImpactEffect(target);
+        pluginRef.getParticleEffectUtils().playGreaterImpactEffect(target);
         target.setVelocity(wolf.getLocation().getDirection().normalize().multiply(1.5D));
         target.setVelocity(wolf.getLocation().getDirection().normalize().multiply(1.5D));
 
 
         if (target instanceof Player) {
         if (target instanceof Player) {
@@ -381,7 +381,7 @@ public class TamingManager extends SkillManager {
         callOfWildEntity.setCustomName(pluginRef.getLocaleManager().getString("Taming.Summon.Name.Format", getPlayer().getName(), StringUtils.getPrettyEntityTypeString(entityType)));
         callOfWildEntity.setCustomName(pluginRef.getLocaleManager().getString("Taming.Summon.Name.Format", getPlayer().getName(), StringUtils.getPrettyEntityTypeString(entityType)));
 
 
         //Particle effect
         //Particle effect
-        ParticleEffectUtils.playCallOfTheWildEffect(callOfWildEntity);
+        pluginRef.getParticleEffectUtils().playCallOfTheWildEffect(callOfWildEntity);
     }
     }
 
 
     private void spawnHorse(Location spawnLocation) {
     private void spawnHorse(Location spawnLocation) {
@@ -408,7 +408,7 @@ public class TamingManager extends SkillManager {
         callOfWildEntity.setCustomName(pluginRef.getLocaleManager().getString("Taming.Summon.Name.Format", getPlayer().getName(), StringUtils.getPrettyEntityTypeString(EntityType.HORSE)));
         callOfWildEntity.setCustomName(pluginRef.getLocaleManager().getString("Taming.Summon.Name.Format", getPlayer().getName(), StringUtils.getPrettyEntityTypeString(EntityType.HORSE)));
 
 
         //Particle effect
         //Particle effect
-        ParticleEffectUtils.playCallOfTheWildEffect(callOfWildEntity);
+        pluginRef.getParticleEffectUtils().playCallOfTheWildEffect(callOfWildEntity);
     }
     }
 
 
     private void setBaseCOTWEntityProperties(LivingEntity callOfWildEntity) {
     private void setBaseCOTWEntityProperties(LivingEntity callOfWildEntity) {

+ 1 - 1
mcmmo-core/src/main/java/com/gmail/nossr50/skills/taming/TrackedTamingEntity.java

@@ -39,7 +39,7 @@ public class TrackedTamingEntity extends BukkitRunnable {
         if (livingEntity.isValid()) {
         if (livingEntity.isValid()) {
             Location location = livingEntity.getLocation();
             Location location = livingEntity.getLocation();
             location.getWorld().playSound(location, Sound.BLOCK_FIRE_EXTINGUISH, 0.8F, 0.8F);
             location.getWorld().playSound(location, Sound.BLOCK_FIRE_EXTINGUISH, 0.8F, 0.8F);
-            ParticleEffectUtils.playCallOfTheWildEffect(livingEntity);
+            pluginRef.getParticleEffectUtils().playCallOfTheWildEffect(livingEntity);
             pluginRef.getCombatTools().dealDamage(livingEntity, livingEntity.getMaxHealth(), EntityDamageEvent.DamageCause.SUICIDE, livingEntity);
             pluginRef.getCombatTools().dealDamage(livingEntity, livingEntity.getMaxHealth(), EntityDamageEvent.DamageCause.SUICIDE, livingEntity);
 
 
             if(tamingManagerRef != null)
             if(tamingManagerRef != null)

+ 9 - 2
mcmmo-core/src/main/java/com/gmail/nossr50/util/player/UserManager.java

@@ -8,6 +8,7 @@ import org.bukkit.OfflinePlayer;
 import org.bukkit.entity.Entity;
 import org.bukkit.entity.Entity;
 import org.bukkit.entity.Player;
 import org.bukkit.entity.Player;
 import org.bukkit.metadata.FixedMetadataValue;
 import org.bukkit.metadata.FixedMetadataValue;
+import org.jetbrains.annotations.Nullable;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collection;
@@ -51,9 +52,11 @@ public final class UserManager {
     public void remove(Player player) {
     public void remove(Player player) {
         McMMOPlayer mcMMOPlayer = getPlayer(player);
         McMMOPlayer mcMMOPlayer = getPlayer(player);
         player.removeMetadata(MetadataConstants.PLAYER_DATA_METAKEY, pluginRef);
         player.removeMetadata(MetadataConstants.PLAYER_DATA_METAKEY, pluginRef);
-        mcMMOPlayer.cleanup();
 
 
-        if(playerDataSet != null && playerDataSet.contains(mcMMOPlayer)) {
+        if(mcMMOPlayer != null)
+            mcMMOPlayer.cleanup();
+
+        if(playerDataSet != null) {
             playerDataSet.remove(mcMMOPlayer); //Clear sync save tracking
             playerDataSet.remove(mcMMOPlayer); //Clear sync save tracking
         }
         }
     }
     }
@@ -114,10 +117,12 @@ public final class UserManager {
      * @param playerName The name of the player whose McMMOPlayer to retrieve
      * @param playerName The name of the player whose McMMOPlayer to retrieve
      * @return the player's McMMOPlayer object
      * @return the player's McMMOPlayer object
      */
      */
+    @Nullable
     public McMMOPlayer getPlayer(String playerName) {
     public McMMOPlayer getPlayer(String playerName) {
         return retrieveMcMMOPlayer(playerName, false);
         return retrieveMcMMOPlayer(playerName, false);
     }
     }
 
 
+    @Nullable
     public McMMOPlayer getOfflinePlayer(OfflinePlayer player) {
     public McMMOPlayer getOfflinePlayer(OfflinePlayer player) {
         if (player instanceof Player) {
         if (player instanceof Player) {
             return getPlayer((Player) player);
             return getPlayer((Player) player);
@@ -136,6 +141,7 @@ public final class UserManager {
      * @param player target player
      * @param player target player
      * @return McMMOPlayer object for this player, null if Player has not been loaded
      * @return McMMOPlayer object for this player, null if Player has not been loaded
      */
      */
+    @Nullable
     public McMMOPlayer getPlayer(Player player) {
     public McMMOPlayer getPlayer(Player player) {
         //Avoid Array Index out of bounds
         //Avoid Array Index out of bounds
         if (player != null && player.hasMetadata(MetadataConstants.PLAYER_DATA_METAKEY))
         if (player != null && player.hasMetadata(MetadataConstants.PLAYER_DATA_METAKEY))
@@ -144,6 +150,7 @@ public final class UserManager {
             return null;
             return null;
     }
     }
 
 
+    @Nullable
     private McMMOPlayer retrieveMcMMOPlayer(String playerName, boolean offlineValid) {
     private McMMOPlayer retrieveMcMMOPlayer(String playerName, boolean offlineValid) {
         Player player = pluginRef.getServer().getPlayerExact(playerName);
         Player player = pluginRef.getServer().getPlayerExact(playerName);
 
 

+ 7 - 8
mcmmo-core/src/main/java/com/gmail/nossr50/util/skills/CombatTools.java

@@ -222,7 +222,7 @@ public final class CombatTools {
 
 
     }
     }
 
 
-    private void processArcheryCombat(LivingEntity target, Player player, EntityDamageByEntityEvent event, Arrow arrow) {
+    private void processArcheryCombat(LivingEntity target, Player player, EntityDamageByEntityEvent event, Projectile arrow) {
         double initialDamage = event.getDamage();
         double initialDamage = event.getDamage();
 
 
         McMMOPlayer mcMMOPlayer = pluginRef.getUserManager().getPlayer(player);
         McMMOPlayer mcMMOPlayer = pluginRef.getUserManager().getPlayer(player);
@@ -376,8 +376,9 @@ public final class CombatTools {
                     processTamingCombat(target, master, wolf, event);
                     processTamingCombat(target, master, wolf, event);
                 }
                 }
             }
             }
-        } else if (entityType == EntityType.ARROW) {
-            Arrow arrow = (Arrow) damager;
+        }
+        else if (entityType == EntityType.ARROW || entityType == EntityType.SPECTRAL_ARROW) {
+            Projectile arrow = (Projectile) damager;
             ProjectileSource projectileSource = arrow.getShooter();
             ProjectileSource projectileSource = arrow.getShooter();
 
 
             if (projectileSource instanceof Player && pluginRef.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.ARCHERY, target)) {
             if (projectileSource instanceof Player && pluginRef.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.ARCHERY, target)) {
@@ -407,11 +408,9 @@ public final class CombatTools {
         if (metadataValue.size() <= 0)
         if (metadataValue.size() <= 0)
             return;
             return;
 
 
-        if (metadataValue != null) {
-            OldName oldName = (OldName) metadataValue.get(0);
-            entity.setCustomName(oldName.asString());
-            entity.setCustomNameVisible(false);
-        }
+        OldName oldName = (OldName) metadataValue.get(0);
+        entity.setCustomName(oldName.asString());
+        entity.setCustomNameVisible(false);
     }
     }
 
 
 
 

+ 22 - 11
mcmmo-core/src/main/java/com/gmail/nossr50/util/skills/ParticleEffectUtils.java

@@ -1,5 +1,8 @@
 package com.gmail.nossr50.util.skills;
 package com.gmail.nossr50.util.skills;
 
 
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.sounds.SoundManager;
+import com.gmail.nossr50.util.sounds.SoundType;
 import org.bukkit.Effect;
 import org.bukkit.Effect;
 import org.bukkit.Location;
 import org.bukkit.Location;
 import org.bukkit.Material;
 import org.bukkit.Material;
@@ -11,10 +14,19 @@ import org.bukkit.entity.Player;
 
 
 public final class ParticleEffectUtils {
 public final class ParticleEffectUtils {
 
 
-    private ParticleEffectUtils() {
+    private final mcMMO pluginRef;
+
+    public ParticleEffectUtils(mcMMO pluginRef) {
+        this.pluginRef = pluginRef;
+    }
+
+    public void playGreenThumbEffect(Location location) {
+        World world = location.getWorld();
+        playSmokeEffect(location);
+        pluginRef.getSoundManager().worldSendSoundMaxPitch(world, location, SoundType.POP);
     }
     }
 
 
-    public static void playBleedEffect(LivingEntity livingEntity) {
+    public void playBleedEffect(LivingEntity livingEntity) {
         /*if (!MainConfig.getInstance().getBleedEffectEnabled()) {
         /*if (!MainConfig.getInstance().getBleedEffectEnabled()) {
             return;
             return;
         }*/
         }*/
@@ -22,15 +34,15 @@ public final class ParticleEffectUtils {
         livingEntity.getWorld().playEffect(livingEntity.getEyeLocation(), Effect.STEP_SOUND, Material.REDSTONE_WIRE);
         livingEntity.getWorld().playEffect(livingEntity.getEyeLocation(), Effect.STEP_SOUND, Material.REDSTONE_WIRE);
     }
     }
 
 
-    public static void playDodgeEffect(Player player) {
+    public void playDodgeEffect(Player player) {
         /*if (!MainConfig.getInstance().getDodgeEffectEnabled()) {
         /*if (!MainConfig.getInstance().getDodgeEffectEnabled()) {
             return;
             return;
         }*/
         }*/
 
 
-        playSmokeEffect(player);
+        playSmokeEffect(player.getLocation());
     }
     }
 
 
-    public static void playFluxEffect(Location location) {
+    public void playFluxEffect(Location location) {
         /*if (!MainConfig.getInstance().getFluxEffectEnabled()) {
         /*if (!MainConfig.getInstance().getFluxEffectEnabled()) {
             return;
             return;
         }*/
         }*/
@@ -38,9 +50,8 @@ public final class ParticleEffectUtils {
         location.getWorld().playEffect(location, Effect.MOBSPAWNER_FLAMES, 1);
         location.getWorld().playEffect(location, Effect.MOBSPAWNER_FLAMES, 1);
     }
     }
 
 
-    public static void playSmokeEffect(LivingEntity livingEntity) {
-        Location location = livingEntity.getEyeLocation();
-        World world = livingEntity.getWorld();
+    public void playSmokeEffect(Location location) {
+        World world = location.getWorld();
 
 
         // Have to do it this way, because not all block directions are valid for smoke
         // Have to do it this way, because not all block directions are valid for smoke
         world.playEffect(location, Effect.SMOKE, BlockFace.SOUTH_EAST);
         world.playEffect(location, Effect.SMOKE, BlockFace.SOUTH_EAST);
@@ -54,7 +65,7 @@ public final class ParticleEffectUtils {
         world.playEffect(location, Effect.SMOKE, BlockFace.NORTH_WEST);
         world.playEffect(location, Effect.SMOKE, BlockFace.NORTH_WEST);
     }
     }
 
 
-    public static void playGreaterImpactEffect(LivingEntity livingEntity) {
+    public void playGreaterImpactEffect(LivingEntity livingEntity) {
         /*if (!MainConfig.getInstance().getGreaterImpactEffectEnabled()) {
         /*if (!MainConfig.getInstance().getGreaterImpactEffectEnabled()) {
             return;
             return;
         }*/
         }*/
@@ -64,7 +75,7 @@ public final class ParticleEffectUtils {
         livingEntity.getWorld().createExplosion(location.getX(), location.getY(), location.getZ(), 0F, false, false);
         livingEntity.getWorld().createExplosion(location.getX(), location.getY(), location.getZ(), 0F, false, false);
     }
     }
 
 
-    public static void playCallOfTheWildEffect(LivingEntity livingEntity) {
+    public void playCallOfTheWildEffect(LivingEntity livingEntity) {
         /*if (!MainConfig.getInstance().getCallOfTheWildEffectEnabled()) {
         /*if (!MainConfig.getInstance().getCallOfTheWildEffectEnabled()) {
             return;
             return;
         }*/
         }*/
@@ -87,7 +98,7 @@ public final class ParticleEffectUtils {
         firework.setFireworkMeta(fireworkMeta);
         firework.setFireworkMeta(fireworkMeta);
     }*/
     }*/
 
 
-    private static boolean hasHeadRoom(Player player) {
+    private boolean hasHeadRoom(Player player) {
         boolean hasHeadRoom = true;
         boolean hasHeadRoom = true;
         Block headBlock = player.getEyeLocation().getBlock();
         Block headBlock = player.getEyeLocation().getBlock();
 
 

+ 7 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/util/sounds/SoundManager.java

@@ -43,6 +43,11 @@ public class SoundManager {
             world.playSound(location, getSound(soundType), getVolume(soundType), getPitch(soundType));
             world.playSound(location, getSound(soundType), getVolume(soundType), getPitch(soundType));
     }
     }
 
 
+    public void worldSendSoundMaxPitch(World world, Location location, SoundType soundType) {
+        if(pluginRef.getConfigManager().getConfigSound().isSoundEnabled(soundType))
+            world.playSound(location, getSound(soundType), getVolume(soundType), 2.0F);
+    }
+
     /**
     /**
      * All volume is multiplied by the master volume to get its final value
      * All volume is multiplied by the master volume to get its final value
      *
      *
@@ -95,6 +100,8 @@ public class SoundManager {
                 return Sound.ENTITY_ENDER_EYE_DEATH;
                 return Sound.ENTITY_ENDER_EYE_DEATH;
             case GLASS:
             case GLASS:
                 return Sound.BLOCK_GLASS_BREAK;
                 return Sound.BLOCK_GLASS_BREAK;
+            case ITEM_CONSUMED:
+                return Sound.ITEM_BOTTLE_EMPTY;
             default:
             default:
                 return null;
                 return null;
         }
         }

+ 1 - 0
mcmmo-core/src/main/java/com/gmail/nossr50/util/sounds/SoundType.java

@@ -15,6 +15,7 @@ public enum SoundType {
     ABILITY_ACTIVATED_BERSERK,
     ABILITY_ACTIVATED_BERSERK,
     BLEED,
     BLEED,
     GLASS,
     GLASS,
+    ITEM_CONSUMED,
     TIRED;
     TIRED;
 
 
     public boolean usesCustomPitch() {
     public boolean usesCustomPitch() {

+ 3 - 3
mcmmo-core/src/main/resources/plugin.yml

@@ -261,7 +261,7 @@ permissions:
         description: Allows access to all Axes abilities
         description: Allows access to all Axes abilities
         children:
         children:
             mcmmo.ability.axes.axemastery: true
             mcmmo.ability.axes.axemastery: true
-            mcmmo.ability.axes.criticalhit: true
+            mcmmo.ability.axes.criticalstrikes: true
             mcmmo.ability.axes.greaterimpact: true
             mcmmo.ability.axes.greaterimpact: true
             mcmmo.ability.axes.armorimpact: true
             mcmmo.ability.axes.armorimpact: true
             mcmmo.ability.axes.skullsplitter: true
             mcmmo.ability.axes.skullsplitter: true
@@ -270,8 +270,8 @@ permissions:
         description: Adds damage to axes
         description: Adds damage to axes
     mcmmo.ability.axes.axemastery:
     mcmmo.ability.axes.axemastery:
         description: Allows bonus damage from Axes
         description: Allows bonus damage from Axes
-    mcmmo.ability.axes.criticalhit:
-        description: Allows access to the Critical Hit ability
+    mcmmo.ability.axes.criticalstrikes:
+        description: Allows access to the Critical Strikes ability
     mcmmo.ability.axes.greaterimpact:
     mcmmo.ability.axes.greaterimpact:
         description: Allows access to the Greater Impact ability
         description: Allows access to the Greater Impact ability
     mcmmo.ability.axes.armorimpact:
     mcmmo.ability.axes.armorimpact:

+ 4 - 0
mcmmo-core/src/main/resources/sounds.yml

@@ -4,6 +4,10 @@ Sounds:
     # 1.0 = Max volume
     # 1.0 = Max volume
     # 0.0 = No Volume
     # 0.0 = No Volume
     MasterVolume: 1.0
     MasterVolume: 1.0
+    ITEM_CONSUMED:
+        Enable: true
+        Volume: 1.0
+        Pitch: 1.0
     GLASS:
     GLASS:
         Enable: true
         Enable: true
         Volume: 1.0
         Volume: 1.0