Quellcode durchsuchen

(WIP) reworking level up commands to have multiple requirements

nossr50 vor 1 Jahr
Ursprung
Commit
9b5be855fd

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

@@ -0,0 +1,114 @@
+package com.gmail.nossr50.commands.levelup;
+
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.LogUtils;
+import org.bukkit.Bukkit;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.VisibleForTesting;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+
+public class LevelUpCommand implements CommandsOnLevel {
+    private final @Nullable List<BiPredicate<PrimarySkillType, Integer>> conditions;
+    private final @Nullable Predicate<Integer> powerLevelCondition;
+    private final boolean logInfo;
+    private final @NotNull LinkedList<String> commands;
+
+    public LevelUpCommand(@Nullable List<BiPredicate<PrimarySkillType, Integer>> conditions,
+                          @Nullable Predicate<Integer> powerLevelCondition,
+                          @NotNull LinkedList<String> commands, boolean logInfo) {
+        this.conditions = conditions;
+        this.powerLevelCondition = powerLevelCondition;
+        if (conditions == null && powerLevelCondition == null)
+            throw new IllegalArgumentException("At least one condition must be set");
+        this.commands = commands;
+        this.logInfo = logInfo;
+    }
+
+    public void process(@NotNull McMMOPlayer player, @NotNull PrimarySkillType primarySkillType, @NotNull Set<Integer> levelsGained,
+                        @NotNull Set<Integer> powerLevelsGained) {
+        // each predicate has to pass at least once
+        // we check the predicates against all levels gained to see if they pass at least once
+        // if all predicates pass at least once, we execute the command
+        boolean allConditionsPass = (conditions == null) || conditions.stream().allMatch(predicate -> levelsGained.stream().anyMatch(level -> predicate.test(primarySkillType, level)));
+        // we also check the power level predicate to see if it passes at least once, if this predicate is null, we mark it as passed
+        boolean powerLevelConditionPass = (powerLevelCondition == null) || powerLevelsGained.stream().anyMatch(powerLevelCondition);
+        if (allConditionsPass && powerLevelConditionPass) {
+            executeCommand(player);
+        }
+    }
+
+    @VisibleForTesting
+    void executeCommand(@NotNull McMMOPlayer player) {
+        LogUtils.debug(mcMMO.p.getLogger(), "Executing level up commands: " + commands);
+        for (String command : commands) {
+            LogUtils.debug(mcMMO.p.getLogger(), "Executing command: " + command);
+            String injectedCommand = injectedCommand(command, player);
+            if (!injectedCommand.equalsIgnoreCase(command)) {
+                LogUtils.debug(mcMMO.p.getLogger(), ("Command has been injected with new values: " + injectedCommand));
+            }
+            Bukkit.dispatchCommand(Bukkit.getConsoleSender(), injectedCommand);
+        }
+    }
+
+    @VisibleForTesting
+    String injectedCommand(String command, McMMOPlayer player) {
+        // TODO: unit tests
+        StringBuilder commandBuilder = new StringBuilder(command);
+
+        // Replace %player% with player name
+        replaceAll(commandBuilder, "{@player}", player.getPlayer().getName());
+
+        // Replace each skill level
+        for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
+            if (primarySkillType == PrimarySkillType.SMELTING || primarySkillType == PrimarySkillType.SALVAGE) {
+                continue;
+            }
+            replaceAll(commandBuilder, "{@" + primarySkillType.name().toLowerCase() + "_level}",
+                    String.valueOf(player.getSkillLevel(primarySkillType)));
+        }
+
+        // Replace power level
+        replaceAll(commandBuilder, "{@power_level}", String.valueOf(player.getPowerLevel()));
+
+        return commandBuilder.toString();
+    }
+
+    private void replaceAll(StringBuilder builder, String from, String to) {
+        int index = builder.indexOf(from);
+        while (index != -1) {
+            builder.replace(index, index + from.length(), to);
+            index = builder.indexOf(from, index + to.length());
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        LevelUpCommand that = (LevelUpCommand) o;
+        return logInfo == that.logInfo && Objects.equals(conditions, that.conditions) && Objects.equals(commands, that.commands);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(conditions, logInfo, commands);
+    }
+
+    @Override
+    public String toString() {
+        return "SkillLevelUpCommand{" +
+                "conditions=" + conditions +
+                ", logInfo=" + logInfo +
+                ", commands=" + commands +
+                '}';
+    }
+}

+ 79 - 0
src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommandBuilder.java

@@ -0,0 +1,79 @@
+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.List;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+
+import static java.util.Objects.requireNonNull;
+
+public class LevelUpCommandBuilder {
+    private LinkedList<String> commands = null;
+    private List<BiPredicate<PrimarySkillType, Integer>> conditions = null;
+    private Predicate<Integer> powerLevelCondition = null;
+    private boolean logInfo;
+
+    public LevelUpCommandBuilder() {
+        this.logInfo = false;
+    }
+
+    public LevelUpCommandBuilder withPredicate(BiPredicate<PrimarySkillType, Integer> condition) {
+        if (this.conditions == null) {
+            this.conditions = new LinkedList<>();
+        }
+
+        conditions.add(condition);
+        return this;
+    }
+
+    public LevelUpCommandBuilder withPowerLevelCondition(Predicate<Integer> powerLevelCondition) {
+        if (this.powerLevelCondition != null) {
+            throw new IllegalStateException("power level condition already set");
+        }
+
+        this.powerLevelCondition = powerLevelCondition;
+        return this;
+    }
+
+    public LevelUpCommandBuilder withConditions(
+            @NotNull Collection<BiPredicate<PrimarySkillType, Integer>> conditions) {
+        if (this.conditions == null) {
+            this.conditions = new LinkedList<>();
+        } else {
+            throw new IllegalStateException("conditions already set");
+        }
+
+        this.conditions.addAll(conditions);
+        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 LevelUpCommand build() {
+        if (conditions == null && powerLevelCondition == null) {
+            throw new IllegalStateException("no conditions found for level up command");
+        }
+        requireNonNull(commands, "no commands found for level up command");
+
+        return new LevelUpCommand(conditions, powerLevelCondition, commands, logInfo);
+    }
+}

+ 16 - 39
src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommandManager.java

@@ -16,26 +16,18 @@ import static java.util.Objects.requireNonNull;
  * Manages commands to be executed on level up
  */
 public class LevelUpCommandManager {
-    private final @NotNull Set<SkillLevelUpCommand> skillLevelCommands;
-    private final @NotNull Set<PowerLevelUpCommand> powerLevelUpCommands;
+    private final @NotNull Set<LevelUpCommand> levelUpCommands;
     private final @NotNull mcMMO plugin;
 
     public LevelUpCommandManager(@NotNull mcMMO plugin) {
         this.plugin = requireNonNull(plugin, "plugin cannot be null");
-        this.skillLevelCommands = new HashSet<>();
-        this.powerLevelUpCommands = new HashSet<>();
+        this.levelUpCommands = new HashSet<>();
     }
 
-    public void registerCommand(@NotNull SkillLevelUpCommand skillLevelUpCommand) {
-        requireNonNull(skillLevelUpCommand, "skillLevelUpCommand cannot be null");
-        skillLevelCommands.add(skillLevelUpCommand);
-        LogUtils.debug(mcMMO.p.getLogger(), "Registered level up command - SkillLevelUpCommand: " + skillLevelUpCommand);
-    }
-
-    public void registerCommand(@NotNull PowerLevelUpCommand powerLevelUpCommand) {
-        requireNonNull(powerLevelUpCommand, "powerLevelUpCommand cannot be null");
-        powerLevelUpCommands.add(powerLevelUpCommand);
-        LogUtils.debug(mcMMO.p.getLogger(), "Registered level up command - PowerLevelUpCommand: " + powerLevelUpCommand);
+    public void registerCommand(@NotNull LevelUpCommand levelUpCommand) {
+        requireNonNull(levelUpCommand, "skillLevelUpCommand cannot be null");
+        levelUpCommands.add(levelUpCommand);
+        LogUtils.debug(mcMMO.p.getLogger(), "Registered level up command - SkillLevelUpCommand: " + levelUpCommand);
     }
 
     /**
@@ -45,32 +37,19 @@ public class LevelUpCommandManager {
      * @param primarySkillType  the skill type
      * @param levelsGained      the levels gained
      */
-    public void applySkillLevelUp(@NotNull McMMOPlayer mmoPlayer, @NotNull PrimarySkillType primarySkillType, Set<Integer> levelsGained) {
+    public void applySkillLevelUp(@NotNull McMMOPlayer mmoPlayer, @NotNull PrimarySkillType primarySkillType,
+                                  Set<Integer> levelsGained, Set<Integer> powerLevelsGained) {
         if (!mmoPlayer.getPlayer().isOnline()) {
             return;
         }
 
-        for (SkillLevelUpCommand command : skillLevelCommands) {
-            command.process(mmoPlayer, primarySkillType, levelsGained);
+        for (LevelUpCommand command : levelUpCommands) {
+            command.process(mmoPlayer, primarySkillType, levelsGained, powerLevelsGained);
         }
     }
 
-    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;
+    public @NotNull Set<LevelUpCommand> getLevelUpCommands() {
+        return levelUpCommands;
     }
 
     /**
@@ -78,8 +57,7 @@ public class LevelUpCommandManager {
      */
     public void clear() {
         mcMMO.p.getLogger().info("Clearing registered commands on level up");
-        skillLevelCommands.clear();
-        powerLevelUpCommands.clear();
+        levelUpCommands.clear();
     }
 
     @Override
@@ -87,19 +65,18 @@ public class LevelUpCommandManager {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         LevelUpCommandManager that = (LevelUpCommandManager) o;
-        return Objects.equals(skillLevelCommands, that.skillLevelCommands) && Objects.equals(powerLevelUpCommands, that.powerLevelUpCommands) && Objects.equals(plugin, that.plugin);
+        return Objects.equals(levelUpCommands, that.levelUpCommands) && Objects.equals(plugin, that.plugin);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(skillLevelCommands, powerLevelUpCommands, plugin);
+        return Objects.hash(levelUpCommands, plugin);
     }
 
     @Override
     public String toString() {
         return "LevelUpCommandManager{" +
-                "skillLevelCommands=" + skillLevelCommands +
-                ", powerLevelUpCommands=" + powerLevelUpCommands +
+                "levelUpCommands=" + levelUpCommands +
                 ", plugin=" + plugin +
                 '}';
     }

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

@@ -1,96 +0,0 @@
-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) {
-        LogUtils.debug(mcMMO.p.getLogger(), "Executing commands for level up: " + commands);
-        for (String command : commands) {
-            LogUtils.debug(mcMMO.p.getLogger(), "Executing command: " + command);
-            String injectedCommand = injectedCommand(command, player, level);
-            if (!injectedCommand.equalsIgnoreCase(command)) {
-                LogUtils.debug(mcMMO.p.getLogger(), ("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, "%power_level%", "power level");
-        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 +
-                '}';
-    }
-}

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

@@ -1,59 +0,0 @@
-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);
-    }
-}

+ 0 - 96
src/main/java/com/gmail/nossr50/commands/levelup/SkillLevelUpCommand.java

@@ -1,96 +0,0 @@
-package com.gmail.nossr50.commands.levelup;
-
-import com.gmail.nossr50.datatypes.player.McMMOPlayer;
-import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
-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.BiPredicate;
-
-public class SkillLevelUpCommand implements CommandsOnLevel {
-    private final BiPredicate<PrimarySkillType, Integer> predicate;
-    private final boolean logInfo;
-    private final @NotNull LinkedList<String> commands;
-
-    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 SkillLevelUpCommand(@NotNull BiPredicate<PrimarySkillType, Integer> predicate, @NotNull LinkedList<String> commands, boolean logInfo) {
-        this.predicate = predicate;
-        this.commands = commands;
-        this.logInfo = logInfo;
-    }
-
-    public void process(McMMOPlayer player, PrimarySkillType primarySkillType, Set<Integer> levelsGained) {
-        for (int i : levelsGained) {
-            if (predicate.test(primarySkillType, 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, primarySkillType, i);
-            }
-        }
-    }
-
-    public void executeCommand(McMMOPlayer player, PrimarySkillType primarySkillType, int level) {
-        LogUtils.debug(mcMMO.p.getLogger(), "Executing commands for level up: " + commands);
-        for (String command : commands) {
-            LogUtils.debug(mcMMO.p.getLogger(), "Executing command: " + command);
-            String injectedCommand = injectedCommand(command, player, primarySkillType, level);
-            if (!injectedCommand.equalsIgnoreCase(command)) {
-                LogUtils.debug(mcMMO.p.getLogger(), ("Command has been injected with new values: " + injectedCommand));
-            }
-            Bukkit.dispatchCommand(Bukkit.getConsoleSender(), injectedCommand);
-        }
-    }
-
-    private String injectedCommand(String command, McMMOPlayer player, PrimarySkillType primarySkillType, 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%", primarySkillType.getName());
-        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;
-        SkillLevelUpCommand that = (SkillLevelUpCommand) 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 "LevelUpCommandImpl{" +
-                "predicate=" + predicate +
-                ", logInfo=" + logInfo +
-                ", commandStr='" + commands + '\'' +
-                '}';
-    }
-}

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

@@ -1,82 +0,0 @@
-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);
-    }
-}

+ 79 - 145
src/main/java/com/gmail/nossr50/config/CommandOnLevelUpConfig.java

@@ -1,9 +1,7 @@
 package com.gmail.nossr50.config;
 
-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.commands.levelup.LevelUpCommand;
+import com.gmail.nossr50.commands.levelup.LevelUpCommandBuilder;
 import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.util.LogUtils;
@@ -48,153 +46,89 @@ public class CommandOnLevelUpConfig extends BukkitConfig {
                 continue;
             }
 
-            SkillLevelUpCommand skillLevelUpCommand = buildSkillLevelUpCommand(commandSection);
-            PowerLevelUpCommand powerLevelUpCommand = buildPowerLevelUpCommand(commandSection);
+            LevelUpCommand levelUpCommand = buildSkillLevelUpCommand(commandSection);
 
-            if (skillLevelUpCommand == null && powerLevelUpCommand == null) {
+            if (levelUpCommand == null) {
                 mcMMO.p.getLogger().severe("Invalid command format for key: " + key);
             } else {
-                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);
-                }
+                mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
+                mcMMO.p.getLogger().info("Level up command successfully loaded from config for key: " + key);
             }
         }
     }
 
-    private @Nullable SkillLevelUpCommand buildSkillLevelUpCommand(final ConfigurationSection commandSection) {
-        SkillLevelUpCommandBuilder builder = new SkillLevelUpCommandBuilder();
-        // 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;
-        }
-
-        // Skill Filter
-        // check if skills is string or configuration section
-        if (condition.contains(SKILLS_SECTION)) {
-            if (condition.isString(SKILLS_SECTION)) {
-                String skillName = condition.getString(SKILLS_SECTION);
-                if (skillName != null) {
-                    PrimarySkillType primarySkillType = mcMMO.p.getSkillTools().matchSkill(skillName);
-                    if (primarySkillType != null) {
-                        builder.withSkillFilter(getSkillsFromFilter(new HashSet<>(Set.of(skillName))));
-                    }
-                }
-            } else {
-                ConfigurationSection skillsSection = condition.getConfigurationSection(SKILLS_SECTION);
-                if (skillsSection != null) {
-                    Set<String> skillNames = skillsSection.getKeys(false);
-                    Set<PrimarySkillType> skillsFromFilter = getSkillsFromFilter(skillNames);
-                    if (skillsFromFilter.isEmpty()) {
-                        LogUtils.debug(mcMMO.p.getLogger(), "No valid skills found for command named "
-                                + commandSection.getName() + "for condition section named " + skillsSection.getName());
-                    } else {
-                        builder.withSkillFilter(skillsFromFilter);
-                    }
-                }
-            }
-        }
-
-        // for now only simple condition is supported
-        if (!condition.contains(LEVELS_SECTION)) {
-            mcMMO.p.getLogger().severe("No condition.levels section found for 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 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 command named "
-                        + commandSection.getName());
-                return null;
-            } else {
-                builder.commands(commands);
-            }
-        }
-
-        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);
+    private @Nullable LevelUpCommand buildSkillLevelUpCommand(final ConfigurationSection commandSection) {
+        // TODO: Rework
+        return null;
+//        LevelUpCommandBuilder builder = new LevelUpCommandBuilder();
+//        // 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;
+//        }
+//
+//        // Skill Filter
+//        // check if skills is string or configuration section
+//        if (condition.contains(SKILLS_SECTION)) {
+//            if (condition.isString(SKILLS_SECTION)) {
+//                String skillName = condition.getString(SKILLS_SECTION);
+//                if (skillName != null) {
+//                    PrimarySkillType primarySkillType = mcMMO.p.getSkillTools().matchSkill(skillName);
+//                    if (primarySkillType != null) {
+//                        builder.withSkillFilter(getSkillsFromFilter(new HashSet<>(Set.of(skillName))));
+//                    }
+//                }
+//            } else {
+//                ConfigurationSection skillsSection = condition.getConfigurationSection(SKILLS_SECTION);
+//                if (skillsSection != null) {
+//                    Set<String> skillNames = skillsSection.getKeys(false);
+//                    Set<PrimarySkillType> skillsFromFilter = getSkillsFromFilter(skillNames);
+//                    if (skillsFromFilter.isEmpty()) {
+//                        LogUtils.debug(mcMMO.p.getLogger(), "No valid skills found for command named "
+//                                + commandSection.getName() + "for condition section named " + skillsSection.getName());
+//                    } else {
+//                        builder.withSkillFilter(skillsFromFilter);
+//                    }
+//                }
+//            }
+//        }
+//
+//        // for now only simple condition is supported
+//        if (!condition.contains(LEVELS_SECTION)) {
+//            mcMMO.p.getLogger().severe("No condition.levels section found for 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 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 command named "
+//                        + commandSection.getName());
+//                return null;
+//            } else {
+//                builder.commands(commands);
+//            }
+//        }
+//
+//        return builder.build();
     }
 }

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

@@ -70,8 +70,7 @@ public class SelfListener implements Listener {
             powerLevelsAchieved.add(startingPowerLevel + (i + 1));
         }
 
-        plugin.getLevelUpCommandManager().applySkillLevelUp(mcMMOPlayer, skill, levelsAchieved);
-        plugin.getLevelUpCommandManager().applyPowerLevelUp(mcMMOPlayer, powerLevelsAchieved);
+        plugin.getLevelUpCommandManager().applySkillLevelUp(mcMMOPlayer, skill, levelsAchieved, powerLevelsAchieved);
     }
 
     @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)

+ 10 - 19
src/main/resources/levelupcommands.yml

@@ -1,32 +1,23 @@
 level_up_commands:
-    unique_id_here:
+    woodcutting_and_swords_command:
         enabled: true
         condition:
-            skills:
-                - 'Swords'
-                - 'Axes'
-            levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+            swords:
+                levels: [10, 100]
+            woodcutting:
+                levels: [10, 100]
         commands:
-            - "say %player% reached level %level%!"
+            - "say {@player} reached level {@swords_level} in swords, and {@woodcutting_level} in woodcutting!"
             - "say Isn't that nice?"
         run_command_as: 'CONSOLE'
         log_level: 'INFO'
-    other_unique_id_here:
-        enabled: true
-        condition:
-            levels: [1, 2, 3, 4, 5]
-        commands:
-            - "say this command should execute for all skills, %player%!"
-            - "say Isn't that fun?"
-        run_command_as: 'CONSOLE'
-        log_level: 'DEBUG'
     power_level_milestones:
         enabled: true
         condition:
-            power_level: true
-            levels: [ 10, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 3000, 4000, 5000, 10000, 20000 ]
+            power_level:
+                levels: [ 1000, 2000, 3000, 4000, 5000 ]
         commands:
-            - "say %player% has reached a power level milestone!"
-            - "say %player% is now at power level %level%!"
+            - "say {@player} has reached a power level milestone!"
+            - "say {@player} is now at power level {@power_level}!"
         run_command_as: 'CONSOLE'
         log_level: 'DEBUG'

+ 187 - 104
src/test/java/com/gmail/nossr50/commands/levelup/LevelUpCommandTest.java

@@ -9,28 +9,32 @@ import com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.util.EventUtils;
 import org.bukkit.Bukkit;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
+import java.util.List;
 import java.util.UUID;
 import java.util.function.BiPredicate;
 import java.util.function.Predicate;
 
+import static com.gmail.nossr50.datatypes.skills.PrimarySkillType.MINING;
+import static com.gmail.nossr50.datatypes.skills.PrimarySkillType.WOODCUTTING;
+import static java.util.Objects.requireNonNull;
 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 mining = PrimarySkillType.MINING;
-    private final PrimarySkillType woodcutting = PrimarySkillType.WOODCUTTING;
+    private static final BiPredicate<PrimarySkillType, Integer> ALWAYS_TRUE = (skill, level) -> true;
     private McMMOPlayer mmoPlayer;
     private final String playerName = "Momshroom";
 
     @BeforeEach
     void beforeEach() {
-        mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().clear();
-        mcMMO.p.getLevelUpCommandManager().getPowerLevelUpCommands().clear();
+        mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().clear();
 
         this.mmoPlayer = getMMOPlayer(UUID.randomUUID(), playerName, 0);
     }
@@ -38,73 +42,96 @@ class LevelUpCommandTest extends MMOTestEnvironmentBasic {
     @Test
     void skillLevelUpShouldRunFiveTimes() {
         // GIVEN level up command for Mining should always execute for Mining level up
-        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().isEmpty();
         final String commandStr = "say hello";
-        final SkillLevelUpCommand levelUpCommand
-                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
+        final LevelUpCommand levelUpCommand = buildLevelUpCommand(commandStr,
+                (skill, skillLevel) -> skill == MINING && skillLevel >= 1 && skillLevel <= 5);
+
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
         // WHEN player gains 5 levels in mining via command
-        levelPlayerViaXP(mmoPlayer, mining, 5);
+        levelPlayerViaXP(mmoPlayer, MINING, 5);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager, atLeastOnce()).applySkillLevelUp(any(), any(), any());
-        verify(levelUpCommand, atLeastOnce()).process(any(), any(), any());
+        verify(levelUpCommandManager, atLeastOnce()).applySkillLevelUp(any(), any(), any(), any());
+        verify(levelUpCommand, atLeastOnce()).process(any(), any(), any(), any());
 
         // THEN the command should have executed
-        verify(levelUpCommand, times(5)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
+        verify(levelUpCommand, times(5)).executeCommand(any(McMMOPlayer.class));
         mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), any()), atLeast(5));
     }
 
+    @Test
+    void dualRequirementsShouldRunOnce() {
+        // GIVEN
+        assert mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().isEmpty();
+        final String commandStr = "say hello";
+        BiPredicate<PrimarySkillType, Integer> predicate = (skill, skillLevel) -> skill == MINING && skillLevel == 3;
+        BiPredicate<PrimarySkillType, Integer> predicate2 = (skill, skillLevel) -> skill == WOODCUTTING && skillLevel == 3;
+        final LevelUpCommand levelUpCommand = buildLevelUpCommand(commandStr, List.of(predicate, predicate2));
+
+        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
+
+        // WHEN player gains 5 levels in mining and woodcutting via command
+        levelPlayerViaXP(mmoPlayer, MINING, 5);
+        levelPlayerViaXP(mmoPlayer, WOODCUTTING, 5);
+
+        // THEN the command should be checked for execution
+        verify(levelUpCommandManager, atLeastOnce()).applySkillLevelUp(any(), any(), any(), any());
+        verify(levelUpCommand, times(10)).process(any(), any(), any(), any());
+
+        // THEN the command should have executed
+        verify(levelUpCommand, times(1)).executeCommand(any(McMMOPlayer.class));
+        mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), any()), atLeast(1));
+    }
+
     @Test
     void skillLevelUpViaXPGainShouldRunFiveTimes() {
         // GIVEN level up command for Mining should always execute for Mining level up
-        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().isEmpty();
         final String commandStr = "say hello";
-        final SkillLevelUpCommand levelUpCommand
-                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
+        final LevelUpCommand levelUpCommand = buildLevelUpCommand(commandStr,
+                (skill, skillLevel) -> skill == MINING && skillLevel >= 1 && skillLevel <= 5);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
         // WHEN player gains 5 levels in mining via command
-        levelPlayerViaXP(mmoPlayer, mining, 5);
+        levelPlayerViaXP(mmoPlayer, MINING, 5);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager, times(5)).applySkillLevelUp(any(), any(), any());
-        verify(levelUpCommand, times(5)).process(any(), any(), any());
+        verify(levelUpCommandManager, times(5)).applySkillLevelUp(any(), any(), any(), any());
+        verify(levelUpCommand, times(5)).process(any(), any(), any(), any());
 
         // THEN the command should have executed
-        verify(levelUpCommand, times(5)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
+        verify(levelUpCommand, times(5)).executeCommand(any(McMMOPlayer.class));
         mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), any()), atLeast(5));
     }
 
     @Test
     void skillLevelUpViaXPGainShouldRunCommandFiveTimesWithPlaceholders() {
         // GIVEN level up command for Mining should always execute for Mining level up
-        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().isEmpty();
         assertEquals(mmoPlayer.getPlayer().getName(), playerName);
-        final String commandStr = "say hello %player%, you have reached level %level%";
+        final String commandStr = "say hello {@player}, you have reached level {@mining_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 SkillLevelUpCommand levelUpCommand
-                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
+        final LevelUpCommand levelUpCommand = buildLevelUpCommand(commandStr,
+                (skill, skillLevel) -> skill == MINING && skillLevel >= 1 && skillLevel <= 5);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
         // WHEN player gains 5 levels in mining via command
-        assertEquals(0, mmoPlayer.getSkillLevel(mining));
+        assertEquals(0, mmoPlayer.getSkillLevel(MINING));
         int levelsGained = 5;
-        for (int i = 0; i < 5; i++) {
-            mmoPlayer.applyXpGain(mining, mmoPlayer.getProfile().getXpToLevel(mining), XPGainReason.COMMAND, XPGainSource.COMMAND);
-        }
+        levelPlayerViaXP(mmoPlayer, MINING, levelsGained);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager, times(levelsGained)).applySkillLevelUp(any(), any(), any());
-        verify(levelUpCommand, times(levelsGained)).process(any(), any(), any());
+        verify(levelUpCommandManager, times(levelsGained)).applySkillLevelUp(any(), any(), any(), any());
+        verify(levelUpCommand, times(levelsGained)).process(any(), any(), any(), any());
 
         // THEN the command should have executed
-        verify(levelUpCommand, times(levelsGained)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
+        verify(levelUpCommand, times(levelsGained)).executeCommand(any(McMMOPlayer.class));
         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
@@ -116,64 +143,110 @@ class LevelUpCommandTest extends MMOTestEnvironmentBasic {
     }
 
     @Test
-    void skillLevelUpShouldRunCommandFiveTimesWithPlaceholders() {
+    void skillLevelUpShouldRunCommandThreeTimesWithPlaceholders() {
+        /*
+            This test executes a player leveling up 5 times.
+            With level 3 separate registered level up commands.
+            Each registered command runs only once.
+         */
         // GIVEN level up command for Mining should always execute for Mining level up
-        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().isEmpty();
         assertEquals(mmoPlayer.getPlayer().getName(), playerName);
 
-        final String commandStr = "say hello %player%";
+        final String commandStr = "say hello {@player}";
         final String expectedStr = "say hello " + playerName;
-        final SkillLevelUpCommand levelUpCommand
-                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
-        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
+        final LevelUpCommand levelUpCommandOne = buildLevelUpCommand(commandStr, (skill, level) -> skill == MINING && level == 1);
+        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommandOne);
+        final LevelUpCommand levelUpCommandTwo = buildLevelUpCommand(commandStr, (skill, level) -> skill == MINING && level == 2);
+        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommandTwo);
+        final LevelUpCommand levelUpCommandThree = buildLevelUpCommand(commandStr, (skill, level) -> skill == MINING && level == 3);
+        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommandThree);
         int levelsGained = 5;
 
         // WHEN player gains 5 levels in mining
-        McMMOPlayerLevelUpEvent event = new McMMOPlayerLevelUpEvent(mmoPlayer.getPlayer(), mining, levelsGained, XPGainReason.PVE);
-        selfListener.onPlayerLevelUp(event);
+        levelPlayerViaXP(mmoPlayer, MINING, levelsGained);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager).applySkillLevelUp(any(), any(), any());
-        verify(levelUpCommand).process(any(), any(), any());
+        verify(levelUpCommandManager, times(levelsGained)).applySkillLevelUp(any(), any(), any(), any());
+        verify(levelUpCommandOne, times(levelsGained)).process(any(), any(), any(), any());
+        verify(levelUpCommandTwo, times(levelsGained)).process(any(), any(), any(), any());
+        verify(levelUpCommandThree, times(levelsGained)).process(any(), any(), any(), any());
         // THEN the command should have executed
-        verify(levelUpCommand, times(levelsGained)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
-        // verify that Bukkit.dispatchCommand got executed at least 5 times with the correct injectedCommand
-        mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), eq(expectedStr)), atLeast(5));
+        verify(levelUpCommandOne, times(1)).executeCommand(any(McMMOPlayer.class));
+        verify(levelUpCommandTwo, times(1)).executeCommand(any(McMMOPlayer.class));
+        verify(levelUpCommandThree, times(1)).executeCommand(any(McMMOPlayer.class));
+        // verify that Bukkit.dispatchCommand got executed at least 20 times with the correct injectedCommand
+        mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), eq(expectedStr)), atLeast(3));
     }
 
     @Test
-    void skillLevelUpViaAddLevelsShouldRunCommandFiveTimesWithPlaceholdersForLevel() {
+    void skillLevelUpShouldRunCommandFourTimesWithPlaceholders() {
+        /*
+            This test executes a player leveling up 5 times.
+            With level 3 separate registered level up commands.
+            One command runs twice, the others run once.
+         */
         // GIVEN level up command for Mining should always execute for Mining level up
-        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().isEmpty();
         assertEquals(mmoPlayer.getPlayer().getName(), playerName);
 
-        final String commandStr = "say hello %player%, you have reached level %level%";
+        final String commandStr = "say hello {@player}";
+        final String expectedStr = "say hello " + playerName;
+        final LevelUpCommand levelUpCommandOne = buildLevelUpCommand(commandStr, (skill, level) -> skill == MINING && (level == 1 || level == 4));
+        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommandOne);
+        final LevelUpCommand levelUpCommandTwo = buildLevelUpCommand(commandStr, (skill, level) -> skill == MINING && level == 2);
+        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommandTwo);
+        final LevelUpCommand levelUpCommandThree = buildLevelUpCommand(commandStr, (skill, level) -> skill == MINING && level == 3);
+        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommandThree);
+        int levelsGained = 5;
+
+        // WHEN player gains 5 levels in mining
+        levelPlayerViaXP(mmoPlayer, MINING, levelsGained);
+
+        // THEN the command should be checked for execution
+        verify(levelUpCommandManager, times(levelsGained)).applySkillLevelUp(any(), any(), any(), any());
+        verify(levelUpCommandOne, times(levelsGained)).process(any(), any(), any(), any());
+        verify(levelUpCommandTwo, times(levelsGained)).process(any(), any(), any(), any());
+        verify(levelUpCommandThree, times(levelsGained)).process(any(), any(), any(), any());
+        // THEN the command should have executed
+        verify(levelUpCommandOne, times(2)).executeCommand(any(McMMOPlayer.class));
+        verify(levelUpCommandTwo, times(1)).executeCommand(any(McMMOPlayer.class));
+        verify(levelUpCommandThree, times(1)).executeCommand(any(McMMOPlayer.class));
+        // verify that Bukkit.dispatchCommand got executed at least 20 times with the correct injectedCommand
+        mockedBukkit.verify(() -> Bukkit.dispatchCommand(any(), eq(expectedStr)), atLeast(3));
+    }
+
+    @Test
+    void addLevelsShouldRunCommandFiveTimesWithPlaceholdersForLevel() {
+        // GIVEN level up command for Mining should always execute for Mining level up
+        assert mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().isEmpty();
+        assertEquals(mmoPlayer.getPlayer().getName(), playerName);
+
+        final String commandStr = "say hello {@player}, you have reached level {@mining_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 SkillLevelUpCommand levelUpCommand
-                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
-        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
+        final LevelUpCommand levelUpCommand = buildLevelUpCommand(commandStr, (skill, ignored) -> skill == MINING);        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
         // WHEN player gains 5 levels in mining
         int levelsGained = 5;
-        mmoPlayer.getProfile().addLevels(mining, levelsGained);
+        mmoPlayer.getProfile().addLevels(MINING, levelsGained);
         EventUtils.tryLevelChangeEvent(
                 mmoPlayer.getPlayer(),
-                mining,
+                MINING,
                 levelsGained,
-                mmoPlayer.getProfile().getSkillXpLevelRaw(mining),
+                mmoPlayer.getProfile().getSkillXpLevelRaw(MINING),
                 true,
                 XPGainReason.COMMAND);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager).applySkillLevelUp(any(), any(), any());
-        verify(levelUpCommand).process(any(), any(), any());
+        verify(levelUpCommandManager).applySkillLevelUp(any(), any(), any(), any());
+        verify(levelUpCommand).process(any(), any(), any(), any());
         // THEN the command should have executed
-        verify(levelUpCommand, times(levelsGained)).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
+        verify(levelUpCommand, times(1)).executeCommand(any(McMMOPlayer.class));
         // 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)));
@@ -185,118 +258,128 @@ class LevelUpCommandTest extends MMOTestEnvironmentBasic {
     @Test
     void skillLevelUpShouldRunCommandAtLeastOnce() {
         // GIVEN level up command for Mining should always execute for Mining level up
-        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().isEmpty();
         final String commandStr = "say hello";
-        final SkillLevelUpCommand levelUpCommand
-                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == mining);
+        final LevelUpCommand levelUpCommand = buildLevelUpCommand(commandStr, (skill, ignored) -> skill == MINING);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
         int levelsGained = 1;
         // WHEN player gains 5 levels in mining
-        McMMOPlayerLevelUpEvent event = new McMMOPlayerLevelUpEvent(mmoPlayer.getPlayer(), mining, 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).applySkillLevelUp(any(), any(), any());
-        verify(levelUpCommand).process(any(), any(), any());
+        verify(levelUpCommandManager).applySkillLevelUp(any(), any(), any(), any());
+        verify(levelUpCommand).process(any(), any(), any(), any());
         // THEN the command should have executed
-        verify(levelUpCommand).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
+        verify(levelUpCommand).executeCommand(any(McMMOPlayer.class));
     }
 
     @Test
     void skillLevelUpShouldNotRunCommand() {
         // GIVEN level up command for Woodcutting should not execute for Mining level up
-        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().isEmpty();
         final String commandStr = "say hello";
-        final SkillLevelUpCommand levelUpCommand
-                = buildSkillLevelUpCommand(commandStr, (s, ignored) -> s == woodcutting);
+        final LevelUpCommand levelUpCommand = buildLevelUpCommand(commandStr, (skill, ignored) -> skill == WOODCUTTING);
         mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
 
         int levelsGained = 5;
         // WHEN player gains 5 levels in mining
-        McMMOPlayerLevelUpEvent event = new McMMOPlayerLevelUpEvent(mmoPlayer.getPlayer(), mining, 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).applySkillLevelUp(any(), any(), any());
-        verify(levelUpCommand).process(any(), any(), any());
+        verify(levelUpCommandManager).applySkillLevelUp(any(), any(), any(), any());
+        verify(levelUpCommand).process(any(), any(), any(), any());
         // THEN the command should not be run
-        verify(levelUpCommand, never()).executeCommand(any(McMMOPlayer.class), any(PrimarySkillType.class), anyInt());
+        verify(levelUpCommand, never()).executeCommand(any(McMMOPlayer.class));
     }
 
     @Test
-    public void skillLevelUpShouldAlwaysRunPowerlevelCommand() {
-        // GIVEN level up command for power level should always execute for any level up
-        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+    public void levelUpShouldAlwaysRunCommand() {
+        // GIVEN level up command should always execute for any level up
+        assert mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().isEmpty();
         final String commandStr = "say hello";
-        final PowerLevelUpCommand powerLevelUpCommand
-                = buildPowerLevelUpCommand(commandStr, (i) -> true);
-        mcMMO.p.getLevelUpCommandManager().registerCommand(powerLevelUpCommand);
+        final LevelUpCommand levelUpCommand = buildLevelUpCommand(commandStr, ALWAYS_TRUE);
+        mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand);
 
         // WHEN player gains 10 levels
-        levelPlayerViaXP(mmoPlayer, mining, 10);
+        levelPlayerViaXP(mmoPlayer, MINING, 10);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager, atLeastOnce()).applyPowerLevelUp(any(), any());
-        verify(powerLevelUpCommand, atLeastOnce()).process(any(), any());
+        verify(levelUpCommandManager, atLeastOnce()).applySkillLevelUp(any(), eq(MINING), any(), any());
+        verify(levelUpCommand, atLeastOnce()).process(any(), any(), any(), any());
         // THEN the command should have executed
-        verify(powerLevelUpCommand, times(10)).executeCommand(any(McMMOPlayer.class), anyInt());
+        verify(levelUpCommand, times(10)).executeCommand(any(McMMOPlayer.class));
     }
 
     @Test
     public void skillLevelUpShouldRunPowerlevelCommandOnce() {
         // GIVEN level up command for power level should always execute for any level up
-        assert mcMMO.p.getLevelUpCommandManager().getSkillLevelCommands().isEmpty();
+        assert mcMMO.p.getLevelUpCommandManager().getLevelUpCommands().isEmpty();
         final String commandStr = "say hello";
-        final PowerLevelUpCommand powerLevelUpCommand
-                = buildPowerLevelUpCommand(commandStr, (i) -> i == 5);
+        final LevelUpCommand powerLevelUpCommand = buildLevelUpCommand(commandStr,
+                (ignoredA, ignoredB) -> true, (powerlevel) -> powerlevel == 3);
         mcMMO.p.getLevelUpCommandManager().registerCommand(powerLevelUpCommand);
 
         // WHEN player gains 5 levels
-        levelPlayerViaXP(mmoPlayer, mining, 5);
+        levelPlayerViaXP(mmoPlayer, MINING, 5);
 
         // THEN the command should be checked for execution
-        verify(levelUpCommandManager, atLeastOnce()).applyPowerLevelUp(any(), any());
-        verify(powerLevelUpCommand, atLeastOnce()).process(any(), any());
+        verify(levelUpCommandManager, atLeastOnce()).applySkillLevelUp(any(), any(), any(), any());
+        verify(powerLevelUpCommand, atLeastOnce()).process(any(), any(), any(), any());
 
         // THEN the command should have executed
-        verify(powerLevelUpCommand, times(1)).executeCommand(any(McMMOPlayer.class), anyInt());
+        verify(powerLevelUpCommand, times(1)).executeCommand(any(McMMOPlayer.class));
     }
 
-    private SkillLevelUpCommand buildSkillLevelUpCommand(String commandStr, BiPredicate<PrimarySkillType, Integer> predicate) {
-        final SkillLevelUpCommandBuilder builder = new SkillLevelUpCommandBuilder();
+    private LevelUpCommand buildLevelUpCommand(@NotNull String commandStr,
+                                               @NotNull List<BiPredicate<PrimarySkillType, Integer>> conditions,
+                                               @Nullable Predicate<Integer> powerLevelCondition) {
+        requireNonNull(commandStr, "commandStr cannot be null");
+        requireNonNull(conditions, "conditions cannot be null");
+        final var builder = new LevelUpCommandBuilder();
+        if (powerLevelCondition != null) {
+            builder.withPowerLevelCondition(powerLevelCondition);
+        }
         builder.command(commandStr)
-                .withPredicate(predicate)
+                .withConditions(conditions)
                 .withLogInfo(true);
         return Mockito.spy(builder.build());
     }
 
-    private PowerLevelUpCommand buildPowerLevelUpCommand(String commandStr, Predicate<Integer> predicate) {
-        final PowerLevelUpCommandBuilder builder = new PowerLevelUpCommandBuilder();
+    private LevelUpCommand buildLevelUpCommand(@NotNull String commandStr,
+                                               @NotNull List<BiPredicate<PrimarySkillType, Integer>> conditions) {
+        return buildLevelUpCommand(commandStr, conditions, null);
+    }
+
+    private LevelUpCommand buildLevelUpCommand(@NotNull String commandStr,
+                                               @NotNull BiPredicate<PrimarySkillType, Integer> predicate,
+                                               @Nullable Predicate<Integer> powerLevelCondition) {
+        requireNonNull(commandStr, "commandStr cannot be null");
+        requireNonNull(predicate, "predicate cannot be null");
+        final var builder = new LevelUpCommandBuilder();
+        if (powerLevelCondition != null) {
+            builder.withPowerLevelCondition(powerLevelCondition);
+        }
         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 LevelUpCommand buildLevelUpCommand(@NotNull String commandStr,
+                                               @NotNull BiPredicate<PrimarySkillType, Integer> predicate) {
+        return buildLevelUpCommand(commandStr, predicate, null);
     }
 
-    private void levelPlayerViaLevelChangeEvent(McMMOPlayer mmoPlayer, PrimarySkillType skill, int levelsGained) {
+    private void levelPlayerViaXP(@NotNull McMMOPlayer mmoPlayer, @NotNull PrimarySkillType skill, int levelsGained) {
+        System.out.println("Leveling " + mmoPlayer.getPlayer().getName() + " up " + levelsGained + " levels in " + skill.getName());
         assertEquals(0, mmoPlayer.getSkillLevel(skill));
-        EventUtils.tryLevelChangeEvent(
-                mmoPlayer.getPlayer(),
-                skill,
-                levelsGained,
-                mmoPlayer.getProfile().getSkillXpLevelRaw(skill),
-                true,
-                XPGainReason.COMMAND);
+        for (int i = 0; i < levelsGained; i++) {
+            mmoPlayer.applyXpGain(skill, mmoPlayer.getProfile().getXpToLevel(skill), XPGainReason.COMMAND, XPGainSource.COMMAND);
+        }
         assertEquals(levelsGained, mmoPlayer.getSkillLevel(skill));
     }
 }