diff --git a/src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommand.java b/src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommand.java new file mode 100644 index 000000000..89cb4e1bb --- /dev/null +++ b/src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommand.java @@ -0,0 +1,10 @@ +package com.gmail.nossr50.commands.levelup; + +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; + +import java.util.Set; + +public interface LevelUpCommand { + void apply(McMMOPlayer player, PrimarySkillType primarySkillType, Set levelsGained); +} diff --git a/src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommandImpl.java b/src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommandImpl.java new file mode 100644 index 000000000..11bdae089 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommandImpl.java @@ -0,0 +1,69 @@ +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.Objects; +import java.util.Set; +import java.util.function.Predicate; + +public class LevelUpCommandImpl implements LevelUpCommand { + private final @NotNull Predicate shouldApply; + private final boolean logInfo; + private final @NotNull String commandStr; + + private final @NotNull Set skills; + + public LevelUpCommandImpl(@NotNull Predicate shouldApply, @NotNull String commandStr, @NotNull Set skills, boolean logInfo) { + this.shouldApply = shouldApply; + this.commandStr = commandStr; + this.skills = skills; + this.logInfo = logInfo; + } + + @Override + public void apply(McMMOPlayer player, PrimarySkillType primarySkillType, Set levelsGained) { + if(!skills.contains(primarySkillType)) { + return; + } + + for (int i : levelsGained) { + if (shouldApply.test(i)) { + // execute command via server console in Bukkit + if(logInfo) { + mcMMO.p.getLogger().info("Executing command: " + commandStr); + } else { + LogUtils.debug(mcMMO.p.getLogger(), "Executing command: " + commandStr); + } + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), commandStr); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LevelUpCommandImpl that = (LevelUpCommandImpl) o; + return logInfo == that.logInfo && Objects.equals(shouldApply, that.shouldApply) && Objects.equals(commandStr, that.commandStr) && Objects.equals(skills, that.skills); + } + + @Override + public int hashCode() { + return Objects.hash(shouldApply, logInfo, commandStr, skills); + } + + @Override + public String toString() { + return "LevelUpCommandImpl{" + + "shouldApply=" + shouldApply + + ", logInfo=" + logInfo + + ", commandStr='" + commandStr + '\'' + + ", skills=" + skills + + '}'; + } +} diff --git a/src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommandManager.java b/src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommandManager.java new file mode 100644 index 000000000..212cf90e7 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/commands/levelup/LevelUpCommandManager.java @@ -0,0 +1,43 @@ +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 org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.Set; + +public class LevelUpCommandManager { + private final @NotNull Set commands; + private final @NotNull mcMMO plugin; + + public LevelUpCommandManager(@NotNull mcMMO plugin) { + this.plugin = plugin; + this.commands = new HashSet<>(); + } + + public void registerCommand(@NotNull LevelUpCommand command) { + commands.add(command); + mcMMO.p.getLogger().info("Registered command on level up: " + command); + } + + public void apply(@NotNull McMMOPlayer mmoPlayer, @NotNull PrimarySkillType primarySkillType, Set levelsGained) { + if (!mmoPlayer.getPlayer().isOnline()) { + return; + } + + for (LevelUpCommand command : commands) { + command.apply(mmoPlayer, primarySkillType, levelsGained); + } + } + + public void clear() { + mcMMO.p.getLogger().info("Clearing registered commands on level up"); + commands.clear(); + } + + public boolean isEmpty() { + return commands.isEmpty(); + } +} diff --git a/src/main/java/com/gmail/nossr50/config/CommandOnLevelUpConfig.java b/src/main/java/com/gmail/nossr50/config/CommandOnLevelUpConfig.java new file mode 100644 index 000000000..6b9498b01 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/config/CommandOnLevelUpConfig.java @@ -0,0 +1,17 @@ +package com.gmail.nossr50.config; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +public class CommandOnLevelUpConfig extends BukkitConfig { + + public CommandOnLevelUpConfig(@NotNull File dataFolder) { + super("commandonlevelup", dataFolder); + } + + @Override + protected void loadKeys() { + + } +} diff --git a/src/main/java/com/gmail/nossr50/events/experience/McMMOPlayerLevelUpEvent.java b/src/main/java/com/gmail/nossr50/events/experience/McMMOPlayerLevelUpEvent.java index 20badcf7c..2ec305b4d 100644 --- a/src/main/java/com/gmail/nossr50/events/experience/McMMOPlayerLevelUpEvent.java +++ b/src/main/java/com/gmail/nossr50/events/experience/McMMOPlayerLevelUpEvent.java @@ -1,6 +1,7 @@ package com.gmail.nossr50.events.experience; import com.gmail.nossr50.datatypes.experience.XPGainReason; +import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; diff --git a/src/main/java/com/gmail/nossr50/listeners/SelfListener.java b/src/main/java/com/gmail/nossr50/listeners/SelfListener.java index 0fe8eec9f..0e2d063f6 100644 --- a/src/main/java/com/gmail/nossr50/listeners/SelfListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/SelfListener.java @@ -20,6 +20,11 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + public class SelfListener implements Listener { //Used in task scheduling and other things private final mcMMO plugin; @@ -31,10 +36,10 @@ public class SelfListener implements Listener { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPlayerLevelUp(McMMOPlayerLevelUpEvent event) { - Player player = event.getPlayer(); - PrimarySkillType skill = event.getSkill(); + final Player player = event.getPlayer(); + final PrimarySkillType skill = event.getSkill(); - McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); + final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); //TODO: Handle proper validation at the event level if(mcMMOPlayer == null || !mcMMOPlayer.getProfile().isLoaded()) @@ -55,6 +60,13 @@ public class SelfListener implements Listener { if(mcMMO.p.getGeneralConfig().getScoreboardsEnabled()) ScoreboardManager.handleLevelUp(player, skill); } + + final Set levelsAchieved = new LinkedHashSet<>(); + for(int i = 0; i < event.getLevelsGained(); i++) + { + levelsAchieved.add(event.getSkillLevel()); + } + plugin.getLevelUpCommandManager().apply(mcMMOPlayer, skill, levelsAchieved); } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index ebf4b6e73..54d8e0d25 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -2,6 +2,7 @@ package com.gmail.nossr50; import com.gmail.nossr50.chat.ChatManager; import com.gmail.nossr50.commands.CommandManager; +import com.gmail.nossr50.commands.levelup.LevelUpCommandManager; import com.gmail.nossr50.config.*; import com.gmail.nossr50.config.experience.ExperienceConfig; import com.gmail.nossr50.config.mods.ArmorConfigManager; @@ -88,6 +89,7 @@ public class mcMMO extends JavaPlugin { private static DatabaseManager databaseManager; private static FormulaManager formulaManager; private static UpgradeManager upgradeManager; + private static LevelUpCommandManager levelUpCommandManager; private static MaterialMapStore materialMapStore; private static PlayerLevelUtils playerLevelUtils; private static SmeltingTracker smeltingTracker; @@ -137,15 +139,8 @@ public class mcMMO extends JavaPlugin { private GeneralConfig generalConfig; private AdvancedConfig advancedConfig; -// private RepairConfig repairConfig; -// private SalvageConfig salvageConfig; -// private PersistentDataConfig persistentDataConfig; -// private ChatConfig chatConfig; -// private CoreSkillsConfig coreSkillsConfig; -// private RankConfig rankConfig; -// private TreasureConfig treasureConfig; -// private FishingTreasureConfig fishingTreasureConfig; -// private SoundConfig soundConfig; + + private CommandOnLevelUpConfig commandOnLevelUpConfig; public mcMMO() { p = this; @@ -173,6 +168,7 @@ public class mcMMO extends JavaPlugin { //Init configs advancedConfig = new AdvancedConfig(getDataFolder()); + commandOnLevelUpConfig = new CommandOnLevelUpConfig(getDataFolder()); //Store this value so other plugins can check it isRetroModeEnabled = generalConfig.getIsRetroMode(); @@ -770,4 +766,12 @@ public class mcMMO extends JavaPlugin { public @NotNull AdvancedConfig getAdvancedConfig() { return advancedConfig; } + + public @NotNull CommandOnLevelUpConfig getCommandOnLevelUpConfig() { + return commandOnLevelUpConfig; + } + + public @NotNull LevelUpCommandManager getLevelUpCommandManager() { + return levelUpCommandManager; + } } diff --git a/src/main/resources/commandonlevelup.yml b/src/main/resources/commandonlevelup.yml new file mode 100644 index 000000000..cfb95699b --- /dev/null +++ b/src/main/resources/commandonlevelup.yml @@ -0,0 +1,23 @@ +level_up_commands: + unique_id_here: + enabled: false + condition: + skills: + - 'Swords' + - 'Axes' + levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + commands: + - "say %player% reached level %level%!" + - "say Isn't that nice?" + run_command_as: 'CONSOLE' + log_level: 'INFO' + other_unique_id_here: + enabled: false + condition: + complex_condition: + source: 'player.getLevel() % 2 == 0' + commands: + - "say %player% reached an even level!" + - "say Isn't that fun?" + run_command_as: 'CONSOLE' + log_level: 'DEBUG' diff --git a/src/test/java/com/gmail/nossr50/MMOTestEnvironmentBasic.java b/src/test/java/com/gmail/nossr50/MMOTestEnvironmentBasic.java new file mode 100644 index 000000000..f0b061924 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/MMOTestEnvironmentBasic.java @@ -0,0 +1,230 @@ +package com.gmail.nossr50; + +import com.gmail.nossr50.commands.levelup.LevelUpCommandManager; +import com.gmail.nossr50.config.*; +import com.gmail.nossr50.config.experience.ExperienceConfig; +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.player.PlayerProfile; +import com.gmail.nossr50.datatypes.skills.SubSkillType; +import com.gmail.nossr50.listeners.SelfListener; +import com.gmail.nossr50.util.*; +import com.gmail.nossr50.util.blockmeta.ChunkManager; +import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.skills.RankUtils; +import com.gmail.nossr50.util.skills.SkillTools; +import org.bukkit.Bukkit; +import org.bukkit.Server; +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.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public abstract class MMOTestEnvironmentBasic { + private final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(MMOTestEnvironmentBasic.class.getName()); + protected MockedStatic mockedMcMMO; + protected MockedStatic mockedBukkit; + protected MockedStatic mockedChatConfig; + protected MockedStatic experienceConfig; + protected MockedStatic mockedPermissions; + protected MockedStatic mockedRankUtils; + protected MockedStatic mockedUserManager; + protected MockedStatic mockedMisc; + protected MockedStatic mockedSkillTools; + protected MockedStatic mockedEventUtils; + protected SelfListener selfListener; + protected TransientEntityTracker transientEntityTracker; + protected AdvancedConfig advancedConfig; + protected CommandOnLevelUpConfig commandOnLevelUpConfig; + protected LevelUpCommandManager levelUpCommandManager; + protected GeneralConfig generalConfig; + protected RankConfig rankConfig; + protected SkillTools skillTools; + protected Server server; + protected PluginManager pluginManager; + protected World world; + + /* 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; + + protected ConsoleCommandSender consoleCommandSender; + + protected void mockBaseEnvironment() { + mockedMcMMO = Mockito.mockStatic(mcMMO.class); + mcMMO.p = mock(mcMMO.class); + when(mcMMO.p.getLogger()).thenReturn(logger); + + // place store + chunkManager = mock(ChunkManager.class); + when(mcMMO.getPlaceStore()).thenReturn(chunkManager); + + // shut off mod manager for woodcutting + when(mcMMO.getModManager()).thenReturn(mock(ModManager.class)); + when(mcMMO.getModManager().isCustomLog(any())).thenReturn(false); + + // chat config + mockedChatConfig = Mockito.mockStatic(ChatConfig.class); + when(ChatConfig.getInstance()).thenReturn(mock(ChatConfig.class)); + + // general config + mockGeneralConfig(); + + // rank config + mockRankConfig(); + + // wire advanced config + mockAdvancedConfig(); + + // wire command level up config + mockLevelUpCommand(); + + // wire experience config + mockExperienceConfig(); + + // wire skill tools + this.skillTools = new SkillTools(mcMMO.p); + when(mcMMO.p.getSkillTools()).thenReturn(skillTools); + + this.transientEntityTracker = new TransientEntityTracker(); + when(mcMMO.getTransientEntityTracker()).thenReturn(transientEntityTracker); + + mockPermissions(); + + mockedRankUtils = Mockito.mockStatic(RankUtils.class); + + // wire server + this.server = mock(Server.class); + when(mcMMO.p.getServer()).thenReturn(server); + + // wire plugin manager + this.pluginManager = mock(PluginManager.class); + when(server.getPluginManager()).thenReturn(pluginManager); + + // wire world + this.world = mock(World.class); + + // wire Misc + 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"); + mockedBukkit = Mockito.mockStatic(Bukkit.class); + when(Bukkit.getConsoleSender()).thenReturn(consoleCommandSender); + } + + private void mockPermissions() { + mockedPermissions = Mockito.mockStatic(Permissions.class); + when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true); + // Mockito.when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true); + when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true); + // Mockito.when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true); + } + + private void mockRankConfig() { + rankConfig = mock(RankConfig.class); + } + + private void mockAdvancedConfig() { + this.advancedConfig = mock(AdvancedConfig.class); + when(mcMMO.p.getAdvancedConfig()).thenReturn(advancedConfig); + } + + private void mockLevelUpCommand() { + this.commandOnLevelUpConfig = mock(CommandOnLevelUpConfig.class); + when(mcMMO.p.getCommandOnLevelUpConfig()).thenReturn(commandOnLevelUpConfig); + + this.levelUpCommandManager = Mockito.spy(new LevelUpCommandManager(mcMMO.p)); + when(mcMMO.p.getLevelUpCommandManager()).thenReturn(levelUpCommandManager); + } + + private void mockGeneralConfig() { + generalConfig = mock(GeneralConfig.class); + when(generalConfig.getLocale()).thenReturn("en_US"); + when(mcMMO.p.getGeneralConfig()).thenReturn(generalConfig); + } + + private void mockExperienceConfig() { + experienceConfig = Mockito.mockStatic(ExperienceConfig.class); + + when(ExperienceConfig.getInstance()).thenReturn(mock(ExperienceConfig.class)); + + // Combat + when(ExperienceConfig.getInstance().getCombatXP(EntityType.COW)).thenReturn(1D); + } + + protected void cleanupBaseEnvironment() { + // Clean up resources here if needed. + if (mockedMcMMO != null) { + mockedMcMMO.close(); + } + if (mockedBukkit != null) { + mockedBukkit.close(); + } + if (experienceConfig != null) { + experienceConfig.close(); + } + if (mockedChatConfig != null) { + mockedChatConfig.close(); + } + if (mockedPermissions != null) { + mockedPermissions.close(); + } + if (mockedRankUtils != null) { + mockedRankUtils.close(); + } + if (mockedUserManager != null) { + mockedUserManager.close(); + } + if (mockedMisc != null) { + mockedMisc.close(); + } + if (mockedEventUtils != null) { + mockedEventUtils.close(); + } + } +} diff --git a/src/test/java/com/gmail/nossr50/commands/levelup/LevelUpCommandTest.java b/src/test/java/com/gmail/nossr50/commands/levelup/LevelUpCommandTest.java new file mode 100644 index 000000000..66b872183 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/commands/levelup/LevelUpCommandTest.java @@ -0,0 +1,62 @@ +package com.gmail.nossr50.commands.levelup; + +import com.gmail.nossr50.MMOTestEnvironmentBasic; +import com.gmail.nossr50.datatypes.experience.XPGainReason; +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.player.PlayerProfile; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent; +import com.gmail.nossr50.listeners.SelfListener; +import com.gmail.nossr50.mcMMO; +import org.bukkit.entity.Player; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.util.Set; +import java.util.UUID; +import java.util.function.Predicate; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class LevelUpCommandTest extends MMOTestEnvironmentBasic { + + @BeforeEach + void setUp() { + mockBaseEnvironment(); + } + + @AfterEach + void tearDown() { + cleanupBaseEnvironment(); + } + + @Test + void levelInMiningShouldRunCommand() { + // validate command manager has zero registered commands + assert mcMMO.p.getLevelUpCommandManager().isEmpty(); + final PrimarySkillType skillType = PrimarySkillType.MINING; + final Predicate predicate = (i) -> true; + final LevelUpCommand levelUpCommand = spy(new LevelUpCommandImpl( + predicate, + "say hello", + Set.of(skillType), + true)); + mcMMO.p.getLevelUpCommandManager().registerCommand(levelUpCommand); + + // GIVEN level up command that should always execute for Mining is registered with command manager + + int levelsGained = 5; + // WHEN player gains 5 levels in mining + McMMOPlayerLevelUpEvent event = new McMMOPlayerLevelUpEvent(player, PrimarySkillType.MINING, levelsGained, XPGainReason.PVE); + selfListener.onPlayerLevelUp(event); + + // THEN the command should be run + // check the mockito spy for level up command manager for executing the command + Mockito.verify(levelUpCommandManager).apply(any(), any(), any()); + Mockito.verify(levelUpCommand).apply(any(), any(), any()); + } +} \ No newline at end of file