2
0
Эх сурвалжийг харах

Create a WoodcuttingManager

GJ 12 жил өмнө
parent
commit
44ede5c3f8

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

@@ -34,6 +34,7 @@ import com.gmail.nossr50.skills.smelting.SmeltingManager;
 import com.gmail.nossr50.skills.swords.SwordsManager;
 import com.gmail.nossr50.skills.taming.TamingManager;
 import com.gmail.nossr50.skills.unarmed.UnarmedManager;
+import com.gmail.nossr50.skills.woodcutting.WoodcuttingManager;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.ModUtils;
 import com.gmail.nossr50.util.Permissions;
@@ -94,12 +95,7 @@ public class McMMOPlayer {
          */
         try {
             for (SkillType skillType : SkillType.values()) {
-                Class<? extends SkillManager> skillManagerClass = skillType.getManagerClass();
-
-                // TODO: The null check is needed only because currently some SkillType doesn't have a valid skillManagerClass 
-                if (skillManagerClass != null) {
-                    skillManagers.put(skillType, skillManagerClass.getConstructor(McMMOPlayer.class).newInstance(this));
-                }
+                skillManagers.put(skillType, skillType.getManagerClass().getConstructor(McMMOPlayer.class).newInstance(this));
             }
         }
         catch (Exception e) {
@@ -166,6 +162,10 @@ public class McMMOPlayer {
         return (UnarmedManager) skillManagers.get(SkillType.UNARMED);
     }
 
+    public WoodcuttingManager getWoodcuttingManager() {
+        return (WoodcuttingManager) skillManagers.get(SkillType.WOODCUTTING);
+    }
+
     /*
      * Abilities
      */

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

@@ -16,6 +16,7 @@ import com.gmail.nossr50.skills.smelting.SmeltingManager;
 import com.gmail.nossr50.skills.swords.SwordsManager;
 import com.gmail.nossr50.skills.taming.TamingManager;
 import com.gmail.nossr50.skills.unarmed.UnarmedManager;
+import com.gmail.nossr50.skills.woodcutting.WoodcuttingManager;
 import com.gmail.nossr50.util.StringUtils;
 
 public enum SkillType {
@@ -31,7 +32,7 @@ public enum SkillType {
     SWORDS(SwordsManager.class, AbilityType.SERRATED_STRIKES, ToolType.SWORD),
     TAMING(TamingManager.class),
     UNARMED(UnarmedManager.class, AbilityType.BERSERK, ToolType.FISTS),
-    WOODCUTTING(null, AbilityType.TREE_FELLER, ToolType.AXE); // TODO: Create a proper WoodcuttingManager class
+    WOODCUTTING(WoodcuttingManager.class, AbilityType.TREE_FELLER, ToolType.AXE);
 
     private Class<? extends SkillManager> managerClass;
     private AbilityType ability;

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

@@ -20,8 +20,6 @@ import org.bukkit.event.block.BlockPlaceEvent;
 import org.bukkit.inventory.ItemStack;
 
 import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.config.AdvancedConfig;
-import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.HiddenConfig;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.AbilityType;
@@ -37,7 +35,7 @@ import com.gmail.nossr50.skills.mining.MiningManager;
 import com.gmail.nossr50.skills.repair.Repair;
 import com.gmail.nossr50.skills.smelting.SmeltingManager;
 import com.gmail.nossr50.skills.unarmed.Unarmed;
-import com.gmail.nossr50.skills.woodcutting.Woodcutting;
+import com.gmail.nossr50.skills.woodcutting.WoodcuttingManager;
 import com.gmail.nossr50.util.BlockUtils;
 import com.gmail.nossr50.util.ItemUtils;
 import com.gmail.nossr50.util.Misc;
@@ -179,18 +177,13 @@ public class BlockListener implements Listener {
 
         /* WOOD CUTTING */
         else if (BlockUtils.isLog(blockState) && Permissions.skillEnabled(player, SkillType.WOODCUTTING) && !mcMMO.placeStore.isTrue(blockState)) {
-            if (mcMMOPlayer.getAbilityMode(AbilityType.TREE_FELLER) && Permissions.treeFeller(player) && ItemUtils.isAxe(heldItem)) {
-                Woodcutting.beginTreeFeller(blockState, player);
+            WoodcuttingManager woodcuttingManager = mcMMOPlayer.getWoodcuttingManager();
+
+            if (woodcuttingManager.canUseTreeFeller(heldItem)) {
+                woodcuttingManager.processTreeFeller(blockState);
             }
             else {
-                if (Config.getInstance().getWoodcuttingRequiresTool()) {
-                    if (ItemUtils.isAxe(heldItem)) {
-                        Woodcutting.beginWoodcutting(player, blockState);
-                    }
-                }
-                else {
-                    Woodcutting.beginWoodcutting(player, blockState);
-                }
+                woodcuttingManager.woodcuttingBlockCheck(blockState);
             }
         }
 
@@ -367,18 +360,10 @@ public class BlockListener implements Listener {
                 }
             }
         }
-        else if ((mcMMOPlayer.getProfile().getSkillLevel(SkillType.WOODCUTTING) >= AdvancedConfig.getInstance().getLeafBlowUnlockLevel()) && BlockUtils.isLeaves(blockState)) {
-            if (SkillUtils.triggerCheck(player, block, AbilityType.LEAF_BLOWER)) {
-                if (Config.getInstance().getWoodcuttingRequiresTool()) {
-                    if (ItemUtils.isAxe(heldItem)) {
-                        event.setInstaBreak(true);
-                        Woodcutting.beginLeafBlower(player, blockState);
-                    }
-                }
-                else if (!(heldItem.getType() == Material.SHEARS)) {
-                    event.setInstaBreak(true);
-                    Woodcutting.beginLeafBlower(player, blockState);
-                }
+        else if (BlockUtils.isLeaves(blockState)) {
+            if (UserManager.getPlayer(player).getWoodcuttingManager().canUseLeafBlower(heldItem) && SkillUtils.blockBreakSimulate(block, player, true)) {
+                event.setInstaBreak(true);
+                player.playSound(blockState.getLocation(), Sound.ITEM_PICKUP, Misc.POP_VOLUME, Misc.POP_PITCH);
             }
         }
     }

+ 0 - 276
src/main/java/com/gmail/nossr50/skills/woodcutting/TreeFeller.java

@@ -1,276 +0,0 @@
-package com.gmail.nossr50.skills.woodcutting;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.bukkit.Location;
-import org.bukkit.Material;
-import org.bukkit.World;
-import org.bukkit.block.BlockState;
-import org.bukkit.enchantments.Enchantment;
-import org.bukkit.entity.Player;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.material.Tree;
-
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.config.Config;
-import com.gmail.nossr50.datatypes.mods.CustomBlock;
-import com.gmail.nossr50.datatypes.skills.SkillType;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.woodcutting.Woodcutting.ExperienceGainMethod;
-import com.gmail.nossr50.util.BlockUtils;
-import com.gmail.nossr50.util.Misc;
-import com.gmail.nossr50.util.ModUtils;
-import com.gmail.nossr50.util.player.UserManager;
-import com.gmail.nossr50.util.skills.CombatUtils;
-import com.gmail.nossr50.util.skills.SkillUtils;
-
-public final class TreeFeller {
-    private static boolean treeFellerReachedThreshold = false;
-
-    private TreeFeller() {}
-
-    /**
-     * Begins Tree Feller
-     *
-     * @param mcMMOPlayer Player using Tree Feller
-     * @param blockState Block being broken
-     */
-    protected static void processTreeFeller(BlockState blockState, Player player) {
-        List<BlockState> treeFellerBlocks = new ArrayList<BlockState>();
-
-        if (blockState.getTypeId() == 17 || blockState.getTypeId() == 99) {
-            processRegularTrees(blockState, treeFellerBlocks);
-        }
-        else {
-            processRedMushroomTrees(blockState, treeFellerBlocks);
-        }
-
-        // If the player is trying to break too many blocks
-        if (treeFellerReachedThreshold) {
-            treeFellerReachedThreshold = false;
-
-            player.sendMessage(LocaleLoader.getString("Woodcutting.Skills.TreeFellerThreshold"));
-            return;
-        }
-
-        // If the tool can't sustain the durability loss
-        if (!handleDurabilityLoss(treeFellerBlocks, player.getItemInHand())) {
-            player.sendMessage(LocaleLoader.getString("Woodcutting.Skills.TreeFeller.Splinter"));
-
-            int health = player.getHealth();
-
-            if (health > 1) {
-                CombatUtils.dealDamage(player, Misc.getRandom().nextInt(health - 1));
-            }
-
-            return;
-        }
-
-        dropBlocks(treeFellerBlocks, player);
-    }
-
-    /**
-     * Processes Tree Feller for generic Trees
-     *
-     * @param blockState Block being checked
-     * @param treeFellerBlocks List of blocks to be removed
-     */
-    private static void processRegularTrees(BlockState blockState, List<BlockState> treeFellerBlocks) {
-        if (!BlockUtils.isLog(blockState)) {
-            return;
-        }
-
-        List<BlockState> futureCenterBlocks = new ArrayList<BlockState>();
-        World world = blockState.getWorld();
-
-        // Handle the blocks around 'block'
-        for (int y = 0; y <= 1; y++) {
-            for (int x = -1; x <= 1; x++) {
-                for (int z = -1; z <= 1; z++) {
-                    BlockState nextBlock = world.getBlockAt(blockState.getLocation().add(x, y, z)).getState();
-
-                    handleBlock(nextBlock, futureCenterBlocks, treeFellerBlocks);
-
-                    if (treeFellerReachedThreshold) {
-                        return;
-                    }
-                }
-            }
-        }
-
-        // Recursive call for each log found
-        for (BlockState futureCenterBlock : futureCenterBlocks) {
-            if (treeFellerReachedThreshold) {
-                return;
-            }
-
-            processRegularTrees(futureCenterBlock, treeFellerBlocks);
-        }
-    }
-
-    /**
-     * Processes Tree Feller for Red Mushrooms (Dome Shaped)
-     *
-     * @param blockState Block being checked
-     * @param treeFellerBlocks List of blocks to be removed
-     */
-    private static void processRedMushroomTrees(BlockState blockState, List<BlockState> treeFellerBlocks) {
-        if (!BlockUtils.isLog(blockState)) {
-            return;
-        }
-
-        List<BlockState> futureCenterBlocks = new ArrayList<BlockState>();
-        World world = blockState.getWorld();
-
-        // Handle the blocks around 'block'
-        for (int y = 0; y <= 1; y++) {
-            for (int x = -1; x <= 1; x++) {
-                for (int z = -1; z <= 1; z++) {
-                    BlockState nextBlock = world.getBlockAt(blockState.getLocation().add(x, y, z)).getState();
-                    BlockState otherNextBlock = world.getBlockAt(blockState.getLocation().add(x, y - (y * 2), z)).getState();
-
-                    handleBlock(nextBlock, futureCenterBlocks, treeFellerBlocks);
-                    handleBlock(otherNextBlock, futureCenterBlocks, treeFellerBlocks);
-
-                    if (treeFellerReachedThreshold) {
-                        return;
-                    }
-                }
-            }
-        }
-
-        // Recursive call for each log found
-        for (BlockState futureCenterBlock : futureCenterBlocks) {
-            if (treeFellerReachedThreshold) {
-                return;
-            }
-
-            processRedMushroomTrees(futureCenterBlock, treeFellerBlocks);
-        }
-    }
-
-    /**
-     * Handle a block addition to the list of blocks to be removed and to the list of blocks used for future recursive calls of 'processRecursively()'
-     *
-     * @param blockState Block to be added
-     * @param futureCenterBlocks List of blocks that will be used to call 'processRecursively()'
-     * @param treeFellerBlocks List of blocks to be removed
-     */
-    private static void handleBlock(BlockState blockState, List<BlockState> futureCenterBlocks, List<BlockState> treeFellerBlocks) {
-        if (!BlockUtils.affectedByTreeFeller(blockState) || mcMMO.placeStore.isTrue(blockState) || treeFellerBlocks.contains(blockState)) {
-            return;
-        }
-
-        treeFellerBlocks.add(blockState);
-
-        if (treeFellerBlocks.size() > Config.getInstance().getTreeFellerThreshold()) {
-            treeFellerReachedThreshold = true;
-            return;
-        }
-
-        futureCenterBlocks.add(blockState);
-    }
-
-    /**
-     * Handles the durability loss
-     *
-     * @param treeFellerBlocks List of blocks to be removed
-     * @param inHand tool being used
-     * @return True if the tool can sustain the durability loss
-     */
-    private static boolean handleDurabilityLoss(List<BlockState> treeFellerBlocks, ItemStack inHand) {
-        Material inHandMaterial = inHand.getType();
-
-        if (inHandMaterial != Material.AIR) {
-            short durabilityLoss = 0;
-            int unbreakingLevel = inHand.getEnchantmentLevel(Enchantment.DURABILITY);
-
-            for (BlockState blockState : treeFellerBlocks) {
-                if (BlockUtils.isLog(blockState) && Misc.getRandom().nextInt(unbreakingLevel + 1) == 0) {
-                    durabilityLoss += Config.getInstance().getAbilityToolDamage();
-                }
-            }
-
-            short finalDurability = (short) (inHand.getDurability() + durabilityLoss);
-            short maxDurability = ModUtils.isCustomTool(inHand) ? ModUtils.getToolFromItemStack(inHand).getDurability() : inHandMaterial.getMaxDurability();
-
-            if (finalDurability >= maxDurability) {
-                inHand.setDurability(maxDurability);
-                return false;
-            }
-
-            inHand.setDurability(finalDurability);
-        }
-
-        return true;
-    }
-
-    /**
-     * Handles the dropping of blocks
-     *
-     * @param treeFellerBlocks List of blocks to be dropped
-     * @param player Player using the ability
-     */
-    private static void dropBlocks(List<BlockState> treeFellerBlocks, Player player) {
-        int xp = 0;
-
-        for (BlockState blockState : treeFellerBlocks) {
-            if (!SkillUtils.blockBreakSimulate(blockState.getBlock(), player, true)) {
-                break; // TODO: Shouldn't we use continue instead?
-            }
-
-            Material material = blockState.getType();
-
-            if (material == Material.HUGE_MUSHROOM_1 || material == Material.HUGE_MUSHROOM_2) {
-                xp += Woodcutting.getExperienceFromLog(blockState, ExperienceGainMethod.TREE_FELLER);
-
-                for (ItemStack drop : blockState.getBlock().getDrops()) {
-                    Misc.dropItem(blockState.getLocation(), drop);
-                }
-            }
-            else if (ModUtils.isCustomLogBlock(blockState)) {
-                Woodcutting.checkForDoubleDrop(player, blockState);
-
-                CustomBlock customBlock = ModUtils.getCustomBlock(blockState);
-                xp = customBlock.getXpGain();
-                int minimumDropAmount = customBlock.getMinimumDropAmount();
-                int maximumDropAmount = customBlock.getMaximumDropAmount();
-                Location location = blockState.getLocation();
-                ItemStack item = customBlock.getItemDrop();;
-
-                Misc.dropItems(location, item, minimumDropAmount);
-
-                if (minimumDropAmount < maximumDropAmount) {
-                    Misc.randomDropItems(location, item, maximumDropAmount - minimumDropAmount);
-                }
-            }
-            else if (ModUtils.isCustomLeafBlock(blockState)) {
-                Misc.randomDropItem(blockState.getLocation(), ModUtils.getCustomBlock(blockState).getItemDrop(), 10);
-            }
-            else {
-                Tree tree = (Tree) blockState.getData();
-                switch (material) {
-                    case LOG:
-                        Woodcutting.checkForDoubleDrop(player, blockState);
-                        xp += Woodcutting.getExperienceFromLog(blockState, ExperienceGainMethod.TREE_FELLER);
-                        Misc.dropItem(blockState.getLocation(), new ItemStack(Material.LOG, 1, tree.getSpecies().getData()));
-                        break;
-
-                    case LEAVES:
-                        Misc.randomDropItem(blockState.getLocation(), new ItemStack(Material.SAPLING, 1, tree.getSpecies().getData()), 10);
-                        break;
-
-                    default:
-                        break;
-                }
-            }
-
-            blockState.setRawData((byte) 0x0);
-            blockState.setType(Material.AIR);
-            blockState.update(true);
-        }
-
-        UserManager.getPlayer(player).beginXpGain(SkillType.WOODCUTTING, xp);
-    }
-}

+ 134 - 53
src/main/java/com/gmail/nossr50/skills/woodcutting/Woodcutting.java

@@ -1,10 +1,12 @@
 package com.gmail.nossr50.skills.woodcutting;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.bukkit.Location;
 import org.bukkit.Material;
-import org.bukkit.Sound;
 import org.bukkit.block.BlockState;
-import org.bukkit.entity.Player;
+import org.bukkit.enchantments.Enchantment;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.material.Tree;
 
@@ -12,18 +14,17 @@ import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.datatypes.mods.CustomBlock;
-import com.gmail.nossr50.datatypes.skills.SkillType;
-import com.gmail.nossr50.events.fake.FakePlayerAnimationEvent;
+import com.gmail.nossr50.util.BlockUtils;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.ModUtils;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.player.UserManager;
-import com.gmail.nossr50.util.skills.SkillUtils;
 
 public final class Woodcutting {
     public static int    doubleDropsMaxLevel  = AdvancedConfig.getInstance().getWoodcuttingDoubleDropMaxLevel();
     public static double doubleDropsMaxChance = AdvancedConfig.getInstance().getWoodcuttingDoubleDropChance();
 
+    public static int leafBlowerUnlockLevel = AdvancedConfig.getInstance().getLeafBlowUnlockLevel();
+    public static int treeFellerThreshold = Config.getInstance().getTreeFellerThreshold();
+
     protected enum ExperienceGainMethod {
         DEFAULT,
         TREE_FELLER,
@@ -31,47 +32,6 @@ public final class Woodcutting {
 
     private Woodcutting() {}
 
-    /**
-     * Begins the Tree Feller ability
-     *
-     * @param mcMMOPlayer Player using the ability
-     * @param block Block being broken
-     */
-    public static void beginTreeFeller(BlockState blockState, Player player) {
-        TreeFeller.processTreeFeller(blockState, player);
-    }
-
-    /**
-     * Begins the Leaf Blower ability
-     *
-     * @param player Player using the ability
-     * @param block Block being broken
-     */
-    public static void beginLeafBlower(Player player, BlockState blockState) {
-        mcMMO.p.getServer().getPluginManager().callEvent(new FakePlayerAnimationEvent(player));
-        player.playSound(blockState.getLocation(), Sound.ITEM_PICKUP, Misc.POP_VOLUME, Misc.POP_PITCH);
-    }
-
-    /**
-     * Begins Woodcutting
-     *
-     * @param mcMMOPlayer Player breaking the block
-     * @param block Block being broken
-     */
-    public static void beginWoodcutting(Player player, BlockState blockState) {
-        int xp = getExperienceFromLog(blockState, ExperienceGainMethod.DEFAULT);
-
-        if (Permissions.doubleDrops(player, SkillType.WOODCUTTING)) {
-            Material blockType = blockState.getType();
-
-            if (blockType != Material.HUGE_MUSHROOM_1 && blockType != Material.HUGE_MUSHROOM_2) {
-                checkForDoubleDrop(player, blockState);
-            }
-        }
-
-        UserManager.getPlayer(player).beginXpGain(SkillType.WOODCUTTING, xp);
-    }
-
     /**
      * Retrieves the experience reward from a log
      *
@@ -126,11 +86,7 @@ public final class Woodcutting {
      * @param mcMMOPlayer Player breaking the block
      * @param blockState Block being broken
      */
-    protected static void checkForDoubleDrop(Player player, BlockState blockState) {
-        if (!SkillUtils.activationSuccessful(player, SkillType.WOODCUTTING, doubleDropsMaxChance, doubleDropsMaxLevel)) {
-            return;
-        }
-
+    protected static void checkForDoubleDrop(BlockState blockState) {
         if (ModUtils.isCustomLogBlock(blockState)) {
             CustomBlock customBlock = ModUtils.getCustomBlock(blockState);
             int minimumDropAmount = customBlock.getMinimumDropAmount();
@@ -179,4 +135,129 @@ public final class Woodcutting {
             }
         }
     }
+
+    /**
+     * Processes Tree Feller for generic Trees
+     *
+     * @param blockState Block being checked
+     * @param treeFellerBlocks List of blocks to be removed
+     */
+    protected static void processRegularTrees(BlockState blockState, List<BlockState> treeFellerBlocks) {
+        List<BlockState> futureCenterBlocks = new ArrayList<BlockState>();
+
+        // Handle the blocks around 'block'
+        for (int y = 0; y <= 1; y++) {
+            for (int x = -1; x <= 1; x++) {
+                for (int z = -1; z <= 1; z++) {
+                    BlockState nextBlock = blockState.getBlock().getRelative(x, y, z).getState();
+                    handleBlock(nextBlock, futureCenterBlocks, treeFellerBlocks);
+
+                    if (WoodcuttingManager.treeFellerReachedThreshold) {
+                        return;
+                    }
+                }
+            }
+        }
+
+        // Recursive call for each log found
+        for (BlockState futureCenterBlock : futureCenterBlocks) {
+            if (WoodcuttingManager.treeFellerReachedThreshold) {
+                return;
+            }
+
+            processRegularTrees(futureCenterBlock, treeFellerBlocks);
+        }
+    }
+
+    /**
+     * Processes Tree Feller for Red Mushrooms (Dome Shaped)
+     *
+     * @param blockState Block being checked
+     * @param treeFellerBlocks List of blocks to be removed
+     */
+    protected static void processRedMushroomTrees(BlockState blockState, List<BlockState> treeFellerBlocks) {
+        List<BlockState> futureCenterBlocks = new ArrayList<BlockState>();
+
+        // Handle the blocks around 'block'
+        for (int y = 0; y <= 1; y++) {
+            for (int x = -1; x <= 1; x++) {
+                for (int z = -1; z <= 1; z++) {
+                    BlockState nextBlock = blockState.getBlock().getRelative(x, y, z).getState();
+                    BlockState otherNextBlock = blockState.getBlock().getRelative(x, y - (y * 2), z).getState();
+
+                    handleBlock(nextBlock, futureCenterBlocks, treeFellerBlocks);
+                    handleBlock(otherNextBlock, futureCenterBlocks, treeFellerBlocks);
+
+                    if (WoodcuttingManager.treeFellerReachedThreshold) {
+                        return;
+                    }
+                }
+            }
+        }
+
+        // Recursive call for each log found
+        for (BlockState futureCenterBlock : futureCenterBlocks) {
+            if (WoodcuttingManager.treeFellerReachedThreshold) {
+                return;
+            }
+
+            processRedMushroomTrees(futureCenterBlock, treeFellerBlocks);
+        }
+    }
+
+    /**
+     * Handles the durability loss
+     *
+     * @param treeFellerBlocks List of blocks to be removed
+     * @param inHand tool being used
+     * @return True if the tool can sustain the durability loss
+     */
+    protected static boolean handleDurabilityLoss(List<BlockState> treeFellerBlocks, ItemStack inHand) {
+        Material inHandMaterial = inHand.getType();
+
+        if (inHandMaterial == Material.AIR) {
+            return false;
+        }
+
+        short durabilityLoss = 0;
+        int unbreakingLevel = inHand.getEnchantmentLevel(Enchantment.DURABILITY);
+
+        for (BlockState blockState : treeFellerBlocks) {
+            if (BlockUtils.isLog(blockState) && Misc.getRandom().nextInt(unbreakingLevel + 1) == 0) {
+                durabilityLoss += Config.getInstance().getAbilityToolDamage();
+            }
+        }
+
+        short finalDurability = (short) (inHand.getDurability() + durabilityLoss);
+        short maxDurability = ModUtils.isCustomTool(inHand) ? ModUtils.getToolFromItemStack(inHand).getDurability() : inHandMaterial.getMaxDurability();
+
+        if (finalDurability >= maxDurability) {
+            return false;
+        }
+
+        inHand.setDurability(finalDurability);
+        return true;
+    }
+
+    /**
+     * Handle a block addition to the list of blocks to be removed and to the list of blocks used for future recursive calls of 'processRecursively()'
+     *
+     * @param blockState Block to be added
+     * @param futureCenterBlocks List of blocks that will be used to call 'processRecursively()'
+     * @param treeFellerBlocks List of blocks to be removed
+     */
+    private static void handleBlock(BlockState blockState, List<BlockState> futureCenterBlocks, List<BlockState> treeFellerBlocks) {
+        if (!BlockUtils.affectedByTreeFeller(blockState) || mcMMO.placeStore.isTrue(blockState) || treeFellerBlocks.contains(blockState)) {
+            return;
+        }
+
+        treeFellerBlocks.add(blockState);
+
+        if (treeFellerBlocks.size() > treeFellerThreshold) {
+            WoodcuttingManager.treeFellerReachedThreshold = true;
+            return;
+        }
+
+        futureCenterBlocks.add(blockState);
+    }
 }

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

@@ -0,0 +1,192 @@
+package com.gmail.nossr50.skills.woodcutting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.block.BlockState;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.material.Tree;
+
+import com.gmail.nossr50.datatypes.mods.CustomBlock;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.skills.AbilityType;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.skills.SkillManager;
+import com.gmail.nossr50.skills.woodcutting.Woodcutting.ExperienceGainMethod;
+import com.gmail.nossr50.util.ItemUtils;
+import com.gmail.nossr50.util.Misc;
+import com.gmail.nossr50.util.ModUtils;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.skills.CombatUtils;
+import com.gmail.nossr50.util.skills.SkillUtils;
+
+public class WoodcuttingManager extends SkillManager {
+    protected static boolean treeFellerReachedThreshold = false;
+
+    public WoodcuttingManager(McMMOPlayer mcMMOPlayer) {
+        super(mcMMOPlayer, SkillType.WOODCUTTING);
+    }
+
+    public boolean canUseLeafBlower(ItemStack heldItem) {
+        return getSkillLevel() >= Woodcutting.leafBlowerUnlockLevel && ItemUtils.isAxe(heldItem);
+    }
+
+    public boolean canUseTreeFeller(ItemStack heldItem) {
+        return mcMMOPlayer.getAbilityMode(AbilityType.TREE_FELLER) && Permissions.treeFeller(getPlayer()) && ItemUtils.isAxe(heldItem);
+    }
+
+    protected boolean canGetDoubleDrops() {
+        return Permissions.doubleDrops(getPlayer(), skill) && SkillUtils.activationSuccessful(getSkillLevel(), getActivationChance(), Woodcutting.doubleDropsMaxChance, Woodcutting.doubleDropsMaxLevel);
+    }
+
+    /**
+     * Begins Woodcutting
+     *
+     * @param blockState Block being broken
+     */
+    public void woodcuttingBlockCheck(BlockState blockState) {
+        int xp = Woodcutting.getExperienceFromLog(blockState, ExperienceGainMethod.DEFAULT);
+
+        switch (blockState.getType()) {
+            case HUGE_MUSHROOM_1:
+            case HUGE_MUSHROOM_2:
+                break;
+
+            default:
+                if (canGetDoubleDrops()) {
+                    Woodcutting.checkForDoubleDrop(blockState);
+                }
+        }
+
+        applyXpGain(xp);
+    }
+
+    /**
+     * Begins Tree Feller
+     *
+     * @param blockState Block being broken
+     */
+    public void processTreeFeller(BlockState blockState) {
+        Player player = getPlayer();
+        List<BlockState> treeFellerBlocks = new ArrayList<BlockState>();
+
+        switch (blockState.getType()) {
+            case LOG:
+            case HUGE_MUSHROOM_1:
+                Woodcutting.processRegularTrees(blockState, treeFellerBlocks);
+                break;
+
+            case HUGE_MUSHROOM_2:
+                Woodcutting.processRedMushroomTrees(blockState, treeFellerBlocks);
+                break;
+
+            default:
+                if (ModUtils.isCustomLogBlock(blockState)) {
+                    Woodcutting.processRegularTrees(blockState, treeFellerBlocks);
+                }
+                break;
+        }
+
+        // If the player is trying to break too many blocks
+        if (treeFellerReachedThreshold) {
+            treeFellerReachedThreshold = false;
+
+            player.sendMessage(LocaleLoader.getString("Woodcutting.Skills.TreeFellerThreshold"));
+            return;
+        }
+
+        // If the tool can't sustain the durability loss
+        if (!Woodcutting.handleDurabilityLoss(treeFellerBlocks, player.getItemInHand())) {
+            player.sendMessage(LocaleLoader.getString("Woodcutting.Skills.TreeFeller.Splinter"));
+
+            int health = player.getHealth();
+
+            if (health > 1) {
+                CombatUtils.dealDamage(player, Misc.getRandom().nextInt(health - 1));
+            }
+
+            return;
+        }
+
+        dropBlocks(treeFellerBlocks);
+        treeFellerReachedThreshold = false; // Reset the value after we're done with Tree Feller each time.
+    }
+
+    /**
+     * Handles the dropping of blocks
+     *
+     * @param treeFellerBlocks List of blocks to be dropped
+     * @param player Player using the ability
+     */
+    private void dropBlocks(List<BlockState> treeFellerBlocks) {
+        Player player = getPlayer();
+        int xp = 0;
+
+        for (BlockState blockState : treeFellerBlocks) {
+            if (!SkillUtils.blockBreakSimulate(blockState.getBlock(), player, true)) {
+                break; // TODO: Shouldn't we use continue instead?
+            }
+
+            Material material = blockState.getType();
+
+            if (material == Material.HUGE_MUSHROOM_1 || material == Material.HUGE_MUSHROOM_2) {
+                xp += Woodcutting.getExperienceFromLog(blockState, ExperienceGainMethod.TREE_FELLER);
+
+                for (ItemStack drop : blockState.getBlock().getDrops()) {
+                    Misc.dropItem(blockState.getLocation(), drop);
+                }
+            }
+            else if (ModUtils.isCustomLogBlock(blockState)) {
+                if (canGetDoubleDrops()) {
+                    Woodcutting.checkForDoubleDrop(blockState);
+                }
+
+                CustomBlock customBlock = ModUtils.getCustomBlock(blockState);
+                xp = customBlock.getXpGain();
+                int minimumDropAmount = customBlock.getMinimumDropAmount();
+                int maximumDropAmount = customBlock.getMaximumDropAmount();
+                Location location = blockState.getLocation();
+                ItemStack item = customBlock.getItemDrop();;
+
+                Misc.dropItems(location, item, minimumDropAmount);
+
+                if (minimumDropAmount < maximumDropAmount) {
+                    Misc.randomDropItems(location, item, maximumDropAmount - minimumDropAmount);
+                }
+            }
+            else if (ModUtils.isCustomLeafBlock(blockState)) {
+                Misc.randomDropItem(blockState.getLocation(), ModUtils.getCustomBlock(blockState).getItemDrop(), 10);
+            }
+            else {
+                Tree tree = (Tree) blockState.getData();
+                switch (material) {
+                    case LOG:
+                        if (canGetDoubleDrops()) {
+                            Woodcutting.checkForDoubleDrop(blockState);
+                        }
+                        xp += Woodcutting.getExperienceFromLog(blockState, ExperienceGainMethod.TREE_FELLER);
+                        Misc.dropItem(blockState.getLocation(), new ItemStack(Material.LOG, 1, tree.getSpecies().getData()));
+                        break;
+
+                    case LEAVES:
+                        Misc.randomDropItem(blockState.getLocation(), new ItemStack(Material.SAPLING, 1, tree.getSpecies().getData()), 10);
+                        break;
+
+                    default:
+                        break;
+                }
+            }
+
+            blockState.setRawData((byte) 0x0);
+            blockState.setType(Material.AIR);
+            blockState.update(true);
+        }
+
+        applyXpGain(xp);
+    }
+
+}