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

Add power level commands to commands on level up

nossr50 1 жил өмнө
parent
commit
847095aff4

+ 4 - 0
src/main/java/com/gmail/nossr50/commands/levelup/CommandsOnLevel.java

@@ -0,0 +1,4 @@
+package com.gmail.nossr50.commands.levelup;
+
+public interface CommandsOnLevel {
+}

+ 0 - 105
src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommand.java

@@ -1,105 +0,0 @@
-package com.gmail.nossr50.commands.levelup;
-
-import com.gmail.nossr50.datatypes.player.McMMOPlayer;
-import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.Set;
-import java.util.function.BiPredicate;
-
-import static java.util.Objects.requireNonNull;
-
-/**
- * Represents a command to be executed on a level up
- */
-public interface LevelUpCommand {
-    /**
-     * Process the command
-     *
-     * @param player            the player
-     * @param primarySkillType  the skill type
-     * @param levelsGained      the levels gained
-     */
-    void process(McMMOPlayer player, PrimarySkillType primarySkillType, Set<Integer> levelsGained);
-
-    /**
-     * Execute the command
-     * @param player the player
-     * @param primarySkillType the skill
-     * @param level the level of the skill
-     */
-    void executeCommand(McMMOPlayer player, PrimarySkillType primarySkillType, int level);
-
-    class LevelUpCommandBuilder {
-        private Set<PrimarySkillType> skillFilter = null;
-        private Set<Integer> levels = null;
-        private LinkedList<String> commands = null;
-        private BiPredicate<PrimarySkillType, Integer> predicate = null;
-        private boolean logInfo;
-
-        public LevelUpCommandBuilder() {
-            this.logInfo = false;
-        }
-
-        public LevelUpCommandBuilder withPredicate(BiPredicate<PrimarySkillType, Integer> predicate) {
-            this.predicate = predicate;
-            return this;
-        }
-
-        public LevelUpCommandBuilder withLogInfo(boolean logInfo) {
-            this.logInfo = logInfo;
-            return this;
-        }
-
-        public LevelUpCommandBuilder command(@NotNull String command) {
-            this.commands = new LinkedList<>();
-            this.commands.add(command);
-            return this;
-        }
-
-        public LevelUpCommandBuilder commands(@NotNull Collection<String> command) {
-            this.commands = new LinkedList<>(command);
-            return this;
-        }
-
-        public LevelUpCommandBuilder withLevels(@NotNull Collection<Integer> levels) {
-            requireNonNull(levels, "levels is null!");
-            this.levels = Set.copyOf(levels);
-            return this;
-        }
-
-        public LevelUpCommandBuilder withSkillFilter(@NotNull Set<PrimarySkillType> skillFilter) {
-            requireNonNull(skillFilter, "skillFilter is null!");
-            if (skillFilter.isEmpty()) {
-                throw new IllegalArgumentException("skillFilter is empty");
-            }
-            this.skillFilter = skillFilter;
-            return this;
-        }
-
-        public LevelUpCommandBuilder withSkillFilter(@NotNull PrimarySkillType skill) {
-            requireNonNull(skill, "skill is null!");
-            this.skillFilter = Set.of(skill);
-            return this;
-        }
-
-        public LevelUpCommand build() {
-            requireNonNull(commands, "commandStr is null");
-            if (predicate == null) {
-                requireNonNull(levels, "levels is null");
-
-                return new LevelUpCommandImpl((skill, level) -> {
-                    if (skillFilter == null) {
-                        return levels.contains(level);
-                    } else {
-                        return skillFilter.contains(skill) && levels.contains(level);
-                    }
-                }, commands, logInfo);
-            }
-
-            return new LevelUpCommandImpl(predicate, commands, logInfo);
-        }
-    }
-}

+ 43 - 24
src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommandManager.java

@@ -9,26 +9,32 @@ import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
 
+import static java.util.Objects.requireNonNull;
+
 /**
  * Manages commands to be executed on level up
  */
 public class LevelUpCommandManager {
-    private final @NotNull Set<LevelUpCommand> commands;
+    private final @NotNull Set<SkillLevelUpCommand> skillLevelCommands;
+    private final @NotNull Set<PowerLevelUpCommand> powerLevelUpCommands;
     private final @NotNull mcMMO plugin;
 
     public LevelUpCommandManager(@NotNull mcMMO plugin) {
-        this.plugin = plugin;
-        this.commands = new HashSet<>();
+        this.plugin = requireNonNull(plugin, "plugin cannot be null");
+        this.skillLevelCommands = new HashSet<>();
+        this.powerLevelUpCommands = new HashSet<>();
     }
 
-    /**
-     * Register a level up command to be executed on level up
-     *
-     * @param levelUpCommand the levelUpCommand
-     */
-    public void registerCommand(@NotNull LevelUpCommand levelUpCommand) {
-        commands.add(levelUpCommand);
-        mcMMO.p.getLogger().info("Registered levelUpCommand on level up: " + levelUpCommand);
+    public void registerCommand(@NotNull SkillLevelUpCommand skillLevelUpCommand) {
+        requireNonNull(skillLevelUpCommand, "skillLevelUpCommand cannot be null");
+        skillLevelCommands.add(skillLevelUpCommand);
+        mcMMO.p.getLogger().info("Registered level up command - SkillLevelUpCommand: " + skillLevelUpCommand);
+    }
+
+    public void registerCommand(@NotNull PowerLevelUpCommand powerLevelUpCommand) {
+        requireNonNull(powerLevelUpCommand, "powerLevelUpCommand cannot be null");
+        powerLevelUpCommands.add(powerLevelUpCommand);
+        mcMMO.p.getLogger().info("Registered level up command - PowerLevelUpCommand: " + powerLevelUpCommand);
     }
 
     /**
@@ -38,29 +44,41 @@ public class LevelUpCommandManager {
      * @param primarySkillType  the skill type
      * @param levelsGained      the levels gained
      */
-    public void apply(@NotNull McMMOPlayer mmoPlayer, @NotNull PrimarySkillType primarySkillType, Set<Integer> levelsGained) {
+    public void applySkillLevelUp(@NotNull McMMOPlayer mmoPlayer, @NotNull PrimarySkillType primarySkillType, Set<Integer> levelsGained) {
         if (!mmoPlayer.getPlayer().isOnline()) {
             return;
         }
 
-        for (LevelUpCommand command : commands) {
+        for (SkillLevelUpCommand command : skillLevelCommands) {
             command.process(mmoPlayer, primarySkillType, levelsGained);
         }
     }
 
+    public void applyPowerLevelUp(@NotNull McMMOPlayer mmoPlayer, Set<Integer> levelsGained) {
+        if (!mmoPlayer.getPlayer().isOnline()) {
+            return;
+        }
+
+        for (PowerLevelUpCommand command : powerLevelUpCommands) {
+            command.process(mmoPlayer, levelsGained);
+        }
+    }
+
+    public @NotNull Set<SkillLevelUpCommand> getSkillLevelCommands() {
+        return skillLevelCommands;
+    }
+
+    public @NotNull Set<PowerLevelUpCommand> getPowerLevelUpCommands() {
+        return powerLevelUpCommands;
+    }
+
     /**
      * Clear all registered commands
      */
     public void clear() {
         mcMMO.p.getLogger().info("Clearing registered commands on level up");
-        commands.clear();
-    }
-
-    /**
-     * @return true if there are no registered commands
-     */
-    public boolean isEmpty() {
-        return commands.isEmpty();
+        skillLevelCommands.clear();
+        powerLevelUpCommands.clear();
     }
 
     @Override
@@ -68,18 +86,19 @@ public class LevelUpCommandManager {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         LevelUpCommandManager that = (LevelUpCommandManager) o;
-        return Objects.equals(commands, that.commands) && Objects.equals(plugin, that.plugin);
+        return Objects.equals(skillLevelCommands, that.skillLevelCommands) && Objects.equals(powerLevelUpCommands, that.powerLevelUpCommands) && Objects.equals(plugin, that.plugin);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(commands, plugin);
+        return Objects.hash(skillLevelCommands, powerLevelUpCommands, plugin);
     }
 
     @Override
     public String toString() {
         return "LevelUpCommandManager{" +
-                "commands=" + commands +
+                "skillLevelCommands=" + skillLevelCommands +
+                ", powerLevelUpCommands=" + powerLevelUpCommands +
                 ", plugin=" + plugin +
                 '}';
     }

+ 98 - 0
src/main/java/com/gmail/nossr50/commands/levelup/PowerLevelUpCommand.java

@@ -0,0 +1,98 @@
+package com.gmail.nossr50.commands.levelup;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.LogUtils;
+import org.bukkit.Bukkit;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.LinkedList;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
+
+public class PowerLevelUpCommand implements CommandsOnLevel {
+    private final Predicate<Integer> predicate;
+    private final boolean logInfo;
+    private final @NotNull LinkedList<String> commands;
+
+    public PowerLevelUpCommand(@NotNull Predicate<Integer> predicate, @NotNull String command, boolean logInfo) {
+        this.predicate = predicate;
+        this.commands = new LinkedList<>();
+        this.commands.add(command);
+        this.logInfo = logInfo;
+    }
+
+    public PowerLevelUpCommand(@NotNull Predicate<Integer> predicate, @NotNull LinkedList<String> commands, boolean logInfo) {
+        this.predicate = predicate;
+        this.commands = commands;
+        this.logInfo = logInfo;
+    }
+
+    public void process(McMMOPlayer player, Set<Integer> levelsGained) {
+        for (int i : levelsGained) {
+            if (predicate.test(i)) {
+                // execute command via server console in Bukkit
+                if(logInfo) {
+                    mcMMO.p.getLogger().info("Executing command: " + commands);
+                } else {
+                    LogUtils.debug(mcMMO.p.getLogger(), "Executing command: " + commands);
+                }
+                executeCommand(player, i);
+            }
+        }
+    }
+
+    public void executeCommand(McMMOPlayer player,  int level) {
+        // TODO: Change this to debug later
+        mcMMO.p.getLogger().info("Executing commands for level up: " + commands);
+        for (String command : commands) {
+            // TODO: Change this to debug later
+            mcMMO.p.getLogger().info("Executing command: " + command);
+            String injectedCommand = injectedCommand(command, player, level);
+            // TODO: Remove verbose logging later
+            if (!injectedCommand.equalsIgnoreCase(command)) {
+                mcMMO.p.getLogger().info(("Command has been injected with new values: " + injectedCommand));
+            }
+            Bukkit.dispatchCommand(Bukkit.getConsoleSender(), injectedCommand);
+        }
+    }
+
+    private String injectedCommand(String command, McMMOPlayer player, int level) {
+        // replace %player% with player name, %skill% with skill name, and %level% with level
+        command = safeReplace(command, "%player%", player.getPlayer().getName());
+        command = safeReplace(command, "%skill%", "power level");
+        command = safeReplace(command, "%level%", String.valueOf(level));
+        return command;
+    }
+
+    private String safeReplace(String targetStr, String toReplace, String replacement) {
+        if (replacement == null) {
+            return targetStr;
+        }
+
+        return targetStr.replace(toReplace, replacement);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PowerLevelUpCommand that = (PowerLevelUpCommand) o;
+        return logInfo == that.logInfo && Objects.equals(predicate, that.predicate) && Objects.equals(commands, that.commands);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(predicate, logInfo, commands);
+    }
+
+    @Override
+    public String toString() {
+        return "PowerLevelUpCommand{" +
+                "predicate=" + predicate +
+                ", logInfo=" + logInfo +
+                ", commands=" + commands +
+                '}';
+    }
+}

+ 59 - 0
src/main/java/com/gmail/nossr50/commands/levelup/PowerLevelUpCommandBuilder.java

@@ -0,0 +1,59 @@
+package com.gmail.nossr50.commands.levelup;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import static java.util.Objects.requireNonNull;
+
+public class PowerLevelUpCommandBuilder {
+    private Set<Integer> levels = null;
+    private LinkedList<String> commands = null;
+    private Predicate<Integer> predicate = null;
+    private boolean logInfo;
+
+    public PowerLevelUpCommandBuilder() {
+        this.logInfo = false;
+    }
+
+    public PowerLevelUpCommandBuilder withPredicate(Predicate<Integer> predicate) {
+        this.predicate = predicate;
+        return this;
+    }
+
+    public PowerLevelUpCommandBuilder withLogInfo(boolean logInfo) {
+        this.logInfo = logInfo;
+        return this;
+    }
+
+    public PowerLevelUpCommandBuilder command(@NotNull String command) {
+        this.commands = new LinkedList<>();
+        this.commands.add(command);
+        return this;
+    }
+
+    public PowerLevelUpCommandBuilder commands(@NotNull Collection<String> command) {
+        this.commands = new LinkedList<>(command);
+        return this;
+    }
+
+    public PowerLevelUpCommandBuilder withLevels(@NotNull Collection<Integer> levels) {
+        requireNonNull(levels, "levels is null!");
+        this.levels = Set.copyOf(levels);
+        return this;
+    }
+
+    public PowerLevelUpCommand build() {
+        requireNonNull(commands, "commandStr is null");
+        if (predicate == null) {
+            requireNonNull(levels, "levels is null");
+
+            return new PowerLevelUpCommand((level) -> levels.contains(level), commands, logInfo);
+        }
+
+        return new PowerLevelUpCommand(predicate, commands, logInfo);
+    }
+}

+ 4 - 5
src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommandImpl.java → src/main/java/com/gmail/nossr50/commands/levelup/SkillLevelUpCommand.java

@@ -12,25 +12,24 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.function.BiPredicate;
 
-public class LevelUpCommandImpl implements LevelUpCommand {
+public class SkillLevelUpCommand implements CommandsOnLevel {
     private final BiPredicate<PrimarySkillType, Integer> predicate;
     private final boolean logInfo;
     private final @NotNull LinkedList<String> commands;
 
-    public LevelUpCommandImpl(@NotNull BiPredicate<PrimarySkillType, Integer> predicate, @NotNull String command, boolean logInfo) {
+    public SkillLevelUpCommand(@NotNull BiPredicate<PrimarySkillType, Integer> predicate, @NotNull String command, boolean logInfo) {
         this.predicate = predicate;
         this.commands = new LinkedList<>();
         this.commands.add(command);
         this.logInfo = logInfo;
     }
 
-    public LevelUpCommandImpl(@NotNull BiPredicate<PrimarySkillType, Integer> predicate, @NotNull LinkedList<String> commands, boolean logInfo) {
+    public SkillLevelUpCommand(@NotNull BiPredicate<PrimarySkillType, Integer> predicate, @NotNull LinkedList<String> commands, boolean logInfo) {
         this.predicate = predicate;
         this.commands = commands;
         this.logInfo = logInfo;
     }
 
-    @Override
     public void process(McMMOPlayer player, PrimarySkillType primarySkillType, Set<Integer> levelsGained) {
         for (int i : levelsGained) {
             if (predicate.test(primarySkillType, i)) {
@@ -80,7 +79,7 @@ public class LevelUpCommandImpl implements LevelUpCommand {
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
-        LevelUpCommandImpl that = (LevelUpCommandImpl) o;
+        SkillLevelUpCommand that = (SkillLevelUpCommand) o;
         return logInfo == that.logInfo && Objects.equals(predicate, that.predicate) && Objects.equals(commands, that.commands);
     }
 

+ 82 - 0
src/main/java/com/gmail/nossr50/commands/levelup/SkillLevelUpCommandBuilder.java

@@ -0,0 +1,82 @@
+package com.gmail.nossr50.commands.levelup;
+
+import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.function.BiPredicate;
+
+import static java.util.Objects.requireNonNull;
+
+public class SkillLevelUpCommandBuilder {
+    private Set<PrimarySkillType> skillFilter = null;
+    private Set<Integer> levels = null;
+    private LinkedList<String> commands = null;
+    private BiPredicate<PrimarySkillType, Integer> predicate = null;
+    private boolean logInfo;
+
+    public SkillLevelUpCommandBuilder() {
+        this.logInfo = false;
+    }
+
+    public SkillLevelUpCommandBuilder withPredicate(BiPredicate<PrimarySkillType, Integer> predicate) {
+        this.predicate = predicate;
+        return this;
+    }
+
+    public SkillLevelUpCommandBuilder withLogInfo(boolean logInfo) {
+        this.logInfo = logInfo;
+        return this;
+    }
+
+    public SkillLevelUpCommandBuilder command(@NotNull String command) {
+        this.commands = new LinkedList<>();
+        this.commands.add(command);
+        return this;
+    }
+
+    public SkillLevelUpCommandBuilder commands(@NotNull Collection<String> command) {
+        this.commands = new LinkedList<>(command);
+        return this;
+    }
+
+    public SkillLevelUpCommandBuilder withLevels(@NotNull Collection<Integer> levels) {
+        requireNonNull(levels, "levels is null!");
+        this.levels = Set.copyOf(levels);
+        return this;
+    }
+
+    public SkillLevelUpCommandBuilder withSkillFilter(@NotNull Set<PrimarySkillType> skillFilter) {
+        requireNonNull(skillFilter, "skillFilter is null!");
+        if (skillFilter.isEmpty()) {
+            throw new IllegalArgumentException("skillFilter is empty");
+        }
+        this.skillFilter = skillFilter;
+        return this;
+    }
+
+    public SkillLevelUpCommandBuilder withSkillFilter(@NotNull PrimarySkillType skill) {
+        requireNonNull(skill, "skill is null!");
+        this.skillFilter = Set.of(skill);
+        return this;
+    }
+
+    public SkillLevelUpCommand build() {
+        requireNonNull(commands, "commandStr is null");
+        if (predicate == null) {
+            requireNonNull(levels, "levels is null");
+
+            return new SkillLevelUpCommand((skill, level) -> {
+                if (skillFilter == null) {
+                    return levels.contains(level);
+                } else {
+                    return skillFilter.contains(skill) && levels.contains(level);
+                }
+            }, commands, logInfo);
+        }
+
+        return new SkillLevelUpCommand(predicate, commands, logInfo);
+    }
+}

+ 73 - 9
src/main/java/com/gmail/nossr50/config/CommandOnLevelUpConfig.java

@@ -1,6 +1,9 @@
 package com.gmail.nossr50.config;
 
-import com.gmail.nossr50.commands.levelup.LevelUpCommand;
+import com.gmail.nossr50.commands.levelup.PowerLevelUpCommand;
+import com.gmail.nossr50.commands.levelup.PowerLevelUpCommandBuilder;
+import com.gmail.nossr50.commands.levelup.SkillLevelUpCommand;
+import com.gmail.nossr50.commands.levelup.SkillLevelUpCommandBuilder;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.util.LogUtils;
@@ -22,6 +25,7 @@ public class CommandOnLevelUpConfig extends BukkitConfig {
     public static final String CONDITION_SECTION = "condition";
     public static final String ENABLED = "enabled";
     public static final String COMMANDS = "commands";
+    public static final String POWER_LEVEL_SECTION = "power_level";
 
     public CommandOnLevelUpConfig(@NotNull File dataFolder) {
         super("levelupcommands.yml", dataFolder);
@@ -44,20 +48,26 @@ public class CommandOnLevelUpConfig extends BukkitConfig {
                 continue;
             }
 
-            LevelUpCommand levelUpCommand = buildCommand(commandSection);
-            if (levelUpCommand == null) {
+            SkillLevelUpCommand skillLevelUpCommand = buildSkillLevelUpCommand(commandSection);
+            PowerLevelUpCommand powerLevelUpCommand = buildPowerLevelUpCommand(commandSection);
+
+            if (skillLevelUpCommand == null && powerLevelUpCommand == null) {
                 mcMMO.p.getLogger().severe("Invalid command format for key: " + key);
-                continue;
             } else {
-                mcMMO.p.getLogger().info("Command successfully loaded from config for key: " + key);
-                mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
+                if(skillLevelUpCommand != null) {
+                    mcMMO.p.getLevelUpCommandManager().registerCommand(skillLevelUpCommand);
+                    mcMMO.p.getLogger().info("Skill Level up command successfully loaded from config for key: " + key);
+                }
+                if(powerLevelUpCommand != null) {
+                    mcMMO.p.getLevelUpCommandManager().registerCommand(powerLevelUpCommand);
+                    mcMMO.p.getLogger().info("Power Level up command successfully loaded from config for key: " + key);
+                }
             }
         }
     }
 
-    @Nullable
-    private LevelUpCommand buildCommand(final ConfigurationSection commandSection) {
-        LevelUpCommand.LevelUpCommandBuilder builder = new LevelUpCommand.LevelUpCommandBuilder();
+    private @NotNull SkillLevelUpCommand buildSkillLevelUpCommand(final ConfigurationSection commandSection) {
+        SkillLevelUpCommandBuilder builder = new SkillLevelUpCommandBuilder();
         // check if command is enabled
         if (!commandSection.getBoolean(ENABLED, true)) {
             return null;
@@ -129,6 +139,60 @@ public class CommandOnLevelUpConfig extends BukkitConfig {
         return builder.build();
     }
 
+    private @Nullable PowerLevelUpCommand buildPowerLevelUpCommand(final ConfigurationSection commandSection) {
+        PowerLevelUpCommandBuilder builder = new PowerLevelUpCommandBuilder();
+        // check if command is enabled
+        if (!commandSection.getBoolean(ENABLED, true)) {
+            return null;
+        }
+
+        /* Condition Section */
+        ConfigurationSection condition = commandSection.getConfigurationSection(CONDITION_SECTION);
+        if (condition == null) {
+            mcMMO.p.getLogger().severe("No condition section found for command named " + commandSection.getName());
+            return null;
+        }
+
+        // No power level condition
+        if (!condition.contains(POWER_LEVEL_SECTION)) {
+            return null;
+        }
+
+        // for now only simple condition is supported
+        if (!condition.contains(LEVELS_SECTION)) {
+            mcMMO.p.getLogger().severe("No condition.levels section found for power level command named "
+                    + commandSection.getName());
+            return null;
+        }
+
+        Collection<Integer> levels = condition.getIntegerList(LEVELS_SECTION);
+        if (levels.isEmpty()) {
+            mcMMO.p.getLogger().severe("No valid levels found in condition.levels for power level command named "
+                    + commandSection.getName());
+            return null;
+        }
+        builder.withLevels(levels);
+
+        // commands
+        if (commandSection.isString(COMMANDS)) {
+            String command = commandSection.getString(COMMANDS);
+            if (command != null) {
+                builder.command(command);
+            }
+        } else {
+            List<String> commands = commandSection.getStringList(COMMANDS);
+            if (commands.isEmpty()) {
+                mcMMO.p.getLogger().severe("No commands defined for power level command named "
+                        + commandSection.getName());
+                return null;
+            } else {
+                builder.commands(commands);
+            }
+        }
+
+        return builder.build();
+    }
+
     private Set<PrimarySkillType> getSkillsFromFilter(Set<String> skillFilter) {
         return mcMMO.p.getSkillTools().matchSkills(skillFilter);
     }

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

@@ -54,7 +54,6 @@ import com.gmail.nossr50.util.sounds.SoundManager;
 import com.gmail.nossr50.util.sounds.SoundType;
 import net.kyori.adventure.identity.Identified;
 import net.kyori.adventure.identity.Identity;
-import org.bukkit.Bukkit;
 import org.bukkit.GameMode;
 import org.bukkit.Location;
 import org.bukkit.block.Block;

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

@@ -60,11 +60,18 @@ public class SelfListener implements Listener {
         }
 
         final Set<Integer> levelsAchieved = new LinkedHashSet<>();
+        final Set<Integer> powerLevelsAchieved = new LinkedHashSet<>();
         int startingLevel = event.getSkillLevel() - event.getLevelsGained();
+        int startingPowerLevel = mcMMOPlayer.getPowerLevel() - event.getLevelsGained();
         for (int i = 0; i < event.getLevelsGained(); i++) {
             levelsAchieved.add(startingLevel + (i + 1));
         }
-        plugin.getLevelUpCommandManager().apply(mcMMOPlayer, skill, levelsAchieved);
+        for (int i = 0; i < event.getLevelsGained(); i++) {
+            powerLevelsAchieved.add(startingPowerLevel + (i + 1));
+        }
+
+        plugin.getLevelUpCommandManager().applySkillLevelUp(mcMMOPlayer, skill, levelsAchieved);
+        plugin.getLevelUpCommandManager().applyPowerLevelUp(mcMMOPlayer, powerLevelsAchieved);
     }
 
     @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)

+ 28 - 28
src/test/java/com/gmail/nossr50/MMOTestEnvironmentBasic.java

@@ -24,17 +24,14 @@ import org.bukkit.World;
 import org.bukkit.command.ConsoleCommandSender;
 import org.bukkit.entity.EntityType;
 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.mockito.MockedStatic;
 import org.mockito.Mockito;
-import org.mockito.internal.matchers.Not;
 
 import java.util.UUID;
-import java.util.function.Function;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
@@ -67,15 +64,7 @@ public abstract class MMOTestEnvironmentBasic {
     private FormulaManager formulaManager;
 
     /* Mocks */
-    protected Player player;
-
-    protected UUID playerUUID = UUID.randomUUID();
-    protected ItemStack itemInMainHand;
-
     protected PlayerInventory playerInventory;
-    protected PlayerProfile playerProfile;
-    protected McMMOPlayer mmoPlayer;
-    protected String playerName = "testPlayer";
 
     protected ChunkManager chunkManager;
 
@@ -163,29 +152,12 @@ public abstract class MMOTestEnvironmentBasic {
         this.mockedMisc = Mockito.mockStatic(Misc.class);
         // Mockito.when(Misc.getBlockCenter(any())).thenReturn(new Location(world, 0, 0, 0));
 
-        // setup player and player related mocks after everything else
-        this.player = mock(Player.class);
-        when(player.getUniqueId()).thenReturn(playerUUID);
-
-        // wire inventory
-        this.playerInventory = mock(PlayerInventory.class);
-        when(player.getInventory()).thenReturn(playerInventory);
-
-        // PlayerProfile and McMMOPlayer are partially mocked
-        playerProfile = Mockito.spy(new PlayerProfile("testPlayer", player.getUniqueId(), 0));
-        when(playerProfile.isLoaded()).thenReturn(true);
-        mmoPlayer = Mockito.spy(new McMMOPlayer(player, playerProfile));
-
         // wire user manager
         this.mockedUserManager = Mockito.mockStatic(UserManager.class);
-        when(UserManager.getPlayer(player)).thenReturn(mmoPlayer);
 
         // Self listener
         selfListener = Mockito.spy(new SelfListener(mcMMO.p));
 
-        // Player online status
-        when(player.isOnline()).thenReturn(true);
-
         // Console command sender
         consoleCommandSender = mock(ConsoleCommandSender.class);
         when(consoleCommandSender.getName()).thenReturn("CONSOLE");
@@ -279,4 +251,32 @@ public abstract class MMOTestEnvironmentBasic {
             mockedNotificationManager.close();
         }
     }
+
+    protected McMMOPlayer getMMOPlayer(UUID playerUUID, String playerName, int startingLevel) {
+        Player player = mock(Player.class);
+        // Player UUID
+        when(player.getUniqueId()).thenReturn(playerUUID);
+        // Player name
+        when(player.getName()).thenReturn(playerName);
+
+        // Player Inventory
+        this.playerInventory = mock(PlayerInventory.class);
+        when(player.getInventory()).thenReturn(playerInventory);
+
+        // Player Profile
+        PlayerProfile playerProfile = Mockito.spy(new PlayerProfile(playerName, player.getUniqueId(), startingLevel));
+        when(playerProfile.isLoaded()).thenReturn(true);
+        // McMMOPlayer
+        McMMOPlayer mmoPlayer = Mockito.spy(new McMMOPlayer(player, playerProfile));
+        // Wire UserManager
+        when(UserManager.getPlayer(player)).thenReturn(mmoPlayer);
+        // Player is online
+        when(player.isOnline()).thenReturn(true);
+
+        return mmoPlayer;
+    }
+
+    protected McMMOPlayer getMMOPlayer(UUID playerUUID, String playerName) {
+        return getMMOPlayer(playerUUID, playerName, 0);
+    }
 }

+ 138 - 75
src/test/java/com/gmail/nossr50/commands/levelup/LevelUpCommandTest.java

@@ -9,98 +9,98 @@ import com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.util.EventUtils;
 import org.bukkit.Bukkit;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
+import java.util.UUID;
 import java.util.function.BiPredicate;
+import java.util.function.Predicate;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.*;
 
 class LevelUpCommandTest extends MMOTestEnvironmentBasic {
-    private final PrimarySkillType skill = PrimarySkillType.MINING;
-    private final PrimarySkillType otherSkill = PrimarySkillType.WOODCUTTING;
+    private final PrimarySkillType mining = PrimarySkillType.MINING;
+    private final PrimarySkillType woodcutting = PrimarySkillType.WOODCUTTING;
+    private McMMOPlayer mmoPlayer;
+    private final String playerName = "Momshroom";
+
+    @BeforeEach
+    void beforeEach() {
+        mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().clear();
+        mcMMO.p.getLevelUpCommandManager().getPowerLevelUpCommands().clear();
+
+        this.mmoPlayer = getMMOPlayer(UUID.randomUUID(), playerName, 0);
+    }
 
     @Test
-    void levelUpShouldRunCommandFiveTimes() {
+    void skillLevelUpShouldRunFiveTimes() {
         // GIVEN level up command for Mining should always execute for Mining level up
-        assert mcMMO.p.getLevelUpCommandManager().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
         final String commandStr = "say hello";
-        final LevelUpCommand levelUpCommand
-                = buildLevelUpCommand(commandStr, (s, ignored) -> s == skill);
+        final SkillLevelUpCommand levelUpCommand
+                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
         // WHEN player gains 5 levels in mining via command
-        assertEquals(0, mmoPlayer.getSkillLevel(skill));
-        int levelsGained = 5;
-        EventUtils.tryLevelChangeEvent(
-                player,
-                skill,
-                levelsGained,
-                mmoPlayer.getProfile().getSkillXpLevelRaw(skill),
-                true,
-                XPGainReason.COMMAND);
+        levelPlayerViaXP(mmoPlayer, mining, 5);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager).apply(any(), any(), any());
-        verify(levelUpCommand).process(any(), any(), any());
+        verify(levelUpCommandManager, atLeastOnce()).applySkillLevelUp(any(), any(), any());
+        verify(levelUpCommand, atLeastOnce()).process(any(), any(), any());
+
         // THEN the command should have executed
-        verify(levelUpCommand, times(levelsGained)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
+        verify(levelUpCommand, times(5)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
         mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), any()), atLeast(5));
     }
 
     @Test
-    void levelUpViaXPGainShouldRunCommandFiveTimes() {
+    void skillLevelUpViaXPGainShouldRunFiveTimes() {
         // GIVEN level up command for Mining should always execute for Mining level up
-        assert mcMMO.p.getLevelUpCommandManager().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
         final String commandStr = "say hello";
-        final LevelUpCommand levelUpCommand
-                = buildLevelUpCommand(commandStr, (s, ignored) -> s == skill);
+        final SkillLevelUpCommand levelUpCommand
+                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
         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);
-        }
+        levelPlayerViaXP(mmoPlayer, mining, 5);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager, times(levelsGained)).apply(any(), any(), any());
-        verify(levelUpCommand, times(levelsGained)).process(any(), any(), any());
+        verify(levelUpCommandManager, times(5)).applySkillLevelUp(any(), any(), any());
+        verify(levelUpCommand, times(5)).process(any(), any(), any());
 
         // THEN the command should have executed
-        verify(levelUpCommand, times(levelsGained)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
+        verify(levelUpCommand, times(5)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
         mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), any()), atLeast(5));
     }
 
     @Test
-    void levelUpViaXPGainShouldRunCommandFiveTimesWithPlaceholders() {
+    void skillLevelUpViaXPGainShouldRunCommandFiveTimesWithPlaceholders() {
         // 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);
+        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        assertEquals(mmoPlayer.getPlayer().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);
+        final SkillLevelUpCommand levelUpCommand
+                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
         // WHEN player gains 5 levels in mining via command
-        assertEquals(0, mmoPlayer.getSkillLevel(skill));
+        assertEquals(0, mmoPlayer.getSkillLevel(mining));
         int levelsGained = 5;
         for (int i = 0; i < 5; i++) {
-            mmoPlayer.applyXpGain(skill, mmoPlayer.getProfile().getXpToLevel(skill), XPGainReason.COMMAND, XPGainSource.COMMAND);
+            mmoPlayer.applyXpGain(mining, mmoPlayer.getProfile().getXpToLevel(mining), XPGainReason.COMMAND, XPGainSource.COMMAND);
         }
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager, times(levelsGained)).apply(any(), any(), any());
+        verify(levelUpCommandManager, times(levelsGained)).applySkillLevelUp(any(), any(), any());
         verify(levelUpCommand, times(levelsGained)).process(any(), any(), any());
 
         // THEN the command should have executed
@@ -116,26 +116,24 @@ class LevelUpCommandTest extends MMOTestEnvironmentBasic {
     }
 
     @Test
-    void levelUpShouldRunCommandFiveTimesWithPlaceholders() {
+    void skillLevelUpShouldRunCommandFiveTimesWithPlaceholders() {
         // 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);
+        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        assertEquals(mmoPlayer.getPlayer().getName(), playerName);
 
         final String commandStr = "say hello %player%";
         final String expectedStr = "say hello " + playerName;
-        final LevelUpCommand levelUpCommand
-                = buildLevelUpCommand(commandStr, (s, ignored) -> s == skill);
+        final SkillLevelUpCommand levelUpCommand
+                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
         int levelsGained = 5;
 
         // WHEN player gains 5 levels in mining
-        McMMOPlayerLevelUpEvent event = new McMMOPlayerLevelUpEvent(player, skill, levelsGained, XPGainReason.PVE);
+        McMMOPlayerLevelUpEvent event = new McMMOPlayerLevelUpEvent(mmoPlayer.getPlayer(), mining, levelsGained, XPGainReason.PVE);
         selfListener.onPlayerLevelUp(event);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager).apply(any(), any(), any());
+        verify(levelUpCommandManager).applySkillLevelUp(any(), any(), any());
         verify(levelUpCommand).process(any(), any(), any());
         // THEN the command should have executed
         verify(levelUpCommand, times(levelsGained)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
@@ -144,12 +142,10 @@ class LevelUpCommandTest extends MMOTestEnvironmentBasic {
     }
 
     @Test
-    void levelUpViaAddLevelsShouldRunCommandFiveTimesWithPlaceholdersForLevel() {
+    void skillLevelUpViaAddLevelsShouldRunCommandFiveTimesWithPlaceholdersForLevel() {
         // 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);
+        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        assertEquals(mmoPlayer.getPlayer().getName(), playerName);
 
         final String commandStr = "say hello %player%, you have reached level %level%";
         final String expectedStr1 = "say hello " + playerName + ", you have reached level 1";
@@ -158,23 +154,23 @@ class LevelUpCommandTest extends MMOTestEnvironmentBasic {
         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);
+        final SkillLevelUpCommand levelUpCommand
+                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
         // WHEN player gains 5 levels in mining
         int levelsGained = 5;
-        mmoPlayer.getProfile().addLevels(skill, levelsGained);
+        mmoPlayer.getProfile().addLevels(mining, levelsGained);
         EventUtils.tryLevelChangeEvent(
-                player,
-                skill,
+                mmoPlayer.getPlayer(),
+                mining,
                 levelsGained,
-                mmoPlayer.getProfile().getSkillXpLevelRaw(skill),
+                mmoPlayer.getProfile().getSkillXpLevelRaw(mining),
                 true,
                 XPGainReason.COMMAND);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager).apply(any(), any(), any());
+        verify(levelUpCommandManager).applySkillLevelUp(any(), any(), any());
         verify(levelUpCommand).process(any(), any(), any());
         // THEN the command should have executed
         verify(levelUpCommand, times(levelsGained)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
@@ -187,53 +183,120 @@ class LevelUpCommandTest extends MMOTestEnvironmentBasic {
     }
 
     @Test
-    void levelUpShouldRunCommandAtLeastOnce() {
+    void skillLevelUpShouldRunCommandAtLeastOnce() {
         // GIVEN level up command for Mining should always execute for Mining level up
-        assert mcMMO.p.getLevelUpCommandManager().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
         final String commandStr = "say hello";
-        final LevelUpCommand levelUpCommand
-                = buildLevelUpCommand(commandStr, (s, ignored) -> s == skill);
+        final SkillLevelUpCommand levelUpCommand
+                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
         int levelsGained = 1;
         // WHEN player gains 5 levels in mining
-        McMMOPlayerLevelUpEvent event = new McMMOPlayerLevelUpEvent(player, skill, levelsGained, XPGainReason.PVE);
+        McMMOPlayerLevelUpEvent event = new McMMOPlayerLevelUpEvent(mmoPlayer.getPlayer(), mining, levelsGained, XPGainReason.PVE);
         selfListener.onPlayerLevelUp(event);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager).apply(any(), any(), any());
+        verify(levelUpCommandManager).applySkillLevelUp(any(), any(), any());
         verify(levelUpCommand).process(any(), any(), any());
         // THEN the command should have executed
         verify(levelUpCommand).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
     }
 
     @Test
-    void levelUpShouldNotRunCommand() {
+    void skillLevelUpShouldNotRunCommand() {
         // GIVEN level up command for Woodcutting should not execute for Mining level up
-        assert mcMMO.p.getLevelUpCommandManager().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
         final String commandStr = "say hello";
-        final LevelUpCommand levelUpCommand
-                = buildLevelUpCommand(commandStr, (s, ignored) -> s == otherSkill);
+        final SkillLevelUpCommand levelUpCommand
+                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == woodcutting);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
 
         int levelsGained = 5;
         // WHEN player gains 5 levels in mining
-        McMMOPlayerLevelUpEvent event = new McMMOPlayerLevelUpEvent(player, skill, levelsGained, XPGainReason.PVE);
+        McMMOPlayerLevelUpEvent event = new McMMOPlayerLevelUpEvent(mmoPlayer.getPlayer(), mining, levelsGained, XPGainReason.PVE);
         selfListener.onPlayerLevelUp(event);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager).apply(any(), any(), any());
+        verify(levelUpCommandManager).applySkillLevelUp(any(), any(), any());
         verify(levelUpCommand).process(any(), any(), any());
         // THEN the command should not be run
         verify(levelUpCommand, never()).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
     }
 
-    private LevelUpCommand buildLevelUpCommand(String commandStr, BiPredicate<PrimarySkillType, Integer> predicate) {
-        LevelUpCommand.LevelUpCommandBuilder builder = new LevelUpCommand.LevelUpCommandBuilder();
+    @Test
+    public void skillLevelUpShouldAlwaysRunPowerlevelCommand() {
+        // GIVEN level up command for power level should always execute for any level up
+        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        final String commandStr = "say hello";
+        final PowerLevelUpCommand powerLevelUpCommand
+                = buildPowerLevelUpCommand(commandStr, (i) -> true);
+        mcMMO.p.getLevelUpCommandManager().registerCommand(powerLevelUpCommand);
+
+        // WHEN player gains 10 levels
+        levelPlayerViaXP(mmoPlayer, mining, 10);
+
+        // THEN the command should be checked for execution
+        verify(levelUpCommandManager, atLeastOnce()).applyPowerLevelUp(any(), any());
+        verify(powerLevelUpCommand, atLeastOnce()).process(any(), any());
+        // THEN the command should have executed
+        verify(powerLevelUpCommand, times(10)).executeCommand(any(McMMOPlayer.class), anyInt());
+    }
+
+    @Test
+    public void skillLevelUpShouldRunPowerlevelCommandOnce() {
+        // GIVEN level up command for power level should always execute for any level up
+        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        final String commandStr = "say hello";
+        final PowerLevelUpCommand powerLevelUpCommand
+                = buildPowerLevelUpCommand(commandStr, (i) -> i == 5);
+        mcMMO.p.getLevelUpCommandManager().registerCommand(powerLevelUpCommand);
+
+        // WHEN player gains 5 levels
+        levelPlayerViaXP(mmoPlayer, mining, 5);
+
+        // THEN the command should be checked for execution
+        verify(levelUpCommandManager, atLeastOnce()).applyPowerLevelUp(any(), any());
+        verify(powerLevelUpCommand, atLeastOnce()).process(any(), any());
+
+        // THEN the command should have executed
+        verify(powerLevelUpCommand, times(1)).executeCommand(any(McMMOPlayer.class), anyInt());
+    }
+
+    private SkillLevelUpCommand buildSkillLevelUpCommand(String commandStr, BiPredicate<PrimarySkillType, Integer> predicate) {
+        final SkillLevelUpCommandBuilder builder = new SkillLevelUpCommandBuilder();
         builder.command(commandStr)
                 .withPredicate(predicate)
                 .withLogInfo(true);
         return Mockito.spy(builder.build());
     }
+
+    private PowerLevelUpCommand buildPowerLevelUpCommand(String commandStr, Predicate<Integer> predicate) {
+        final PowerLevelUpCommandBuilder builder = new PowerLevelUpCommandBuilder();
+        builder.command(commandStr)
+                .withPredicate(predicate)
+                .withLogInfo(true);
+        return Mockito.spy(builder.build());
+    }
+
+    private void levelPlayerViaXP(McMMOPlayer mmoPlayer, PrimarySkillType skill, int levelsGained) {
+        assertEquals(0, mmoPlayer.getSkillLevel(skill));
+        for (int i = 0; i < levelsGained; i++) {
+            mmoPlayer.applyXpGain(mining, mmoPlayer.getProfile().getXpToLevel(skill), XPGainReason.COMMAND, XPGainSource.COMMAND);
+        }
+        assertEquals(levelsGained, mmoPlayer.getSkillLevel(skill));
+    }
+
+    private void levelPlayerViaLevelChangeEvent(McMMOPlayer mmoPlayer, PrimarySkillType skill, int levelsGained) {
+        assertEquals(0, mmoPlayer.getSkillLevel(skill));
+        EventUtils.tryLevelChangeEvent(
+                mmoPlayer.getPlayer(),
+                skill,
+                levelsGained,
+                mmoPlayer.getProfile().getSkillXpLevelRaw(skill),
+                true,
+                XPGainReason.COMMAND);
+        assertEquals(levelsGained, mmoPlayer.getSkillLevel(skill));
+    }
 }