Procházet zdrojové kódy

Wire up woodcutting behaviour, adding missing dependency injection in a
few places

nossr50 před 6 roky
rodič
revize
900d3bb7fd

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

@@ -271,7 +271,7 @@ public class McMMOPlayer {
     public void resetAbilityMode() {
         for (SuperAbilityType ability : SuperAbilityType.values()) {
             // Correctly disable and handle any special deactivate code
-            new AbilityDisableTask(this, ability).run();
+            new AbilityDisableTask(pluginRef,this, ability).run();
         }
     }
 
@@ -889,7 +889,7 @@ public class McMMOPlayer {
         }
 
         setToolPreparationMode(tool, false);
-        new AbilityDisableTask(this, ability).runTaskLater(pluginRef, abilityLength * Misc.TICK_CONVERSION_FACTOR);
+        new AbilityDisableTask(pluginRef,   this, ability).runTaskLater(pluginRef, abilityLength * Misc.TICK_CONVERSION_FACTOR);
     }
 
     public void processAbilityActivation(PrimarySkillType skill) {
@@ -936,7 +936,7 @@ public class McMMOPlayer {
             }
 
             setToolPreparationMode(tool, true);
-            new ToolLowerTask(this, tool).runTaskLater(pluginRef, 4 * Misc.TICK_CONVERSION_FACTOR);
+            new ToolLowerTask(pluginRef,this, tool).runTaskLater(pluginRef, 4 * Misc.TICK_CONVERSION_FACTOR);
         }
     }
 

+ 72 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/behaviours/WoodcuttingBehaviour.java

@@ -1,6 +1,8 @@
 package com.gmail.nossr50.datatypes.skills.behaviours;
 
 import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.Misc;
+import org.bukkit.block.BlockState;
 
 /**
  * These behaviour classes are a band-aid fix for a larger problem
@@ -13,8 +15,78 @@ public class WoodcuttingBehaviour {
 
     private final mcMMO pluginRef;
 
+    /**
+     * The x/y differences to the blocks in a flat cylinder around the center
+     * block, which is excluded.
+     */
+    private final int[][] directions = {
+            new int[]{-2, -1}, new int[]{-2, 0}, new int[]{-2, 1},
+            new int[]{-1, -2}, new int[]{-1, -1}, new int[]{-1, 0}, new int[]{-1, 1}, new int[]{-1, 2},
+            new int[]{0, -2}, new int[]{0, -1}, new int[]{0, 1}, new int[]{0, 2},
+            new int[]{1, -2}, new int[]{1, -1}, new int[]{1, 0}, new int[]{1, 1}, new int[]{1, 2},
+            new int[]{2, -1}, new int[]{2, 0}, new int[]{2, 1},
+    };
+
     public WoodcuttingBehaviour(mcMMO pluginRef) {
         this.pluginRef = pluginRef;
+
+    }
+
+    public int[][] getDirections() {
+        return directions;
+    }
+
+    /**
+     * Retrieves the experience reward from a log
+     *
+     * @param blockState Log being broken
+     * @return Amount of experience
+     */
+    public int getExperienceFromLog(BlockState blockState) {
+        return pluginRef.getDynamicSettingsManager().getExperienceManager().getWoodcuttingXp(blockState.getType());
+    }
+
+    /**
+     * Retrieves the experience reward from logging via Tree Feller
+     * Experience is reduced per log processed so far
+     * Experience is only reduced if the config option to reduce Tree Feller XP is set
+     * Experience per log will not fall below 1 unless the experience for that log is set to 0 in the config
+     *
+     * @param blockState Log being broken
+     * @param woodCount how many logs have given out XP for this tree feller so far
+     * @return Amount of experience
+     */
+    public int processTreeFellerXPGains(BlockState blockState, int woodCount) {
+        int rawXP = pluginRef.getDynamicSettingsManager().getExperienceManager().getWoodcuttingXp(blockState.getType());
+
+        if(rawXP <= 0)
+            return 0;
+
+        if(pluginRef.getConfigManager().getConfigExperience().getExperienceWoodcutting().isReduceTreeFellerXP()) {
+            int reducedXP = 1 + (woodCount * 5);
+            rawXP = Math.max(1, rawXP - reducedXP);
+            return rawXP;
+        } else {
+            return pluginRef.getDynamicSettingsManager().getExperienceManager().getWoodcuttingXp(blockState.getType());
+        }
     }
 
+    /**
+     * Checks for double drops
+     *
+     * @param blockState Block being broken
+     */
+    public void checkForDoubleDrop(BlockState blockState) {
+        /*if (mcMMO.getModManager().isCustomLog(blockState) && mcMMO.getModManager().getBlock(blockState).isDoubleDropEnabled()) {
+            Misc.dropItems(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops());
+        }
+        else {*/
+        if (pluginRef.getDynamicSettingsManager().getBonusDropManager().isBonusDropWhitelisted(blockState.getType())) {
+            Misc.dropItems(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops());
+        }
+        //}
+    }
+
+
+
 }

+ 3 - 3
src/main/java/com/gmail/nossr50/mcMMO.java

@@ -575,7 +575,7 @@ public class mcMMO extends JavaPlugin {
 
             //TODO: Should do this differently
             if (configManager.getConfigCoreSkills().isRollEnabled()) {
-                InteractionManager.registerSubSkill(new Roll());
+                InteractionManager.registerSubSkill(new Roll(this));
             }
         }
     }
@@ -594,14 +594,14 @@ public class mcMMO extends JavaPlugin {
     private void scheduleTasks() {
         // Periodic save timer (Saves every 10 minutes by default)
         long saveIntervalTicks = Math.max(1200, (getConfigManager().getConfigDatabase().getConfigSectionDatabaseGeneral().getSaveIntervalMinutes() * (20 * 60)));
-        new SaveTimerTask().runTaskTimer(this, saveIntervalTicks, saveIntervalTicks);
+        new SaveTimerTask(this).runTaskTimer(this, saveIntervalTicks, saveIntervalTicks);
 
         // Cleanup the backups folder
         new CleanBackupFilesTask(this).runTaskAsynchronously(this);
 
         // Bleed timer (Runs every 0.5 seconds)
         bleedTimerTask = new BleedTimerTask(this);
-        pluginRef.getBleedTimerTask().runTaskTimer(this, Misc.TICK_CONVERSION_FACTOR, (Misc.TICK_CONVERSION_FACTOR / 2));
+        bleedTimerTask.runTaskTimer(this, Misc.TICK_CONVERSION_FACTOR, (Misc.TICK_CONVERSION_FACTOR / 2));
 
         // Old & Powerless User remover
         long purgeIntervalTicks = getConfigManager().getConfigDatabase().getConfigSectionCleaning().getPurgeInterval() * 60L * 60L * Misc.TICK_CONVERSION_FACTOR;

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

@@ -11,7 +11,6 @@ import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.random.RandomChanceUtil;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.skills.SkillActivationType;
-import org.bukkit.block.BlockState;
 import org.bukkit.event.inventory.FurnaceBurnEvent;
 import org.bukkit.inventory.ItemStack;
 

+ 0 - 213
src/main/java/com/gmail/nossr50/skills/woodcutting/Woodcutting.java

@@ -1,213 +0,0 @@
-package com.gmail.nossr50.skills.woodcutting;
-
-import com.gmail.nossr50.util.BlockUtils;
-import com.gmail.nossr50.util.Misc;
-import com.gmail.nossr50.util.skills.SkillUtils;
-import org.bukkit.Material;
-import org.bukkit.block.BlockFace;
-import org.bukkit.block.BlockState;
-import org.bukkit.inventory.ItemStack;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-public final class Woodcutting {
-    /**
-     * The x/y differences to the blocks in a flat cylinder around the center
-     * block, which is excluded.
-     */
-    private static final int[][] directions = {
-            new int[]{-2, -1}, new int[]{-2, 0}, new int[]{-2, 1},
-            new int[]{-1, -2}, new int[]{-1, -1}, new int[]{-1, 0}, new int[]{-1, 1}, new int[]{-1, 2},
-            new int[]{0, -2}, new int[]{0, -1}, new int[]{0, 1}, new int[]{0, 2},
-            new int[]{1, -2}, new int[]{1, -1}, new int[]{1, 0}, new int[]{1, 1}, new int[]{1, 2},
-            new int[]{2, -1}, new int[]{2, 0}, new int[]{2, 1},
-    };
-    protected static boolean treeFellerReachedThreshold = false;
-
-    private Woodcutting() {
-
-    }
-
-    /**
-     * Retrieves the experience reward from a log
-     *
-     * @param blockState Log being broken
-     * @return Amount of experience
-     */
-    protected static int getExperienceFromLog(BlockState blockState) {
-        return pluginRef.getDynamicSettingsManager().getExperienceManager().getWoodcuttingXp(blockState.getType());
-    }
-
-    /**
-     * Retrieves the experience reward from logging via Tree Feller
-     * Experience is reduced per log processed so far
-     * Experience is only reduced if the config option to reduce Tree Feller XP is set
-     * Experience per log will not fall below 1 unless the experience for that log is set to 0 in the config
-     *
-     * @param blockState Log being broken
-     * @param woodCount how many logs have given out XP for this tree feller so far
-     * @return Amount of experience
-     */
-    protected static int processTreeFellerXPGains(BlockState blockState, int woodCount) {
-        int rawXP = pluginRef.getDynamicSettingsManager().getExperienceManager().getWoodcuttingXp(blockState.getType());
-
-        if(rawXP <= 0)
-            return 0;
-
-        if(pluginRef.getConfigManager().getConfigExperience().getExperienceWoodcutting().isReduceTreeFellerXP()) {
-            int reducedXP = 1 + (woodCount * 5);
-            rawXP = Math.max(1, rawXP - reducedXP);
-            return rawXP;
-        } else {
-            return pluginRef.getDynamicSettingsManager().getExperienceManager().getWoodcuttingXp(blockState.getType());
-        }
-    }
-
-    /**
-     * Checks for double drops
-     *
-     * @param blockState Block being broken
-     */
-    protected static void checkForDoubleDrop(BlockState blockState) {
-        /*if (mcMMO.getModManager().isCustomLog(blockState) && mcMMO.getModManager().getBlock(blockState).isDoubleDropEnabled()) {
-            Misc.dropItems(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops());
-        }
-        else {*/
-        if (pluginRef.getDynamicSettingsManager().getBonusDropManager().isBonusDropWhitelisted(blockState.getType())) {
-            Misc.dropItems(Misc.getBlockCenter(blockState), blockState.getBlock().getDrops());
-        }
-        //}
-    }
-
-    /**
-     * Processes Tree Feller in a recursive manner
-     *
-     * @param blockState       Block being checked
-     * @param treeFellerBlocks List of blocks to be removed
-     */
-    /*
-     * Algorithm: An int[][] of X/Z directions is created on static class
-     * initialization, representing a cylinder with radius of about 2 - the
-     * (0,0) center and all (+-2, +-2) corners are omitted.
-     *
-     * handleBlock() returns a boolean, which is used for the sole purpose of
-     * switching between these two behaviors:
-     *
-     * (Call blockState "this log" for the below explanation.)
-     *
-     *  [A] There is another log above this log (TRUNK)
-     *    Only the flat cylinder in the directions array is searched.
-     *  [B] There is not another log above this log (BRANCH AND TOP)
-     *    The cylinder in the directions array is extended up and down by 1
-     *    block in the Y-axis, and the block below this log is checked as
-     *    well. Due to the fact that the directions array will catch all
-     *    blocks on a red mushroom, the special method for it is eliminated.
-     *
-     * This algorithm has been shown to achieve a performance of 2-5
-     * milliseconds on regular trees and 10-15 milliseconds on jungle trees
-     * once the JIT has optimized the function (use the ability about 4 times
-     * before taking measurements).
-     */
-    protected static void processTree(BlockState blockState, Set<BlockState> treeFellerBlocks) {
-        List<BlockState> futureCenterBlocks = new ArrayList<>();
-
-        // Check the block up and take different behavior (smaller search) if it's a log
-        if (handleBlock(blockState.getBlock().getRelative(BlockFace.UP).getState(), futureCenterBlocks, treeFellerBlocks)) {
-            for (int[] dir : directions) {
-                handleBlock(blockState.getBlock().getRelative(dir[0], 0, dir[1]).getState(), futureCenterBlocks, treeFellerBlocks);
-
-                if (treeFellerReachedThreshold) {
-                    return;
-                }
-            }
-        } else {
-            // Cover DOWN
-            handleBlock(blockState.getBlock().getRelative(BlockFace.DOWN).getState(), futureCenterBlocks, treeFellerBlocks);
-            // Search in a cube
-            for (int y = -1; y <= 1; y++) {
-                for (int[] dir : directions) {
-                    handleBlock(blockState.getBlock().getRelative(dir[0], y, dir[1]).getState(), futureCenterBlocks, treeFellerBlocks);
-
-                    if (treeFellerReachedThreshold) {
-                        return;
-                    }
-                }
-            }
-        }
-
-        // Recursive call for each log found
-        for (BlockState futureCenterBlock : futureCenterBlocks) {
-            if (treeFellerReachedThreshold) {
-                return;
-            }
-
-            processTree(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(Set<BlockState> treeFellerBlocks, ItemStack inHand) {
-        //Treat the NBT tag for unbreakable and the durability enchant differently
-        if(inHand.getItemMeta() != null && inHand.getItemMeta().isUnbreakable()) {
-            return true;
-        }
-
-        short durabilityLoss = 0;
-        Material type = inHand.getType();
-
-        for (BlockState blockState : treeFellerBlocks) {
-            if (BlockUtils.isLog(blockState)) {
-                durabilityLoss += pluginRef.getConfigManager().getConfigSuperAbilities().getSuperAbilityLimits().getToolDurabilityDamage();
-            }
-        }
-
-        SkillUtils.handleDurabilityChange(inHand, durabilityLoss);
-        return (inHand.getDurability() < (pluginRef.getRepairableManager().isRepairable(type) ? pluginRef.getRepairableManager().getRepairable(type).getMaximumDurability() : type.getMaxDurability()));
-    }
-
-    /**
-     * Handle a block addition to the list of blocks to be removed and to the
-     * list of blocks used for future recursive calls of
-     * 'processTree()'
-     *
-     * @param blockState         Block to be added
-     * @param futureCenterBlocks List of blocks that will be used to call
-     *                           'processTree()'
-     * @param treeFellerBlocks   List of blocks to be removed
-     * @return true if and only if the given blockState was a Log not already
-     * in treeFellerBlocks.
-     */
-    private static boolean handleBlock(BlockState blockState, List<BlockState> futureCenterBlocks, Set<BlockState> treeFellerBlocks) {
-        if (treeFellerBlocks.contains(blockState) || pluginRef.getPlaceStore().isTrue(blockState)) {
-            return false;
-        }
-
-        // Without this check Tree Feller propagates through leaves until the threshold is hit
-        if (treeFellerBlocks.size() > pluginRef.getConfigManager().getConfigSuperAbilities().getSuperAbilityLimits().getTreeFeller().getTreeFellerLimit()) {
-            treeFellerReachedThreshold = true;
-        }
-
-        if (BlockUtils.isLog(blockState)) {
-            treeFellerBlocks.add(blockState);
-            futureCenterBlocks.add(blockState);
-            return true;
-        } else if (BlockUtils.isLeaves(blockState)) {
-            treeFellerBlocks.add(blockState);
-            return false;
-        }
-        return false;
-    }
-
-    protected enum ExperienceGainMethod {
-        DEFAULT,
-        TREE_FELLER,
-    }
-}

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

@@ -6,6 +6,7 @@ import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
+import com.gmail.nossr50.datatypes.skills.behaviours.WoodcuttingBehaviour;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.skills.SkillManager;
 import com.gmail.nossr50.util.BlockUtils;
@@ -16,19 +17,28 @@ import com.gmail.nossr50.util.random.RandomChanceUtil;
 import com.gmail.nossr50.util.skills.CombatUtils;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.skills.SkillActivationType;
+import com.gmail.nossr50.util.skills.SkillUtils;
 import org.bukkit.Material;
 import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
 import org.bukkit.block.BlockState;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
 
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 public class WoodcuttingManager extends SkillManager {
 
+    private final WoodcuttingBehaviour woodcuttingBehaviour;
+    private boolean treeFellerReachedThreshold;
+
     public WoodcuttingManager(mcMMO pluginRef, McMMOPlayer mcMMOPlayer) {
         super(pluginRef, mcMMOPlayer, PrimarySkillType.WOODCUTTING);
+        this.treeFellerReachedThreshold = false;
+        this.woodcuttingBehaviour = pluginRef.getDynamicSettingsManager().getSkillBehaviourManager().getWoodcuttingBehaviour();
     }
 
     public boolean canUseLeafBlower(ItemStack heldItem) {
@@ -42,7 +52,7 @@ public class WoodcuttingManager extends SkillManager {
                 && ItemUtils.isAxe(heldItem);
     }
 
-    protected boolean canGetDoubleDrops() {
+    public boolean canGetDoubleDrops() {
         return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
                 && RankUtils.hasReachedRank(1, getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
                 && RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.WOODCUTTING_HARVEST_LUMBER, getPlayer());
@@ -54,7 +64,7 @@ public class WoodcuttingManager extends SkillManager {
      * @param blockState Block being broken
      */
     public void woodcuttingBlockCheck(BlockState blockState) {
-        int xp = Woodcutting.getExperienceFromLog(blockState);
+        int xp = woodcuttingBehaviour.getExperienceFromLog(blockState);
 
         switch (blockState.getType()) {
             case BROWN_MUSHROOM_BLOCK:
@@ -63,7 +73,7 @@ public class WoodcuttingManager extends SkillManager {
 
             default:
                 if (canGetDoubleDrops()) {
-                    Woodcutting.checkForDoubleDrop(blockState);
+                    woodcuttingBehaviour.checkForDoubleDrop(blockState);
                 }
         }
 
@@ -79,20 +89,20 @@ public class WoodcuttingManager extends SkillManager {
         Player player = getPlayer();
         Set<BlockState> treeFellerBlocks = new HashSet<>();
 
-        Woodcutting.treeFellerReachedThreshold = false;
+        treeFellerReachedThreshold = false;
 
-        Woodcutting.processTree(blockState, treeFellerBlocks);
+        processTree(blockState, treeFellerBlocks);
 
         // If the player is trying to break too many blocks
-        if (Woodcutting.treeFellerReachedThreshold) {
-            Woodcutting.treeFellerReachedThreshold = false;
+        if (treeFellerReachedThreshold) {
+            treeFellerReachedThreshold = false;
 
             pluginRef.getNotificationManager().sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Woodcutting.Skills.TreeFeller.Threshold");
             return;
         }
 
         // If the tool can't sustain the durability loss
-        if (!Woodcutting.handleDurabilityLoss(treeFellerBlocks, player.getInventory().getItemInMainHand())) {
+        if (!handleDurabilityLoss(treeFellerBlocks, player.getInventory().getItemInMainHand())) {
             pluginRef.getNotificationManager().sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Woodcutting.Skills.TreeFeller.Splinter");
 
             double health = player.getHealth();
@@ -105,7 +115,137 @@ public class WoodcuttingManager extends SkillManager {
         }
 
         dropBlocks(treeFellerBlocks);
-        Woodcutting.treeFellerReachedThreshold = false; // Reset the value after we're done with Tree Feller each time.
+        treeFellerReachedThreshold = false; // Reset the value after we're done with Tree Feller each time.
+    }
+
+    /**
+     * Processes Tree Feller in a recursive manner
+     *
+     * @param blockState       Block being checked
+     * @param treeFellerBlocks List of blocks to be removed
+     */
+    /*
+     * Algorithm: An int[][] of X/Z directions is created on class
+     * initialization, representing a cylinder with radius of about 2 - the
+     * (0,0) center and all (+-2, +-2) corners are omitted.
+     *
+     * handleBlock() returns a boolean, which is used for the sole purpose of
+     * switching between these two behaviors:
+     *
+     * (Call blockState "this log" for the below explanation.)
+     *
+     *  [A] There is another log above this log (TRUNK)
+     *    Only the flat cylinder in the directions array is searched.
+     *  [B] There is not another log above this log (BRANCH AND TOP)
+     *    The cylinder in the directions array is extended up and down by 1
+     *    block in the Y-axis, and the block below this log is checked as
+     *    well. Due to the fact that the directions array will catch all
+     *    blocks on a red mushroom, the special method for it is eliminated.
+     *
+     * This algorithm has been shown to achieve a performance of 2-5
+     * milliseconds on regular trees and 10-15 milliseconds on jungle trees
+     * once the JIT has optimized the function (use the ability about 4 times
+     * before taking measurements).
+     */
+    public void processTree(BlockState blockState, Set<BlockState> treeFellerBlocks) {
+        List<BlockState> futureCenterBlocks = new ArrayList<>();
+
+        // Check the block up and take different behavior (smaller search) if it's a log
+        if (handleBlock(blockState.getBlock().getRelative(BlockFace.UP).getState(), futureCenterBlocks, treeFellerBlocks)) {
+            for (int[] dir : woodcuttingBehaviour.getDirections()) {
+                handleBlock(blockState.getBlock().getRelative(dir[0], 0, dir[1]).getState(), futureCenterBlocks, treeFellerBlocks);
+
+                if (treeFellerReachedThreshold) {
+                    return;
+                }
+            }
+        } else {
+            // Cover DOWN
+            handleBlock(blockState.getBlock().getRelative(BlockFace.DOWN).getState(), futureCenterBlocks, treeFellerBlocks);
+            // Search in a cube
+            for (int y = -1; y <= 1; y++) {
+                for (int[] dir : woodcuttingBehaviour.getDirections()) {
+                    handleBlock(blockState.getBlock().getRelative(dir[0], y, dir[1]).getState(), futureCenterBlocks, treeFellerBlocks);
+
+                    if (treeFellerReachedThreshold) {
+                        return;
+                    }
+                }
+            }
+        }
+
+        // Recursive call for each log found
+        for (BlockState futureCenterBlock : futureCenterBlocks) {
+            if (treeFellerReachedThreshold) {
+                return;
+            }
+
+            processTree(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
+     */
+    public boolean handleDurabilityLoss(Set<BlockState> treeFellerBlocks, ItemStack inHand) {
+        //Treat the NBT tag for unbreakable and the durability enchant differently
+        if(inHand.getItemMeta() != null && inHand.getItemMeta().isUnbreakable()) {
+            return true;
+        }
+
+        short durabilityLoss = 0;
+        Material type = inHand.getType();
+
+        for (BlockState blockState : treeFellerBlocks) {
+            if (BlockUtils.isLog(blockState)) {
+                durabilityLoss += pluginRef.getConfigManager().getConfigSuperAbilities().getSuperAbilityLimits().getToolDurabilityDamage();
+            }
+        }
+
+        SkillUtils.handleDurabilityChange(inHand, durabilityLoss);
+        return (inHand.getDurability() < (pluginRef.getRepairableManager().isRepairable(type) ? pluginRef.getRepairableManager().getRepairable(type).getMaximumDurability() : type.getMaxDurability()));
+    }
+
+    /**
+     * Handle a block addition to the list of blocks to be removed and to the
+     * list of blocks used for future recursive calls of
+     * 'processTree()'
+     *
+     * @param blockState         Block to be added
+     * @param futureCenterBlocks List of blocks that will be used to call
+     *                           'processTree()'
+     * @param treeFellerBlocks   List of blocks to be removed
+     * @return true if and only if the given blockState was a Log not already
+     * in treeFellerBlocks.
+     */
+    private boolean handleBlock(BlockState blockState, List<BlockState> futureCenterBlocks, Set<BlockState> treeFellerBlocks) {
+        if (treeFellerBlocks.contains(blockState) || pluginRef.getPlaceStore().isTrue(blockState)) {
+            return false;
+        }
+
+        // Without this check Tree Feller propagates through leaves until the threshold is hit
+        if (treeFellerBlocks.size() > pluginRef.getConfigManager().getConfigSuperAbilities().getSuperAbilityLimits().getTreeFeller().getTreeFellerLimit()) {
+            treeFellerReachedThreshold = true;
+        }
+
+        if (BlockUtils.isLog(blockState)) {
+            treeFellerBlocks.add(blockState);
+            futureCenterBlocks.add(blockState);
+            return true;
+        } else if (BlockUtils.isLeaves(blockState)) {
+            treeFellerBlocks.add(blockState);
+            return false;
+        }
+        return false;
+    }
+
+    public enum ExperienceGainMethod {
+        DEFAULT,
+        TREE_FELLER,
     }
 
     /**
@@ -129,14 +269,14 @@ public class WoodcuttingManager extends SkillManager {
 
             //TODO: Update this to drop the correct items/blocks via NMS
             if (material == Material.BROWN_MUSHROOM_BLOCK || material == Material.RED_MUSHROOM_BLOCK) {
-                xp += Woodcutting.processTreeFellerXPGains(blockState, processedLogCount);
+                xp += woodcuttingBehaviour.processTreeFellerXPGains(blockState, processedLogCount);
                 Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops());
             } else {
                 if (BlockUtils.isLog(blockState)) {
                     if (canGetDoubleDrops()) {
-                        Woodcutting.checkForDoubleDrop(blockState);
+                        woodcuttingBehaviour.checkForDoubleDrop(blockState);
                     }
-                    xp += Woodcutting.processTreeFellerXPGains(blockState, processedLogCount);
+                    xp += woodcuttingBehaviour.processTreeFellerXPGains(blockState, processedLogCount);
                     Misc.dropItems(Misc.getBlockCenter(blockState), block.getDrops());
                 }
                 if (BlockUtils.isLeaves(blockState)) {