瀏覽代碼

Fix bug with levels up commands reporting incorrect level

nossr50 1 年之前
父節點
當前提交
edab455581

+ 2 - 2
src/main/java/com/gmail/nossr50/api/ExperienceAPI.java

@@ -1123,7 +1123,7 @@ public final class ExperienceAPI {
      * @throws InvalidFormulaTypeException if the given formulaType is not valid
      * @throws InvalidFormulaTypeException if the given formulaType is not valid
      */
      */
     public static int getXpNeededToLevel(int level) {
     public static int getXpNeededToLevel(int level) {
-        return mcMMO.getFormulaManager().getXPtoNextLevel(level, ExperienceConfig.getInstance().getFormulaType());
+        return mcMMO.p.getFormulaManager().getXPtoNextLevel(level, ExperienceConfig.getInstance().getFormulaType());
     }
     }
 
 
     /**
     /**
@@ -1137,7 +1137,7 @@ public final class ExperienceAPI {
      * @throws InvalidFormulaTypeException if the given formulaType is not valid
      * @throws InvalidFormulaTypeException if the given formulaType is not valid
      */
      */
     public static int getXpNeededToLevel(int level, String formulaType) {
     public static int getXpNeededToLevel(int level, String formulaType) {
-        return mcMMO.getFormulaManager().getXPtoNextLevel(level, getFormulaType(formulaType));
+        return mcMMO.p.getFormulaManager().getXPtoNextLevel(level, getFormulaType(formulaType));
     }
     }
 
 
     /**
     /**

+ 1 - 1
src/main/java/com/gmail/nossr50/commands/experience/ConvertExperienceCommand.java

@@ -18,7 +18,7 @@ public class ConvertExperienceCommand implements CommandExecutor {
     @Override
     @Override
     public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
     public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
         if (args.length == 2) {
         if (args.length == 2) {
-            FormulaType previousType = mcMMO.getFormulaManager().getPreviousFormulaType();
+            FormulaType previousType = mcMMO.p.getFormulaManager().getPreviousFormulaType();
             FormulaType newType = FormulaType.getFormulaType(args[1].toUpperCase(Locale.ENGLISH));
             FormulaType newType = FormulaType.getFormulaType(args[1].toUpperCase(Locale.ENGLISH));
 
 
             if (newType == FormulaType.UNKNOWN) {
             if (newType == FormulaType.UNKNOWN) {

+ 5 - 3
src/main/java/com/gmail/nossr50/config/experience/ExperienceConfig.java

@@ -199,7 +199,7 @@ public class ExperienceConfig extends BukkitConfig {
 
 
     /* Curve settings */
     /* Curve settings */
     public FormulaType getFormulaType() {
     public FormulaType getFormulaType() {
-        return FormulaType.getFormulaType(config.getString("Experience_Formula.Curve"));
+        return FormulaType.getFormulaType(config.getString("Experience_Formula.Curve", "LINEAR"));
     }
     }
 
 
     public boolean getCumulativeCurveEnabled() {
     public boolean getCumulativeCurveEnabled() {
@@ -208,11 +208,13 @@ public class ExperienceConfig extends BukkitConfig {
 
 
     /* Curve values */
     /* Curve values */
     public double getMultiplier(FormulaType type) {
     public double getMultiplier(FormulaType type) {
-        return config.getDouble("Experience_Formula." + StringUtils.getCapitalized(type.toString()) + "_Values.multiplier");
+        double def = type == FormulaType.LINEAR ? 20D : 0.1D;
+        return config.getDouble("Experience_Formula." + StringUtils.getCapitalized(type.toString()) + "_Values.multiplier", def);
     }
     }
 
 
     public int getBase(FormulaType type) {
     public int getBase(FormulaType type) {
-        return config.getInt("Experience_Formula." + StringUtils.getCapitalized(type.toString()) + "_Values.base");
+        int def = type == FormulaType.LINEAR ? 1020 : 2000;
+        return config.getInt("Experience_Formula." + StringUtils.getCapitalized(type.toString()) + "_Values.base", def);
     }
     }
 
 
     public double getExponent(FormulaType type) {
     public double getExponent(FormulaType type) {

+ 1 - 1
src/main/java/com/gmail/nossr50/datatypes/party/Party.java

@@ -203,7 +203,7 @@ public class Party {
 
 
     public int getXpToLevel() {
     public int getXpToLevel() {
         FormulaType formulaType = ExperienceConfig.getInstance().getFormulaType();
         FormulaType formulaType = ExperienceConfig.getInstance().getFormulaType();
-        return (mcMMO.getFormulaManager().getXPtoNextLevel(level, formulaType)) * (getOnlineMembers().size() + mcMMO.p.getGeneralConfig().getPartyXpCurveMultiplier());
+        return (mcMMO.p.getFormulaManager().getXPtoNextLevel(level, formulaType)) * (getOnlineMembers().size() + mcMMO.p.getGeneralConfig().getPartyXpCurveMultiplier());
     }
     }
 
 
     public String getXpToLevelPercentage() {
     public String getXpToLevelPercentage() {

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

@@ -663,7 +663,7 @@ public class McMMOPlayer implements Identified {
         }
         }
 
 
         final McMMOPlayerPreXpGainEvent mcMMOPlayerPreXpGainEvent = new McMMOPlayerPreXpGainEvent(player, primarySkillType, xp, xpGainReason);
         final McMMOPlayerPreXpGainEvent mcMMOPlayerPreXpGainEvent = new McMMOPlayerPreXpGainEvent(player, primarySkillType, xp, xpGainReason);
-        Bukkit.getPluginManager().callEvent(mcMMOPlayerPreXpGainEvent);
+        mcMMO.p.getServer().getPluginManager().callEvent(mcMMOPlayerPreXpGainEvent);
         xp = mcMMOPlayerPreXpGainEvent.getXpGained();
         xp = mcMMOPlayerPreXpGainEvent.getXpGained();
 
 
         if (SkillTools.isChildSkill(primarySkillType)) {
         if (SkillTools.isChildSkill(primarySkillType)) {

+ 1 - 1
src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java

@@ -428,7 +428,7 @@ public class PlayerProfile {
         int level = (ExperienceConfig.getInstance().getCumulativeCurveEnabled()) ? UserManager.getPlayer(playerName).getPowerLevel() : skills.get(primarySkillType);
         int level = (ExperienceConfig.getInstance().getCumulativeCurveEnabled()) ? UserManager.getPlayer(playerName).getPowerLevel() : skills.get(primarySkillType);
         FormulaType formulaType = ExperienceConfig.getInstance().getFormulaType();
         FormulaType formulaType = ExperienceConfig.getInstance().getFormulaType();
 
 
-        return mcMMO.getFormulaManager().getXPtoNextLevel(level, formulaType);
+        return mcMMO.p.getFormulaManager().getXPtoNextLevel(level, formulaType);
     }
     }
 
 
     private int getChildSkillLevel(PrimarySkillType primarySkillType) {
     private int getChildSkillLevel(PrimarySkillType primarySkillType) {

+ 3 - 3
src/main/java/com/gmail/nossr50/listeners/SelfListener.java

@@ -60,9 +60,9 @@ public class SelfListener implements Listener {
         }
         }
 
 
         final Set<Integer> levelsAchieved = new LinkedHashSet<>();
         final Set<Integer> levelsAchieved = new LinkedHashSet<>();
-        for(int i = 1; i <= event.getLevelsGained(); i++)
-        {
-            levelsAchieved.add(event.getSkillLevel() + i);
+        int startingLevel = event.getSkillLevel() - event.getLevelsGained();
+        for (int i = 0; i < event.getLevelsGained(); i++) {
+            levelsAchieved.add(startingLevel + (i + 1));
         }
         }
         plugin.getLevelUpCommandManager().apply(mcMMOPlayer, skill, levelsAchieved);
         plugin.getLevelUpCommandManager().apply(mcMMOPlayer, skill, levelsAchieved);
     }
     }

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

@@ -87,7 +87,7 @@ public class mcMMO extends JavaPlugin {
     private static SalvageableManager salvageableManager;
     private static SalvageableManager salvageableManager;
     private static ModManager         modManager;
     private static ModManager         modManager;
     private static DatabaseManager    databaseManager;
     private static DatabaseManager    databaseManager;
-    private static FormulaManager     formulaManager;
+    private FormulaManager formulaManager;
     private static UpgradeManager     upgradeManager;
     private static UpgradeManager     upgradeManager;
     private static LevelUpCommandManager levelUpCommandManager;
     private static LevelUpCommandManager levelUpCommandManager;
     private static MaterialMapStore materialMapStore;
     private static MaterialMapStore materialMapStore;
@@ -428,7 +428,7 @@ public class mcMMO extends JavaPlugin {
         xpEventEnabled = !xpEventEnabled;
         xpEventEnabled = !xpEventEnabled;
     }
     }
 
 
-    public static FormulaManager getFormulaManager() {
+    public FormulaManager getFormulaManager() {
         return formulaManager;
         return formulaManager;
     }
     }
 
 

+ 3 - 3
src/main/java/com/gmail/nossr50/runnables/database/FormulaConversionTask.java

@@ -52,7 +52,7 @@ public class FormulaConversionTask extends BukkitRunnable {
             convertedUsers++;
             convertedUsers++;
             Misc.printProgress(convertedUsers, DatabaseManager.progressInterval, startMillis);
             Misc.printProgress(convertedUsers, DatabaseManager.progressInterval, startMillis);
         }
         }
-        mcMMO.getFormulaManager().setPreviousFormulaType(formulaType);
+        mcMMO.p.getFormulaManager().setPreviousFormulaType(formulaType);
 
 
         sender.sendMessage(LocaleLoader.getString("Commands.mcconvert.Experience.Finish", formulaType.toString()));
         sender.sendMessage(LocaleLoader.getString("Commands.mcconvert.Experience.Finish", formulaType.toString()));
     }
     }
@@ -63,13 +63,13 @@ public class FormulaConversionTask extends BukkitRunnable {
         for (PrimarySkillType primarySkillType : SkillTools.NON_CHILD_SKILLS) {
         for (PrimarySkillType primarySkillType : SkillTools.NON_CHILD_SKILLS) {
             int oldLevel = profile.getSkillLevel(primarySkillType);
             int oldLevel = profile.getSkillLevel(primarySkillType);
             int oldXPLevel = profile.getSkillXpLevel(primarySkillType);
             int oldXPLevel = profile.getSkillXpLevel(primarySkillType);
-            int totalOldXP = mcMMO.getFormulaManager().calculateTotalExperience(oldLevel, oldXPLevel);
+            int totalOldXP = mcMMO.p.getFormulaManager().calculateTotalExperience(oldLevel, oldXPLevel);
 
 
             if (totalOldXP == 0) {
             if (totalOldXP == 0) {
                 continue;
                 continue;
             }
             }
 
 
-            int[] newExperienceValues = mcMMO.getFormulaManager().calculateNewLevel(primarySkillType, (int) Math.floor(totalOldXP / ExperienceConfig.getInstance().getExpModifier()), formulaType);
+            int[] newExperienceValues = mcMMO.p.getFormulaManager().calculateNewLevel(primarySkillType, (int) Math.floor(totalOldXP / ExperienceConfig.getInstance().getExpModifier()), formulaType);
             int newLevel = newExperienceValues[0];
             int newLevel = newExperienceValues[0];
             int newXPlevel = newExperienceValues[1];
             int newXPlevel = newExperienceValues[1];
 
 

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

@@ -263,7 +263,6 @@ public final class EventUtils {
             if (isLevelUp) {
             if (isLevelUp) {
                 NotificationManager.processLevelUpBroadcasting(mmoPlayer, skill, mmoPlayer.getSkillLevel(skill));
                 NotificationManager.processLevelUpBroadcasting(mmoPlayer, skill, mmoPlayer.getSkillLevel(skill));
                 NotificationManager.processPowerLevelUpBroadcasting(mmoPlayer, mmoPlayer.getPowerLevel());
                 NotificationManager.processPowerLevelUpBroadcasting(mmoPlayer, mmoPlayer.getPowerLevel());
-
             }
             }
         }
         }
 
 

+ 15 - 14
src/main/java/com/gmail/nossr50/util/experience/FormulaManager.java

@@ -6,6 +6,7 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.util.LogUtils;
 import com.gmail.nossr50.util.LogUtils;
 import org.bukkit.configuration.file.YamlConfiguration;
 import org.bukkit.configuration.file.YamlConfiguration;
+import org.jetbrains.annotations.VisibleForTesting;
 
 
 import java.io.File;
 import java.io.File;
 import java.util.HashMap;
 import java.util.HashMap;
@@ -25,7 +26,19 @@ public class FormulaManager {
     public FormulaManager() {
     public FormulaManager() {
         /* Setting for Classic Mode (Scales a lot of stuff up by * 10) */
         /* Setting for Classic Mode (Scales a lot of stuff up by * 10) */
         initExperienceNeededMaps();
         initExperienceNeededMaps();
-        loadFormula();
+        if (!formulaFile.exists()) {
+            previousFormula = FormulaType.UNKNOWN;
+            return;
+        }
+
+        previousFormula = FormulaType.getFormulaType(YamlConfiguration.loadConfiguration(formulaFile).getString("Previous_Formula", "UNKNOWN"));
+    }
+
+    @VisibleForTesting
+    public FormulaManager(FormulaType previousFormulaType) {
+        /* Setting for Classic Mode (Scales a lot of stuff up by * 10) */
+        initExperienceNeededMaps();
+        this.previousFormula = previousFormulaType;
     }
     }
 
 
     /**
     /**
@@ -122,7 +135,7 @@ public class FormulaManager {
          */
          */
 
 
         //TODO: When the heck is Unknown used?
         //TODO: When the heck is Unknown used?
-        if (formulaType == FormulaType.UNKNOWN) {
+        if (formulaType ==  null || formulaType == FormulaType.UNKNOWN) {
             formulaType = FormulaType.LINEAR;
             formulaType = FormulaType.LINEAR;
         }
         }
 
 
@@ -209,18 +222,6 @@ public class FormulaManager {
         }
         }
     }
     }
 
 
-    /**
-     * Load formula file.
-     */
-    public void loadFormula() {
-        if (!formulaFile.exists()) {
-            previousFormula = FormulaType.UNKNOWN;
-            return;
-        }
-
-        previousFormula = FormulaType.getFormulaType(YamlConfiguration.loadConfiguration(formulaFile).getString("Previous_Formula", "UNKNOWN"));
-    }
-
     /**
     /**
      * Save formula file.
      * Save formula file.
      */
      */

+ 1 - 1
src/main/java/com/gmail/nossr50/util/player/NotificationManager.java

@@ -139,7 +139,7 @@ public class NotificationManager {
                 notificationType, message, destination, mcMMO.p.getAdvancedConfig().doesNotificationSendCopyToChat(notificationType));
                 notificationType, message, destination, mcMMO.p.getAdvancedConfig().doesNotificationSendCopyToChat(notificationType));
 
 
         //Call event
         //Call event
-        Bukkit.getServer().getPluginManager().callEvent(customEvent);
+        mcMMO.p.getServer().getPluginManager().callEvent(customEvent);
         return customEvent;
         return customEvent;
     }
     }
 
 

+ 39 - 4
src/test/java/com/gmail/nossr50/MMOTestEnvironmentBasic.java

@@ -3,13 +3,18 @@ package com.gmail.nossr50;
 import com.gmail.nossr50.commands.levelup.LevelUpCommandManager;
 import com.gmail.nossr50.commands.levelup.LevelUpCommandManager;
 import com.gmail.nossr50.config.*;
 import com.gmail.nossr50.config.*;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
+import com.gmail.nossr50.datatypes.experience.FormulaType;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
 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.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent;
 import com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent;
+import com.gmail.nossr50.events.experience.McMMOPlayerPreXpGainEvent;
 import com.gmail.nossr50.listeners.SelfListener;
 import com.gmail.nossr50.listeners.SelfListener;
 import com.gmail.nossr50.util.*;
 import com.gmail.nossr50.util.*;
 import com.gmail.nossr50.util.blockmeta.ChunkManager;
 import com.gmail.nossr50.util.blockmeta.ChunkManager;
+import com.gmail.nossr50.util.experience.FormulaManager;
+import com.gmail.nossr50.util.player.NotificationManager;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.skills.SkillTools;
 import com.gmail.nossr50.util.skills.SkillTools;
@@ -26,8 +31,10 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.mockito.MockedStatic;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.Mockito;
+import org.mockito.internal.matchers.Not;
 
 
 import java.util.UUID;
 import java.util.UUID;
+import java.util.function.Function;
 
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mock;
@@ -39,11 +46,11 @@ public abstract class MMOTestEnvironmentBasic {
     protected MockedStatic<Bukkit> mockedBukkit;
     protected MockedStatic<Bukkit> mockedBukkit;
     protected MockedStatic<ChatConfig> mockedChatConfig;
     protected MockedStatic<ChatConfig> mockedChatConfig;
     protected MockedStatic<ExperienceConfig> experienceConfig;
     protected MockedStatic<ExperienceConfig> experienceConfig;
+    private MockedStatic<NotificationManager> mockedNotificationManager;
     protected MockedStatic<Permissions> mockedPermissions;
     protected MockedStatic<Permissions> mockedPermissions;
     protected MockedStatic<RankUtils> mockedRankUtils;
     protected MockedStatic<RankUtils> mockedRankUtils;
     protected MockedStatic<UserManager> mockedUserManager;
     protected MockedStatic<UserManager> mockedUserManager;
     protected MockedStatic<Misc> mockedMisc;
     protected MockedStatic<Misc> mockedMisc;
-    protected MockedStatic<SkillTools> mockedSkillTools;
     protected MockedStatic<EventUtils> mockedEventUtils;
     protected MockedStatic<EventUtils> mockedEventUtils;
     protected SelfListener selfListener;
     protected SelfListener selfListener;
     protected TransientEntityTracker transientEntityTracker;
     protected TransientEntityTracker transientEntityTracker;
@@ -57,6 +64,8 @@ public abstract class MMOTestEnvironmentBasic {
     protected PluginManager pluginManager;
     protected PluginManager pluginManager;
     protected World world;
     protected World world;
 
 
+    private FormulaManager formulaManager;
+
     /* Mocks */
     /* Mocks */
     protected Player player;
     protected Player player;
 
 
@@ -87,6 +96,10 @@ public abstract class MMOTestEnvironmentBasic {
         mcMMO.p = mock(mcMMO.class);
         mcMMO.p = mock(mcMMO.class);
         when(mcMMO.p.getLogger()).thenReturn(logger);
         when(mcMMO.p.getLogger()).thenReturn(logger);
 
 
+        // formula manager
+        formulaManager = new FormulaManager(FormulaType.UNKNOWN);
+        when(mcMMO.p.getFormulaManager()).thenReturn(formulaManager);
+
         // place store
         // place store
         chunkManager = mock(ChunkManager.class);
         chunkManager = mock(ChunkManager.class);
         when(mcMMO.getPlaceStore()).thenReturn(chunkManager);
         when(mcMMO.getPlaceStore()).thenReturn(chunkManager);
@@ -115,7 +128,7 @@ public abstract class MMOTestEnvironmentBasic {
         mockExperienceConfig();
         mockExperienceConfig();
 
 
         // wire skill tools
         // wire skill tools
-        this.skillTools = new SkillTools(mcMMO.p);
+        this.skillTools = Mockito.spy(new SkillTools(mcMMO.p));
         when(mcMMO.p.getSkillTools()).thenReturn(skillTools);
         when(mcMMO.p.getSkillTools()).thenReturn(skillTools);
 
 
         this.transientEntityTracker = new TransientEntityTracker();
         this.transientEntityTracker = new TransientEntityTracker();
@@ -123,6 +136,8 @@ public abstract class MMOTestEnvironmentBasic {
 
 
         mockPermissions();
         mockPermissions();
 
 
+        mockNotifications();
+
         mockedRankUtils = Mockito.mockStatic(RankUtils.class);
         mockedRankUtils = Mockito.mockStatic(RankUtils.class);
 
 
         // wire server
         // wire server
@@ -132,11 +147,15 @@ public abstract class MMOTestEnvironmentBasic {
         // wire plugin manager
         // wire plugin manager
         this.pluginManager = mock(PluginManager.class);
         this.pluginManager = mock(PluginManager.class);
         when(mockedServer.getPluginManager()).thenReturn(pluginManager);
         when(mockedServer.getPluginManager()).thenReturn(pluginManager);
+        // Process level up events in our self listener
         Mockito.doAnswer(invocation -> {
         Mockito.doAnswer(invocation -> {
             selfListener.onPlayerLevelUp(invocation.getArgument(0));
             selfListener.onPlayerLevelUp(invocation.getArgument(0));
             return null;
             return null;
         }).when(pluginManager).callEvent(any(McMMOPlayerLevelUpEvent.class));
         }).when(pluginManager).callEvent(any(McMMOPlayerLevelUpEvent.class));
 
 
+        // Don't process pre-gain events
+        Mockito.doAnswer((ignored) -> null).when(pluginManager).callEvent(any(McMMOPlayerPreXpGainEvent.class));
+
         // wire world
         // wire world
         this.world = mock(World.class);
         this.world = mock(World.class);
 
 
@@ -177,9 +196,12 @@ public abstract class MMOTestEnvironmentBasic {
     private void mockPermissions() {
     private void mockPermissions() {
         mockedPermissions = Mockito.mockStatic(Permissions.class);
         mockedPermissions = Mockito.mockStatic(Permissions.class);
         when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true);
         when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true);
-        // Mockito.when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true);
         when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true);
         when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true);
-        // Mockito.when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true);
+        when(Permissions.skillEnabled(any(Player.class), any(PrimarySkillType.class))).thenReturn(true);
+    }
+
+    private void mockNotifications() {
+        mockedNotificationManager = Mockito.mockStatic(NotificationManager.class);
     }
     }
 
 
     private void mockRankConfig() {
     private void mockRankConfig() {
@@ -203,6 +225,10 @@ public abstract class MMOTestEnvironmentBasic {
         generalConfig = mock(GeneralConfig.class);
         generalConfig = mock(GeneralConfig.class);
         when(generalConfig.getLocale()).thenReturn("en_US");
         when(generalConfig.getLocale()).thenReturn("en_US");
         when(mcMMO.p.getGeneralConfig()).thenReturn(generalConfig);
         when(mcMMO.p.getGeneralConfig()).thenReturn(generalConfig);
+
+        // Experience related
+        when(generalConfig.getLevelCap(any(PrimarySkillType.class))).thenReturn(Integer.MAX_VALUE);
+        when(generalConfig.getPowerLevelCap()).thenReturn(Integer.MAX_VALUE);
     }
     }
 
 
     private void mockExperienceConfig() {
     private void mockExperienceConfig() {
@@ -212,6 +238,12 @@ public abstract class MMOTestEnvironmentBasic {
 
 
         // Combat
         // Combat
         when(ExperienceConfig.getInstance().getCombatXP(EntityType.COW)).thenReturn(1D);
         when(ExperienceConfig.getInstance().getCombatXP(EntityType.COW)).thenReturn(1D);
+        when(ExperienceConfig.getInstance().getFormulaType()).thenReturn(FormulaType.LINEAR);
+        when(ExperienceConfig.getInstance().getBase(FormulaType.LINEAR)).thenReturn(1020);
+        when(ExperienceConfig.getInstance().getMultiplier(FormulaType.LINEAR)).thenReturn(20D);
+        when(ExperienceConfig.getInstance().getFormulaSkillModifier(any(PrimarySkillType.class))).thenReturn(1D);
+        when(ExperienceConfig.getInstance().getExperienceGainsGlobalMultiplier()).thenReturn(1D);
+        when(ExperienceConfig.getInstance().getExpModifier()).thenReturn(1D);
     }
     }
 
 
     protected void cleanupBaseEnvironment() {
     protected void cleanupBaseEnvironment() {
@@ -243,5 +275,8 @@ public abstract class MMOTestEnvironmentBasic {
         if (mockedEventUtils != null) {
         if (mockedEventUtils != null) {
             mockedEventUtils.close();
             mockedEventUtils.close();
         }
         }
+        if (mockedNotificationManager != null) {
+            mockedNotificationManager.close();
+        }
     }
     }
 }
 }

+ 76 - 6
src/test/java/com/gmail/nossr50/commands/levelup/LevelUpCommandTest.java

@@ -2,6 +2,7 @@ package com.gmail.nossr50.commands.levelup;
 
 
 import com.gmail.nossr50.MMOTestEnvironmentBasic;
 import com.gmail.nossr50.MMOTestEnvironmentBasic;
 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.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.events.experience.McMMOPlayerLevelUpEvent;
 import com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent;
@@ -11,11 +12,9 @@ import org.bukkit.Bukkit;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 import org.mockito.Mockito;
 
 
-import java.util.Set;
 import java.util.function.BiPredicate;
 import java.util.function.BiPredicate;
 
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.*;
 import static org.mockito.Mockito.*;
 
 
@@ -51,6 +50,71 @@ class LevelUpCommandTest extends MMOTestEnvironmentBasic {
         mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), any()), atLeast(5));
         mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), any()), atLeast(5));
     }
     }
 
 
+    @Test
+    void levelUpViaXPGainShouldRunCommandFiveTimes() {
+        // GIVEN level up command for Mining should always execute for Mining level up
+        assert mcMMO.p.getLevelUpCommandManager().isEmpty();
+        final String commandStr = "say hello";
+        final LevelUpCommand levelUpCommand
+                = buildLevelUpCommand(commandStr, (s, ignored) -> s == skill);
+        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
+
+        // WHEN player gains 5 levels in mining via command
+        assertEquals(0, mmoPlayer.getSkillLevel(skill));
+        int levelsGained = 5;
+        for (int i = 0; i < 5; i++) {
+            mmoPlayer.applyXpGain(skill, mmoPlayer.getProfile().getXpToLevel(skill), XPGainReason.COMMAND, XPGainSource.COMMAND);
+        }
+
+        // THEN the command should be checked for execution
+        verify(levelUpCommandManager, times(levelsGained)).apply(any(), any(), any());
+        verify(levelUpCommand, times(levelsGained)).process(any(), any(), any());
+
+        // THEN the command should have executed
+        verify(levelUpCommand, times(levelsGained)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
+        mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), any()), atLeast(5));
+    }
+
+    @Test
+    void levelUpViaXPGainShouldRunCommandFiveTimesWithPlaceholders() {
+        // GIVEN level up command for Mining should always execute for Mining level up
+        assert mcMMO.p.getLevelUpCommandManager().isEmpty();
+        String playerName = "Momshroom";
+        when (player.getName()).thenReturn(playerName);
+        assertEquals(player.getName(), playerName);
+        final String commandStr = "say hello %player%, you have reached level %level%";
+        final String expectedStr1 = "say hello " + playerName + ", you have reached level 1";
+        final String expectedStr2 = "say hello " + playerName + ", you have reached level 2";
+        final String expectedStr3 = "say hello " + playerName + ", you have reached level 3";
+        final String expectedStr4 = "say hello " + playerName + ", you have reached level 4";
+        final String expectedStr5 = "say hello " + playerName + ", you have reached level 5";
+        final LevelUpCommand levelUpCommand
+                = buildLevelUpCommand(commandStr, (s, ignored) -> s == skill);
+        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
+
+        // WHEN player gains 5 levels in mining via command
+        assertEquals(0, mmoPlayer.getSkillLevel(skill));
+        int levelsGained = 5;
+        for (int i = 0; i < 5; i++) {
+            mmoPlayer.applyXpGain(skill, mmoPlayer.getProfile().getXpToLevel(skill), XPGainReason.COMMAND, XPGainSource.COMMAND);
+        }
+
+        // THEN the command should be checked for execution
+        verify(levelUpCommandManager, times(levelsGained)).apply(any(), any(), any());
+        verify(levelUpCommand, times(levelsGained)).process(any(), any(), any());
+
+        // THEN the command should have executed
+        verify(levelUpCommand, times(levelsGained)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
+        mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), any()), atLeast(5));
+        // AND THEN the message for each level up should have happened at least once
+        // verify that Bukkit.dispatchCommand got executed at least 5 times with the correct injectedCommand
+        mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), eq(expectedStr1)));
+        mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), eq(expectedStr2)));
+        mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), eq(expectedStr3)));
+        mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), eq(expectedStr4)));
+        mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), eq(expectedStr5)));
+    }
+
     @Test
     @Test
     void levelUpShouldRunCommandFiveTimesWithPlaceholders() {
     void levelUpShouldRunCommandFiveTimesWithPlaceholders() {
         // GIVEN level up command for Mining should always execute for Mining level up
         // GIVEN level up command for Mining should always execute for Mining level up
@@ -80,7 +144,7 @@ class LevelUpCommandTest extends MMOTestEnvironmentBasic {
     }
     }
 
 
     @Test
     @Test
-    void levelUpShouldRunCommandFiveTimesWithPlaceholdersForLevel() {
+    void levelUpViaAddLevelsShouldRunCommandFiveTimesWithPlaceholdersForLevel() {
         // GIVEN level up command for Mining should always execute for Mining level up
         // GIVEN level up command for Mining should always execute for Mining level up
         assert mcMMO.p.getLevelUpCommandManager().isEmpty();
         assert mcMMO.p.getLevelUpCommandManager().isEmpty();
         String playerName = "Momshroom";
         String playerName = "Momshroom";
@@ -97,11 +161,17 @@ class LevelUpCommandTest extends MMOTestEnvironmentBasic {
         final LevelUpCommand levelUpCommand
         final LevelUpCommand levelUpCommand
                 = buildLevelUpCommand(commandStr, (s, ignored) -> s == skill);
                 = buildLevelUpCommand(commandStr, (s, ignored) -> s == skill);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
-        int levelsGained = 5;
 
 
         // WHEN player gains 5 levels in mining
         // WHEN player gains 5 levels in mining
-        McMMOPlayerLevelUpEvent event = new McMMOPlayerLevelUpEvent(player, skill, levelsGained, XPGainReason.PVE);
-        selfListener.onPlayerLevelUp(event);
+        int levelsGained = 5;
+        mmoPlayer.getProfile().addLevels(skill, levelsGained);
+        EventUtils.tryLevelChangeEvent(
+                player,
+                skill,
+                levelsGained,
+                mmoPlayer.getProfile().getSkillXpLevelRaw(skill),
+                true,
+                XPGainReason.COMMAND);
 
 
         // THEN the command should be checked for execution
         // THEN the command should be checked for execution
         verify(levelUpCommandManager).apply(any(), any(), any());
         verify(levelUpCommandManager).apply(any(), any(), any());