Browse Source

Add XP test for Woodcutting

nossr50 2 năm trước cách đây
mục cha
commit
72957c3d31

+ 52 - 71
src/main/java/com/gmail/nossr50/config/experience/ExperienceConfig.java

@@ -296,6 +296,10 @@ public class ExperienceConfig extends BukkitConfig {
     }
 
     /* Combat XP Multipliers */
+    public double getCombatXP(String entity) {
+        return config.getDouble("Experience_Values.Combat.Multiplier." + entity);
+    }
+
     public double getCombatXP(EntityType entity) {
         return config.getDouble("Experience_Values.Combat.Multiplier." + StringUtils.getPrettyEntityTypeString(entity).replace(" ", "_"));
     }
@@ -314,96 +318,73 @@ public class ExperienceConfig extends BukkitConfig {
 
     /* Materials  */
     public int getXp(PrimarySkillType skill, Material material) {
-        //TODO: Temporary measure to fix an exploit caused by a yet to be fixed Spigot bug (as of 7/3/2020)
-        if (material.toString().equalsIgnoreCase("LILY_PAD"))
-            return 0;
-
-        String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + ".";
-        String explicitString = baseString + StringUtils.getExplicitConfigMaterialString(material);
-        if (config.contains(explicitString))
-            return config.getInt(explicitString);
-        String friendlyString = baseString + StringUtils.getFriendlyConfigMaterialString(material);
-        if (config.contains(friendlyString))
-            return config.getInt(friendlyString);
-        String wildcardString = baseString + StringUtils.getWildcardConfigMaterialString(material);
-        if (config.contains(wildcardString))
-            return config.getInt(wildcardString);
-        return 0;
+        return getXpHelper(skill, StringUtils.getExplicitConfigMaterialString(material),
+                StringUtils.getFriendlyConfigMaterialString(material),
+                StringUtils.getWildcardConfigMaterialString(material));
     }
 
-    /* Materials  */
     public int getXp(PrimarySkillType skill, BlockState blockState) {
-        Material data = blockState.getType();
-
-        String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + ".";
-        String explicitString = baseString + StringUtils.getExplicitConfigMaterialString(data);
-        if (config.contains(explicitString))
-            return config.getInt(explicitString);
-        String friendlyString = baseString + StringUtils.getFriendlyConfigMaterialString(data);
-        if (config.contains(friendlyString))
-            return config.getInt(friendlyString);
-        String wildcardString = baseString + StringUtils.getWildcardConfigMaterialString(data);
-        if (config.contains(wildcardString))
-            return config.getInt(wildcardString);
-        return 0;
+        Material material = blockState.getType();
+        return getXp(skill, material);
     }
 
-    /* Materials  */
     public int getXp(PrimarySkillType skill, Block block) {
-        Material data = block.getType();
-
-        String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + ".";
-        String explicitString = baseString + StringUtils.getExplicitConfigMaterialString(data);
-        if (config.contains(explicitString))
-            return config.getInt(explicitString);
-        String friendlyString = baseString + StringUtils.getFriendlyConfigMaterialString(data);
-        if (config.contains(friendlyString))
-            return config.getInt(friendlyString);
-        String wildcardString = baseString + StringUtils.getWildcardConfigMaterialString(data);
-        if (config.contains(wildcardString))
-            return config.getInt(wildcardString);
-        return 0;
+        Material material = block.getType();
+        return getXp(skill, material);
     }
 
-    /* Materials  */
     public int getXp(PrimarySkillType skill, BlockData data) {
+        return getXpHelper(skill, StringUtils.getExplicitConfigBlockDataString(data),
+                StringUtils.getFriendlyConfigBlockDataString(data),
+                StringUtils.getWildcardConfigBlockDataString(data));
+    }
+
+    private int getXpHelper(PrimarySkillType skill, String explicitString, String friendlyString, String wildcardString) {
+        if (explicitString.equalsIgnoreCase("LILY_PAD")) {
+            return 0;
+        }
+
         String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + ".";
-        String explicitString = baseString + StringUtils.getExplicitConfigBlockDataString(data);
-        if (config.contains(explicitString))
-            return config.getInt(explicitString);
-        String friendlyString = baseString + StringUtils.getFriendlyConfigBlockDataString(data);
-        if (config.contains(friendlyString))
-            return config.getInt(friendlyString);
-        String wildcardString = baseString + StringUtils.getWildcardConfigBlockDataString(data);
-        if (config.contains(wildcardString))
-            return config.getInt(wildcardString);
+        String[] configStrings = {explicitString, friendlyString, wildcardString};
+
+        for (String configString : configStrings) {
+            String fullPath = baseString + configString;
+            if (config.contains(fullPath)) {
+                return config.getInt(fullPath);
+            }
+        }
+
         return 0;
     }
 
-    public boolean doesBlockGiveSkillXP(PrimarySkillType skill, Material data) {
-        String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + ".";
-        String explicitString = baseString + StringUtils.getExplicitConfigMaterialString(data);
-        if (config.contains(explicitString))
-            return true;
-        String friendlyString = baseString + StringUtils.getFriendlyConfigMaterialString(data);
-        if (config.contains(friendlyString))
-            return true;
-        String wildcardString = baseString + StringUtils.getWildcardConfigMaterialString(data);
-        return config.contains(wildcardString);
+
+    public boolean doesBlockGiveSkillXP(PrimarySkillType skill, Material material) {
+        return doesBlockGiveSkillXPHelper(skill, StringUtils.getExplicitConfigMaterialString(material),
+                StringUtils.getFriendlyConfigMaterialString(material),
+                StringUtils.getWildcardConfigMaterialString(material));
     }
 
     public boolean doesBlockGiveSkillXP(PrimarySkillType skill, BlockData data) {
+        return doesBlockGiveSkillXPHelper(skill, StringUtils.getExplicitConfigBlockDataString(data),
+                StringUtils.getFriendlyConfigBlockDataString(data),
+                StringUtils.getWildcardConfigBlockDataString(data));
+    }
+
+    private boolean doesBlockGiveSkillXPHelper(PrimarySkillType skill, String explicitString, String friendlyString, String wildcardString) {
         String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + ".";
-        String explicitString = baseString + StringUtils.getExplicitConfigBlockDataString(data);
-        if (config.contains(explicitString))
-            return true;
-        String friendlyString = baseString + StringUtils.getFriendlyConfigBlockDataString(data);
-        if (config.contains(friendlyString))
-            return true;
-        String wildcardString = baseString + StringUtils.getWildcardConfigBlockDataString(data);
-        return config.contains(wildcardString);
+        String[] configStrings = {explicitString, friendlyString, wildcardString};
+
+        for (String configString : configStrings) {
+            String fullPath = baseString + configString;
+            if (config.contains(fullPath)) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
+
     /*
      * Experience Bar Stuff
      */

+ 0 - 1
src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java

@@ -17,7 +17,6 @@ import com.gmail.nossr50.runnables.skills.AwardCombatXpTask;
 import com.gmail.nossr50.skills.acrobatics.AcrobaticsManager;
 import com.gmail.nossr50.skills.archery.ArcheryManager;
 import com.gmail.nossr50.skills.axes.AxesManager;
-import com.gmail.nossr50.skills.crossbows.CrossbowsManager;
 import com.gmail.nossr50.skills.swords.SwordsManager;
 import com.gmail.nossr50.skills.taming.TamingManager;
 import com.gmail.nossr50.skills.tridents.TridentsManager;

+ 0 - 1
src/main/java/com/gmail/nossr50/util/skills/RankUtils.java

@@ -317,7 +317,6 @@ public class RankUtils {
      * @param rank The target rank
      * @return The level at which this rank unlocks
      */
-    @Deprecated
     public static int getRankUnlockLevel(SubSkillType subSkillType, int rank)
     {
         return RankConfig.getInstance().getSubSkillUnlockLevel(subSkillType, rank);

+ 87 - 106
src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManagerTest.java → src/test/java/com/gmail/nossr50/MMOTestEnvironment.java

@@ -1,18 +1,16 @@
-package com.gmail.nossr50.skills.woodcutting;
+package com.gmail.nossr50;
 
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.ChatConfig;
 import com.gmail.nossr50.config.GeneralConfig;
 import com.gmail.nossr50.config.RankConfig;
+import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
-import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.util.EventUtils;
-import com.gmail.nossr50.util.Misc;
-import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.TransientEntityTracker;
+import com.gmail.nossr50.util.*;
+import com.gmail.nossr50.util.blockmeta.ChunkManager;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.skills.SkillTools;
@@ -20,83 +18,78 @@ import org.bukkit.Location;
 import org.bukkit.Material;
 import org.bukkit.Server;
 import org.bukkit.World;
-import org.bukkit.block.Block;
-import org.bukkit.block.BlockState;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.PlayerInventory;
 import org.bukkit.plugin.PluginManager;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 
 import java.util.UUID;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-
-class WoodcuttingManagerTest {
-    private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(WoodcuttingManagerTest.class.getName());
-    private MockedStatic<mcMMO> mockedMcMMO;
-    private MockedStatic<ChatConfig> mockedChatConfig;
-    private MockedStatic<Permissions> mockedPermissions;
-    private MockedStatic<RankUtils> mockedRankUtils;
-    private MockedStatic<UserManager> mockedUserManager;
-    private MockedStatic<Misc> mockedMisc;
-    private MockedStatic<SkillTools> mockedSkillTools;
-    private MockedStatic<EventUtils> mockedEventUtils;
-    private TransientEntityTracker transientEntityTracker;
-    private AdvancedConfig advancedConfig;
-    private GeneralConfig generalConfig;
-    private RankConfig rankConfig;
-    private SkillTools skillTools;
-    private Server server;
-    private PluginManager pluginManager;
-    private World world;
-
-    private WoodcuttingManager woodcuttingManager;
+
+public abstract class MMOTestEnvironment {
+    private final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(MMOTestEnvironment.class.getName());
+    protected MockedStatic<mcMMO> mockedMcMMO;
+    protected MockedStatic<ChatConfig> mockedChatConfig;
+    protected MockedStatic<ExperienceConfig> experienceConfig;
+    protected MockedStatic<Permissions> mockedPermissions;
+    protected MockedStatic<RankUtils> mockedRankUtils;
+    protected MockedStatic<UserManager> mockedUserManager;
+    protected MockedStatic<Misc> mockedMisc;
+    protected MockedStatic<SkillTools> mockedSkillTools;
+    protected MockedStatic<EventUtils> mockedEventUtils;
+    protected TransientEntityTracker transientEntityTracker;
+    protected AdvancedConfig advancedConfig;
+    protected GeneralConfig generalConfig;
+    protected RankConfig rankConfig;
+    protected SkillTools skillTools;
+    protected Server server;
+    protected PluginManager pluginManager;
+    protected World world;
 
     /* Mocks */
-    Player player;
+    protected Player player;
+
+    protected UUID playerUUID = UUID.randomUUID();
+    protected ItemStack itemInMainHand;
 
-    UUID playerUUID = UUID.randomUUID();
-    ItemStack itemInMainHand;
+    protected PlayerInventory playerInventory;
+    protected PlayerProfile playerProfile;
+    protected McMMOPlayer mmoPlayer;
+    protected String playerName = "testPlayer";
 
-    PlayerInventory playerInventory;
-    PlayerProfile playerProfile;
-    McMMOPlayer mmoPlayer;
-    String playerName = "testPlayer";
+    protected ChunkManager chunkManager;
 
-    @BeforeEach
-    void setUp() {
+    protected void mockBaseEnvironment() {
         mockedMcMMO = Mockito.mockStatic(mcMMO.class);
         mcMMO.p = Mockito.mock(mcMMO.class);
         Mockito.when(mcMMO.p.getLogger()).thenReturn(logger);
 
+        // place store
+        chunkManager = Mockito.mock(ChunkManager.class);
+        Mockito.when(mcMMO.getPlaceStore()).thenReturn(chunkManager);
+
+        // shut off mod manager for woodcutting
+        Mockito.when(mcMMO.getModManager()).thenReturn(Mockito.mock(ModManager.class));
+        Mockito.when(mcMMO.getModManager().isCustomLog(any())).thenReturn(false);
+
         // chat config
         mockedChatConfig = Mockito.mockStatic(ChatConfig.class);
         Mockito.when(ChatConfig.getInstance()).thenReturn(Mockito.mock(ChatConfig.class));
 
         // general config
-        generalConfig = Mockito.mock(GeneralConfig.class);
-        Mockito.when(generalConfig.getTreeFellerThreshold()).thenReturn(100);
-        Mockito.when(generalConfig.getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, Material.OAK_LOG)).thenReturn(true);
-        Mockito.when(generalConfig.getLocale()).thenReturn("en_US");
-        Mockito.when(mcMMO.p.getGeneralConfig()).thenReturn(generalConfig);
+        mockGeneralConfig();
 
         // rank config
-        rankConfig = Mockito.mock(RankConfig.class);
-        Mockito.when(rankConfig.getSubSkillUnlockLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER, 1)).thenReturn(1);
+        mockRankConfig();
 
         // wire advanced config
-        this.advancedConfig = Mockito.mock(AdvancedConfig.class);
-        Mockito.when(advancedConfig.getMaximumProbability(SubSkillType.WOODCUTTING_HARVEST_LUMBER)).thenReturn(100D);
-        Mockito.when(advancedConfig.getMaximumProbability(SubSkillType.WOODCUTTING_CLEAN_CUTS)).thenReturn(10D);
-        Mockito.when(advancedConfig.getMaxBonusLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER)).thenReturn(1000);
-        Mockito.when(advancedConfig.getMaxBonusLevel(SubSkillType.WOODCUTTING_CLEAN_CUTS)).thenReturn(10000);
-        Mockito.when(mcMMO.p.getAdvancedConfig()).thenReturn(advancedConfig);
+        mockAdvancedConfig();
+
+        // wire experience config
+        mockExperienceConfig();
 
         // wire skill tools
         this.skillTools = new SkillTools(mcMMO.p);
@@ -105,18 +98,9 @@ class WoodcuttingManagerTest {
         this.transientEntityTracker = new TransientEntityTracker();
         Mockito.when(mcMMO.getTransientEntityTracker()).thenReturn(transientEntityTracker);
 
-        mockedPermissions = Mockito.mockStatic(Permissions.class);
-        Mockito.when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true);
-        Mockito.when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true);
-        Mockito.when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true);
-        Mockito.when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true);
-        Mockito.when(Permissions.lucky(player, PrimarySkillType.WOODCUTTING)).thenReturn(false); // player is not lucky
+        mockPermissions();
 
         mockedRankUtils = Mockito.mockStatic(RankUtils.class);
-        Mockito.when(RankUtils.getRankUnlockLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER, 1)).thenReturn(1); // needed?
-        Mockito.when(RankUtils.getRankUnlockLevel(SubSkillType.WOODCUTTING_CLEAN_CUTS, 1)).thenReturn(1000); // needed?
-        Mockito.when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.WOODCUTTING_HARVEST_LUMBER))).thenReturn(true);
-        Mockito.when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.WOODCUTTING_CLEAN_CUTS))).thenReturn(true);
 
         // wire server
         this.server = Mockito.mock(Server.class);
@@ -139,9 +123,7 @@ class WoodcuttingManagerTest {
 
         // wire inventory
         this.playerInventory = Mockito.mock(PlayerInventory.class);
-        this.itemInMainHand = new ItemStack(Material.DIAMOND_AXE);
         Mockito.when(player.getInventory()).thenReturn(playerInventory);
-        Mockito.when(playerInventory.getItemInMainHand()).thenReturn(itemInMainHand);
 
         // PlayerProfile and McMMOPlayer are partially mocked
         playerProfile = new PlayerProfile("testPlayer", player.getUniqueId(), 0);
@@ -150,17 +132,51 @@ class WoodcuttingManagerTest {
         // wire user manager
         this.mockedUserManager = Mockito.mockStatic(UserManager.class);
         Mockito.when(UserManager.getPlayer(player)).thenReturn(mmoPlayer);
+    }
+
+    private void mockPermissions() {
+        mockedPermissions = Mockito.mockStatic(Permissions.class);
+        Mockito.when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true);
+        Mockito.when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true);
+        Mockito.when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true);
+        Mockito.when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true);
+        Mockito.when(Permissions.lucky(player, PrimarySkillType.WOODCUTTING)).thenReturn(false); // player is not lucky
+    }
 
-        // Set up spy for WoodcuttingManager
-        woodcuttingManager = Mockito.spy(new WoodcuttingManager(mmoPlayer));
+    private void mockRankConfig() {
+        rankConfig = Mockito.mock(RankConfig.class);
+    }
+
+    private void mockAdvancedConfig() {
+        this.advancedConfig = Mockito.mock(AdvancedConfig.class);
+        Mockito.when(mcMMO.p.getAdvancedConfig()).thenReturn(advancedConfig);
+    }
+
+    private void mockGeneralConfig() {
+        generalConfig = Mockito.mock(GeneralConfig.class);
+        Mockito.when(generalConfig.getTreeFellerThreshold()).thenReturn(100);
+        Mockito.when(generalConfig.getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, Material.OAK_LOG)).thenReturn(true);
+        Mockito.when(generalConfig.getLocale()).thenReturn("en_US");
+        Mockito.when(mcMMO.p.getGeneralConfig()).thenReturn(generalConfig);
+    }
+
+    private void mockExperienceConfig() {
+        experienceConfig = Mockito.mockStatic(ExperienceConfig.class);
+
+        Mockito.when(ExperienceConfig.getInstance()).thenReturn(Mockito.mock(ExperienceConfig.class));
+
+        // Combat
+        Mockito.when(ExperienceConfig.getInstance().getCombatXP("Cow")).thenReturn(1D);
     }
 
-    @AfterEach
-    void tearDown() {
+    protected void cleanupBaseEnvironment() {
         // Clean up resources here if needed.
         if (mockedMcMMO != null) {
             mockedMcMMO.close();
         }
+        if (experienceConfig != null) {
+            experienceConfig.close();
+        }
         if (mockedChatConfig != null) {
             mockedChatConfig.close();
         }
@@ -180,39 +196,4 @@ class WoodcuttingManagerTest {
             mockedEventUtils.close();
         }
     }
-
-    @Test
-    void harvestLumberShouldDoubleDrop() {
-        mmoPlayer.modifySkill(PrimarySkillType.WOODCUTTING, 1000);
-
-        BlockState blockState = Mockito.mock(BlockState.class);
-        Block block = Mockito.mock(Block.class);
-        // wire block
-        Mockito.when(blockState.getBlock()).thenReturn(block);
-
-        Mockito.when(blockState.getBlock().getDrops(any())).thenReturn(null);
-        Mockito.when(blockState.getType()).thenReturn(Material.OAK_LOG);
-        woodcuttingManager.processBonusDropCheck(blockState);
-
-        // verify bonus drops were spawned
-        Mockito.verify(woodcuttingManager, Mockito.times(1)).spawnHarvestLumberBonusDrops(blockState);
-    }
-
-
-    @Test
-    void harvestLumberShouldNotDoubleDrop() {
-        mmoPlayer.modifySkill(PrimarySkillType.WOODCUTTING, 0);
-
-        BlockState blockState = Mockito.mock(BlockState.class);
-        Block block = Mockito.mock(Block.class);
-        // wire block
-        Mockito.when(blockState.getBlock()).thenReturn(block);
-
-        Mockito.when(blockState.getBlock().getDrops(any())).thenReturn(null);
-        Mockito.when(blockState.getType()).thenReturn(Material.OAK_LOG);
-        woodcuttingManager.processBonusDropCheck(blockState);
-
-        // verify bonus drops were not spawned
-        Mockito.verify(woodcuttingManager, Mockito.times(0)).spawnHarvestLumberBonusDrops(blockState);
-    }
 }

+ 38 - 0
src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java

@@ -0,0 +1,38 @@
+package com.gmail.nossr50.skills.tridents;
+
+import com.gmail.nossr50.MMOTestEnvironment;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.PlayerInventory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.mockito.Mockito;
+
+class TridentsTest extends MMOTestEnvironment {
+    private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(TridentsTest.class.getName());
+
+    TridentsManager tridentsManager;
+    ItemStack trident;
+    @BeforeEach
+    void setUp() {
+        mockBaseEnvironment();
+
+        // setup player and player related mocks after everything else
+        this.player = Mockito.mock(Player.class);
+        Mockito.when(player.getUniqueId()).thenReturn(playerUUID);
+
+        // wire inventory
+        this.playerInventory = Mockito.mock(PlayerInventory.class);
+        this.trident = new ItemStack(Material.TRIDENT);
+        Mockito.when(playerInventory.getItemInMainHand()).thenReturn(trident);
+
+        // Set up spy for manager
+        tridentsManager = Mockito.spy(new TridentsManager(mmoPlayer));
+    }
+
+    @AfterEach
+    void tearDown() {
+        cleanupBaseEnvironment();
+    }
+}

+ 107 - 0
src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java

@@ -0,0 +1,107 @@
+package com.gmail.nossr50.skills.woodcutting;
+
+import com.gmail.nossr50.MMOTestEnvironment;
+import com.gmail.nossr50.config.experience.ExperienceConfig;
+import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.skills.RankUtils;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockState;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.PlayerInventory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+
+class WoodcuttingTest extends MMOTestEnvironment {
+    private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(WoodcuttingTest.class.getName());
+
+    WoodcuttingManager woodcuttingManager;
+    @BeforeEach
+    void setUp() {
+        mockBaseEnvironment();
+        Mockito.when(rankConfig.getSubSkillUnlockLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER, 1)).thenReturn(1);
+
+        // wire advanced config
+        Mockito.when(advancedConfig.getMaximumProbability(SubSkillType.WOODCUTTING_HARVEST_LUMBER)).thenReturn(100D);
+        Mockito.when(advancedConfig.getMaximumProbability(SubSkillType.WOODCUTTING_CLEAN_CUTS)).thenReturn(10D);
+        Mockito.when(advancedConfig.getMaxBonusLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER)).thenReturn(1000);
+        Mockito.when(advancedConfig.getMaxBonusLevel(SubSkillType.WOODCUTTING_CLEAN_CUTS)).thenReturn(10000);
+
+        Mockito.when(RankUtils.getRankUnlockLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER, 1)).thenReturn(1); // needed?
+        Mockito.when(RankUtils.getRankUnlockLevel(SubSkillType.WOODCUTTING_CLEAN_CUTS, 1)).thenReturn(1000); // needed?
+        Mockito.when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.WOODCUTTING_HARVEST_LUMBER))).thenReturn(true);
+        Mockito.when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.WOODCUTTING_CLEAN_CUTS))).thenReturn(true);
+
+        // setup player and player related mocks after everything else
+        this.player = Mockito.mock(Player.class);
+        Mockito.when(player.getUniqueId()).thenReturn(playerUUID);
+
+        // wire inventory
+        this.playerInventory = Mockito.mock(PlayerInventory.class);
+        this.itemInMainHand = new ItemStack(Material.DIAMOND_AXE);
+        Mockito.when(player.getInventory()).thenReturn(playerInventory);
+        Mockito.when(playerInventory.getItemInMainHand()).thenReturn(itemInMainHand);
+
+        // Set up spy for WoodcuttingManager
+        woodcuttingManager = Mockito.spy(new WoodcuttingManager(mmoPlayer));
+    }
+
+    @AfterEach
+    void tearDown() {
+        cleanupBaseEnvironment();
+    }
+
+    @Test
+    void harvestLumberShouldDoubleDrop() {
+        mmoPlayer.modifySkill(PrimarySkillType.WOODCUTTING, 1000);
+
+        BlockState blockState = Mockito.mock(BlockState.class);
+        Block block = Mockito.mock(Block.class);
+        // wire block
+        Mockito.when(blockState.getBlock()).thenReturn(block);
+
+        Mockito.when(blockState.getBlock().getDrops(any())).thenReturn(null);
+        Mockito.when(blockState.getType()).thenReturn(Material.OAK_LOG);
+        woodcuttingManager.processBonusDropCheck(blockState);
+
+        // verify bonus drops were spawned
+        Mockito.verify(woodcuttingManager, Mockito.times(1)).spawnHarvestLumberBonusDrops(blockState);
+    }
+
+
+    @Test
+    void harvestLumberShouldNotDoubleDrop() {
+        mmoPlayer.modifySkill(PrimarySkillType.WOODCUTTING, 0);
+
+        BlockState blockState = Mockito.mock(BlockState.class);
+        Block block = Mockito.mock(Block.class);
+        // wire block
+        Mockito.when(blockState.getBlock()).thenReturn(block);
+
+        Mockito.when(blockState.getBlock().getDrops(any())).thenReturn(null);
+        Mockito.when(blockState.getType()).thenReturn(Material.OAK_LOG);
+        woodcuttingManager.processBonusDropCheck(blockState);
+
+        // verify bonus drops were not spawned
+        Mockito.verify(woodcuttingManager, Mockito.times(0)).spawnHarvestLumberBonusDrops(blockState);
+    }
+
+    @Test
+    void testProcessWoodcuttingBlockXP() {
+        BlockState targetBlock = Mockito.mock(BlockState.class);
+        Mockito.when(targetBlock.getType()).thenReturn(Material.OAK_LOG);
+        // wire XP
+        Mockito.when(ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, Material.OAK_LOG)).thenReturn(5);
+
+        // Verify XP increased by 5 when processing XP
+        woodcuttingManager.processWoodcuttingBlockXP(targetBlock);
+        Mockito.verify(mmoPlayer, Mockito.times(1)).beginXpGain(eq(PrimarySkillType.WOODCUTTING), eq(5F), any(), any());
+    }
+}