From ad02113a8150e5188edb5054d5985231e11160b3 Mon Sep 17 00:00:00 2001 From: benrobson Date: Thu, 10 Apr 2025 23:33:23 +1000 Subject: [PATCH 1/7] Refactor to use YML and remove SQL. Base implementation needs additional fixes. --- pom.xml | 6 + .../PlayerHeadHunt/HeadChatController.java | 12 +- .../modularsoft/PlayerHeadHunt/HeadQuery.java | 253 +++++------------- .../PlayerHeadHunt/HeadWorldController.java | 42 +-- .../PlayerHeadHunt/PlayerHeadHuntMain.java | 50 +--- .../PlayerHeadHunt/PluginConfig.java | 12 - .../PlayerHeadHunt/commands/clearheads.java | 15 +- .../PlayerHeadHunt/commands/countheads.java | 21 +- .../PlayerHeadHunt/commands/leaderboard.java | 9 +- .../PlayerHeadHunt/events/HeadFindEvent.java | 48 ++-- .../events/HeadHunterOnJoin.java | 16 +- .../helpers/YamlFileManager.java | 78 ++++++ src/main/resources/config.yml | 6 - 13 files changed, 258 insertions(+), 310 deletions(-) create mode 100644 src/main/java/org/modularsoft/PlayerHeadHunt/helpers/YamlFileManager.java diff --git a/pom.xml b/pom.xml index be2b865..4e970b9 100644 --- a/pom.xml +++ b/pom.xml @@ -100,6 +100,12 @@ 1.18.30 provided + + + org.yaml + snakeyaml + 2.0 + diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadChatController.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadChatController.java index a545a0f..52dfbdc 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadChatController.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadChatController.java @@ -9,13 +9,13 @@ import java.util.List; -import static org.modularsoft.PlayerHeadHunt.HeadQuery.foundHeadsAlreadyCount; - public class HeadChatController { private final PlayerHeadHuntMain plugin; + private final HeadQuery headQuery; - public HeadChatController(PlayerHeadHuntMain plugin) { + public HeadChatController(PlayerHeadHuntMain plugin, HeadQuery headQuery) { this.plugin = plugin; + this.headQuery = headQuery; } public void headFoundResponse(Player player, boolean hasAlreadyBeenFound, int headCount, int x, int y, int z) { @@ -28,7 +28,7 @@ public void headFoundResponse(Player player, boolean hasAlreadyBeenFound, int he player.playSound(player.getLocation(), plugin.config().getHeadFoundSound(), 1, 1); } - int otherPlayerFoundHead = foundHeadsAlreadyCount(plugin, x, y, z) - 1; + int otherPlayerFoundHead = headQuery.foundHeadsAlreadyCount(x, y, z) - 1; String otherPlayersHaveFoundSuffix; if (otherPlayerFoundHead == 0) { @@ -100,9 +100,9 @@ public void newPlayerJoinsTheHunt(Player player) { } public void playersOwnHeadCountResponse(Player player) { - // Players wants to see their own head count + // Use the instance of HeadQuery to call the method player.sendMessage(plugin.config().getLangHeadCount() - .replace("%FOUNDHEADS%", "" + HeadQuery.foundHeadsCount(plugin, player)) + .replace("%FOUNDHEADS%", "" + headQuery.foundHeadsCount(player)) .replace("%NUMBEROFHEADS%", "" + plugin.config().getTotalHeads())); } diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java index af057aa..959d74e 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java @@ -2,217 +2,102 @@ import lombok.Getter; import org.bukkit.entity.Player; +import org.modularsoft.PlayerHeadHunt.helpers.YamlFileManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class HeadQuery { - public record HeadHunter(@Getter String name, @Getter int headsCollected) { } - - /** - * @param plugin The PlayerHeadHunt main plugin - * @param player The player to check - * @return Returns the number of heads found by the player - */ - public static int foundHeadsCount(PlayerHeadHuntMain plugin, Player player) { - String playerUUID = "" + player.getUniqueId(); - - try { - // Check how many heads the player has collected. - PreparedStatement foundHeadsCount = plugin.getConnection().prepareStatement( - "SELECT headsCollected AS 'heads' FROM playerdata WHERE uuid=?"); - foundHeadsCount.setString(1, playerUUID); - ResultSet results = foundHeadsCount.executeQuery(); + private final YamlFileManager yamlFileManager; - if (results.next()) return results.getInt("heads"); - } catch (SQLException e) { - e.printStackTrace(); - player.sendMessage(plugin.config().getLangDatabaseConnectionError()); - } - return 0; + public HeadQuery(YamlFileManager yamlFileManager) { + this.yamlFileManager = yamlFileManager; } - /** - * @param plugin The PlayerHeadHunt main plugin - * @param xCord The x int to check - * @param yCord The y int to check - * @param zCord The z int to check - * @return Returns the number of other players who have found this head already - */ - public static int foundHeadsAlreadyCount(PlayerHeadHuntMain plugin, int xCord, int yCord, int zCord) { - try { - // Check how many heads the player has collected. - PreparedStatement foundHeadsCount = plugin.getConnection().prepareStatement( - "SELECT COUNT(*) AS total_heads FROM heads WHERE headcordx=? AND headcordy=? AND headcordz=?;"); - foundHeadsCount.setInt(1, xCord); - foundHeadsCount.setInt(2, yCord); - foundHeadsCount.setInt(3, zCord); - ResultSet results = foundHeadsCount.executeQuery(); + public record HeadHunter(@Getter String name, @Getter int headsCollected) { } - if (results.next()) return results.getInt("total_heads"); - } catch (SQLException e) { - e.printStackTrace(); - } - return 0; + public int foundHeadsCount(Player player) { + String playerUUID = player.getUniqueId().toString(); + Map data = yamlFileManager.getData(); + Map playerData = (Map) data.get(playerUUID); + return playerData != null ? (int) playerData.getOrDefault("headsCollected", 0) : 0; } - /** - * Clears the number of heads found by the player to 0 - * @param plugin The PlayerHeadHunt main plugin - * @param player The player to reset - * @return Returns true if the clear was successful. - */ - public static boolean clearHeads(PlayerHeadHuntMain plugin, Player player) { - String playerUUID = "" + player.getUniqueId(); - - // - // Database Query - // Check how many heads the player has collected. - // - try { - PreparedStatement clearHeadsStatement = plugin.getConnection().prepareStatement( - "DELETE FROM heads WHERE playerid=(SELECT id FROM playerdata WHERE uuid=?)"); - clearHeadsStatement.setString(1, playerUUID); - clearHeadsStatement.executeUpdate(); - - PreparedStatement resetHeadCountStatement = plugin.getConnection().prepareStatement( - "UPDATE playerdata SET headsCollected = 0 WHERE uuid = ?"); - resetHeadCountStatement.setString(1, playerUUID); - resetHeadCountStatement.executeUpdate(); - return true; - } catch (SQLException e) { - e.printStackTrace(); - player.sendMessage(plugin.config().getLangDatabaseConnectionError()); - } - - return false; + public int foundHeadsAlreadyCount(int xCord, int yCord, int zCord) { + Map data = yamlFileManager.getData(); + List> heads = (List>) data.get("heads"); + return (int) heads.stream() + .filter(head -> head.get("x").equals(xCord) && head.get("y").equals(yCord) && head.get("z").equals(zCord)) + .count(); } - /** - * Checks if the player has found a head in the specified position before - * @param plugin The PlayerHeadHunt main plugin - * @param player The player who found the head - * @param x X position of the head - * @param y Y position of the head - * @param z Z position of the head - * @return True if the head has already been found - */ - public static boolean hasAlreadyCollectedHead(PlayerHeadHuntMain plugin, Player player, int x, int y, int z) { + public boolean clearHeads(Player player) { String playerUUID = player.getUniqueId().toString(); - - try { - // Check if the player has already found that Player Head before. - PreparedStatement hasAlreadyFoundHeadStatement = plugin.getConnection().prepareStatement( - "SELECT e.* FROM heads e JOIN playerdata p ON e.playerid = p.id WHERE p.uuid = ? AND headcordx=? AND headcordy=? AND headcordz=?"); - hasAlreadyFoundHeadStatement.setString(1, playerUUID); - hasAlreadyFoundHeadStatement.setString(2, "" + x); - hasAlreadyFoundHeadStatement.setString(3, "" + y); - hasAlreadyFoundHeadStatement.setString(4, "" + z); - ResultSet results = hasAlreadyFoundHeadStatement.executeQuery(); - - // Return's true if we already found the head. - return results.next(); - } catch (SQLException e) { - e.printStackTrace(); - player.sendMessage(plugin.config().getLangDatabaseConnectionError()); - } - return false; + Map data = yamlFileManager.getData(); + data.remove(playerUUID); + yamlFileManager.save(); + return true; } - /** - * Ties the location of the head (x, y, z) to the player so that we know in - * the future that this head has been found - * @param plugin The PlayerHeadHunt main plugin - * @param player The player who found the head - * @param x X position of the head - * @param y Y position of the head - * @param z Z position of the head - */ - public static void insertCollectedHead(PlayerHeadHuntMain plugin, Player player, int x, int y, int z) { + public boolean hasAlreadyCollectedHead(Player player, int x, int y, int z) { String playerUUID = player.getUniqueId().toString(); + Map data = yamlFileManager.getData(); + + // Safely retrieve the "heads" list + List> heads = (List>) data.get("heads"); + if (heads == null) { + // Initialize the "heads" list if it is null + heads = new ArrayList<>(); + data.put("heads", heads); + yamlFileManager.save(); + } - try { - // Insert Player Head - PreparedStatement insertCollectedHeadStatement = plugin.getConnection().prepareStatement( - "INSERT INTO heads (playerid, headcordx, headcordy, headcordz) " + - "VALUES ((SELECT id FROM playerdata WHERE uuid=?), ?, ?, ?)"); - insertCollectedHeadStatement.setString(1, playerUUID); - insertCollectedHeadStatement.setString(2, String.valueOf(x)); - insertCollectedHeadStatement.setString(3, String.valueOf(y)); - insertCollectedHeadStatement.setString(4, String.valueOf(z)); - insertCollectedHeadStatement.executeUpdate(); + // Check if the player has already collected the head + return heads.stream() + .anyMatch(head -> head.get("playerUUID").equals(playerUUID) && + head.get("x").equals(x) && + head.get("y").equals(y) && + head.get("z").equals(z)); + } - PreparedStatement updatePlayersHeadsCollectedStatement = plugin.getConnection().prepareStatement( - "UPDATE playerdata SET headsCollected = headsCollected + 1 WHERE uuid = ?"); - updatePlayersHeadsCollectedStatement.setString(1, "" + player.getUniqueId()); - updatePlayersHeadsCollectedStatement.executeUpdate(); - } catch (SQLException e) { - e.printStackTrace(); - player.sendMessage(plugin.config().getLangDatabaseConnectionError()); + public void insertCollectedHead(Player player, int x, int y, int z) { + String playerUUID = player.getUniqueId().toString(); + Map data = yamlFileManager.getData(); + List> heads = (List>) data.get("heads"); + heads.add(Map.of("playerUUID", playerUUID, "x", x, "y", y, "z", z)); + Map playerData = (Map) data.get(playerUUID); + if (playerData == null) { + playerData = Map.of("headsCollected", 1); + data.put(playerUUID, playerData); + } else { + playerData.put("headsCollected", (int) playerData.get("headsCollected") + 1); } + yamlFileManager.save(); } - /** - * @param plugin The PlayerHeadHunt main plugin - * @param player The player who joined - * @return Returns true if the player specified was indeed a new player. - */ - public static boolean addNewHunter(PlayerHeadHuntMain plugin, Player player) { + public boolean addNewHunter(Player player) { String playerUUID = player.getUniqueId().toString(); String username = player.getName(); - - try { - // Check if a player has been added into the database already. - PreparedStatement findstatement = plugin.getConnection().prepareStatement( - "SELECT * FROM playerdata WHERE uuid=?"); - findstatement.setString(1, playerUUID); - ResultSet results = findstatement.executeQuery(); - - // The player already exists - if (results.next()) - return false; - - PreparedStatement insertstatement = plugin.getConnection().prepareStatement( - "INSERT INTO playerdata (uuid, username) VALUES (?, ?)"); - insertstatement.setString(1, playerUUID); - insertstatement.setString(2, username); - insertstatement.executeUpdate(); - return true; - } catch (SQLException e) { - e.printStackTrace(); - player.sendMessage(plugin.config().getLangDatabaseConnectionError()); + Map data = yamlFileManager.getData(); + if (data.containsKey(playerUUID)) { + return false; } - return false; + data.put(playerUUID, Map.of("username", username, "headsCollected", 0)); + yamlFileManager.save(); + return true; } - /** - * @param plugin The PlayerHeadHunt main plugin - * @param player The player who issued the command - * @return Returns a list of the Best Hunters. idx 0 is the best player and so on... - */ - public static List getBestHunters(PlayerHeadHuntMain plugin, Player player, int topHunters) { + public List getBestHunters(int topHunters) { + Map data = yamlFileManager.getData(); List bestHunters = new ArrayList<>(); - - try { - // Check if a player has been added into the database already. - PreparedStatement getHeadHuntersStatement = plugin.getConnection().prepareStatement( - "SELECT username, headsCollected, id FROM playerdata ORDER BY headsCollected DESC LIMIT ?"); - getHeadHuntersStatement.setInt(1, topHunters); - ResultSet results = getHeadHuntersStatement.executeQuery(); - - // The player already exists - while (results.next()) { - String name = results.getString("username"); - int headsCollected = results.getInt("headsCollected"); - bestHunters.add(new HeadHunter(name, headsCollected)); + data.forEach((key, value) -> { + if (value instanceof Map) { + Map playerData = (Map) value; + bestHunters.add(new HeadHunter((String) playerData.get("username"), (int) playerData.get("headsCollected"))); } - } catch (SQLException e) { - e.printStackTrace(); - player.sendMessage(plugin.config().getLangDatabaseConnectionError()); - } - return bestHunters; + }); + bestHunters.sort((a, b) -> Integer.compare(b.headsCollected(), a.headsCollected())); + return bestHunters.subList(0, Math.min(topHunters, bestHunters.size())); } -} +} \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java index ac130bd..9e6d7bb 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java @@ -21,28 +21,20 @@ import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; +import org.modularsoft.PlayerHeadHunt.helpers.YamlFileManager; -import java.util.Objects; -import java.util.Random; -import java.util.UUID; +import java.io.File; +import java.util.*; -/** - * This class controls any functionality that requires access to the world. - */ public class HeadWorldController { private final PlayerHeadHuntMain plugin; + private final YamlFileManager yamlFileManager; public HeadWorldController(PlayerHeadHuntMain plugin) { this.plugin = plugin; + this.yamlFileManager = new YamlFileManager(new File(plugin.getDataFolder(), "player-data.yml")); } - /** - * Using the lower region and upper region areas in the config file, count the - * number of heads in the region (technically it counts the number of player heads) - * and update the "totalHeads" field in the config to reflect the answer. - * - * Note: Heads that have disappeared temporarily will not show up in this count. - */ public void countHeadsInRegion() { String headBlock = plugin.config().getHeadBlock().toLowerCase(); BlockVector3 upperRegion = plugin.config().getUpperRegion(); @@ -53,17 +45,28 @@ public void countHeadsInRegion() { Mask mask = new BlockTypeMask(world, BlockTypes.get(headBlock)); try (EditSession editSession = WorldEdit.getInstance().newEditSession(world)) { - int countedblocks = editSession.countBlocks(selection, mask); - plugin.getServer().getConsoleSender().sendMessage("There are " + countedblocks + " total heads in the region"); + int countedBlocks = editSession.countBlocks(selection, mask); + plugin.getServer().getConsoleSender().sendMessage("There are " + countedBlocks + " total heads in the region"); - // Put total amount into config file. - plugin.config().setTotalHeads(countedblocks); + // Update the HEAD.HEADTOTAL in the plugin config + plugin.config().setTotalHeads(countedBlocks); plugin.config().save(); } } public void playerCollectedHead(Player player, Block block, int x, int y, int z) { - HeadQuery.insertCollectedHead(plugin, player, x, y, z); + Map data = yamlFileManager.getData(); + List> collectedHeads = (List>) data.get("collectedHeads"); + + // Initialize the list if it is null + if (collectedHeads == null) { + collectedHeads = new ArrayList<>(); + data.put("collectedHeads", collectedHeads); + } + + collectedHeads.add(Map.of("player", player.getUniqueId().toString(), "x", x, "y", y, "z", z)); + yamlFileManager.save(); + Material blockType = block.getType(); BlockData blockData = block.getBlockData(); @@ -77,7 +80,6 @@ public void run() { replaceHeadBlock(blockType, blockData, x, y, z); } }.runTaskLater(plugin, headRespawnTimer); - } private void breakBlock(int x, int y, int z) { @@ -105,4 +107,4 @@ private String getRandomHead() { int skins = plugin.config().getHeadSkins().size(); return plugin.config().getHeadSkins().get(random.nextInt(0, skins)); } -} +} \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java b/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java index b95faf7..bf2827d 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java @@ -12,7 +12,9 @@ import org.bukkit.command.ConsoleCommandSender; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; +import org.modularsoft.PlayerHeadHunt.helpers.YamlFileManager; +import java.io.File; import java.sql.Connection; import java.sql.SQLException; import java.util.Objects; @@ -29,33 +31,32 @@ public PluginConfig config() { @Override public void onEnable() { // Generate configuration file - // plugin.saveDefaultConfig(); + saveDefaultConfig(); config = new PluginConfig(this); console = getServer().getConsoleSender(); - HeadChatController headChatController = new HeadChatController(this); + YamlFileManager yamlFileManager = new YamlFileManager(new File(getDataFolder(), "player-data.yml")); + HeadQuery headQuery = new HeadQuery(yamlFileManager); + HeadChatController headChatController = new HeadChatController(this, headQuery); HeadWorldController headWorldController = new HeadWorldController(this); HeadHatController headHatController = new HeadHatController(this); HeadScoreboardController headScoreboardController = new HeadScoreboardController(this); - // Connect to the database - establishConnection(); - // Do an initial calculation of the number of heads. This can be // manually recalculated with the relevant command. headWorldController.countHeadsInRegion(); // Plugin Event Register PluginManager pluginmanager = getServer().getPluginManager(); - pluginmanager.registerEvents(new HeadFindEvent(this, headWorldController, headChatController, headHatController, headScoreboardController), this); - pluginmanager.registerEvents(new HeadHunterOnJoin(this, headChatController, headScoreboardController), this); + pluginmanager.registerEvents(new HeadFindEvent(this, headWorldController, headChatController, headHatController, headScoreboardController, headQuery), this); + pluginmanager.registerEvents(new HeadHunterOnJoin(this, headChatController, headScoreboardController, headQuery), this); pluginmanager.registerEvents(new HeadHatOnHead(), this); // Command Registry Objects.requireNonNull(getCommand("heads")).setExecutor(new heads(this, headChatController)); - Objects.requireNonNull(getCommand("clearheads")).setExecutor(new clearheads(this, headChatController, headHatController, headScoreboardController)); - Objects.requireNonNull(getCommand("countheads")).setExecutor(new countheads(this, headWorldController, headScoreboardController)); - Objects.requireNonNull(getCommand("leaderboard")).setExecutor(new leaderboard(this, headChatController)); + Objects.requireNonNull(getCommand("clearheads")).setExecutor(new clearheads(this, headChatController, headHatController, headScoreboardController, headQuery)); + Objects.requireNonNull(getCommand("countheads")).setExecutor(new countheads(this, headWorldController, headScoreboardController, headQuery)); + Objects.requireNonNull(getCommand("heads")).setExecutor(new heads(this, headChatController)); // Plugin Load Message console.sendMessage(ChatColor.GREEN + getDescription().getName() + " is now enabled."); @@ -69,33 +70,4 @@ public void onDisable() { // Plugin Shutdown Message console.sendMessage(ChatColor.RED + getDescription().getName() + " is now disabled."); } - - public void establishConnection() { - try { - Class.forName("com.mysql.jdbc.Driver"); - MysqlDataSource dataSource = new MysqlDataSource(); - dataSource.setServerName(config.getDatabaseHost()); - dataSource.setPort(config.getDatabasePort()); - dataSource.setDatabaseName(config.getDatabaseName()); - dataSource.setUser(config.getDatabaseUsername()); - dataSource.setPassword(config.getDatabasePassword()); - connection = dataSource.getConnection(); - } catch (SQLException | ClassNotFoundException e) { - getLogger().info(config.getLangDatabaseConnectionError()); - e.printStackTrace(); - } - } - - public Connection getConnection() { - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - getLogger().info(config.getLangDatabaseConnectionError()); - e.printStackTrace(); - } - } - establishConnection(); - return connection; - } } diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/PluginConfig.java b/src/main/java/org/modularsoft/PlayerHeadHunt/PluginConfig.java index c9d3816..2ed8059 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/PluginConfig.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/PluginConfig.java @@ -14,12 +14,6 @@ public class PluginConfig { private final PlayerHeadHuntMain plugin; private final FileConfiguration config; - @Getter private final String databaseHost; - @Getter private final int databasePort; - @Getter private final String databaseName; - @Getter private final String databaseUsername; - @Getter private final String databasePassword; - @Getter private final boolean milestoneHatFeatureEnabled; @Getter private final boolean milestoneMessageFeatureEnabled; @@ -61,12 +55,6 @@ public PluginConfig(PlayerHeadHuntMain plugin) { this.plugin = plugin; this.config = plugin.getConfig(); - databaseHost = config.getString("DATABASE.HOST"); - databasePort = config.getInt("DATABASE.PORT"); - databaseName = config.getString("DATABASE.DATABASE"); - databaseUsername = config.getString("DATABASE.USERNAME"); - databasePassword = config.getString("DATABASE.PASSWORD"); - milestoneHatFeatureEnabled = config.getBoolean("FEATURE.MILESTONEHAT"); milestoneMessageFeatureEnabled = config.getBoolean("FEATURE.MILESTONEMESSAGE"); diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/clearheads.java b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/clearheads.java index 1770cbf..9bc1592 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/clearheads.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/clearheads.java @@ -12,13 +12,18 @@ public class clearheads implements CommandExecutor { private final HeadChatController headChatController; private final HeadHatController headHatController; private final HeadScoreboardController scoreboardController; + private final HeadQuery headQuery; - public clearheads(PlayerHeadHuntMain plugin, HeadChatController headChatController, - HeadHatController headHatController, HeadScoreboardController scoreboardController) { + public clearheads(PlayerHeadHuntMain plugin, + HeadChatController headChatController, + HeadHatController headHatController, + HeadScoreboardController scoreboardController, + HeadQuery headQuery) { this.plugin = plugin; this.headChatController = headChatController; this.headHatController = headHatController; this.scoreboardController = scoreboardController; + this.headQuery = headQuery; } @Override @@ -33,12 +38,12 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N return true; } - if (!HeadQuery.clearHeads(plugin, player)) + if (!headQuery.clearHeads(player)) // Use the HeadQuery instance to call clearHeads return true; headChatController.playerClearedTheirHeadsResponse(player); headHatController.clearHelmet(player); - scoreboardController.reloadScoreboard(player, HeadQuery.foundHeadsCount(plugin, player)); + scoreboardController.reloadScoreboard(player, headQuery.foundHeadsCount(player)); // Use headQuery for foundHeadsCount return true; } -} +} \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/countheads.java b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/countheads.java index 1bb92c9..b2890f3 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/countheads.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/countheads.java @@ -15,11 +15,16 @@ public class countheads implements CommandExecutor { private final PlayerHeadHuntMain plugin; private final HeadWorldController headWorldController; private final HeadScoreboardController headScoreboardController; + private final HeadQuery headQuery; - public countheads(PlayerHeadHuntMain plugin, HeadWorldController headWorldController, HeadScoreboardController headScoreboardController) { + public countheads(PlayerHeadHuntMain plugin, + HeadWorldController headWorldController, + HeadScoreboardController headScoreboardController, + HeadQuery headQuery) { this.plugin = plugin; this.headWorldController = headWorldController; this.headScoreboardController = headScoreboardController; + this.headQuery = headQuery; } @Override @@ -34,14 +39,22 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N return true; } + // Count heads in the region headWorldController.countHeadsInRegion(); + + // Update HEAD.HEADTOTAL in the configuration + int totalHeads = plugin.config().getTotalHeads(); + plugin.getConfig().set("HEAD.HEADTOTAL", totalHeads); + plugin.saveConfig(); // Save the updated configuration to disk + + // Notify the sender BlockVector3 upper = plugin.config().getUpperRegion(); BlockVector3 lower = plugin.config().getLowerRegion(); - sender.sendMessage("There are " + plugin.config().getTotalHeads() + - " total heads in " + lower + ", " + upper + "."); + sender.sendMessage("There are " + totalHeads + " total heads in " + lower + ", " + upper + "."); + // Update the scoreboard for all online players for (Player otherPlayer : plugin.getServer().getOnlinePlayers()) { - int headsFound = HeadQuery.foundHeadsCount(plugin, otherPlayer); + int headsFound = headQuery.foundHeadsCount(otherPlayer); headScoreboardController.reloadScoreboard(otherPlayer, headsFound); } return true; diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java index c8a582c..8b12021 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java @@ -14,10 +14,12 @@ public class leaderboard implements CommandExecutor { private final PlayerHeadHuntMain plugin; private final HeadChatController headChatController; + private final HeadQuery headQuery; // Add HeadQuery instance - public leaderboard(PlayerHeadHuntMain plugin, HeadChatController headChatController) { + public leaderboard(PlayerHeadHuntMain plugin, HeadChatController headChatController, HeadQuery headQuery) { this.plugin = plugin; this.headChatController = headChatController; + this.headQuery = headQuery; // Initialize HeadQuery } @Override @@ -27,8 +29,9 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N return true; } - List bestHunters = HeadQuery.getBestHunters(plugin, player, 5); + // Use the HeadQuery instance to call getBestHunters + List bestHunters = headQuery.getBestHunters(5); headChatController.showLeaderBoardResponse(player, bestHunters); return true; } -} +} \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadFindEvent.java b/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadFindEvent.java index 65e47fc..13272be 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadFindEvent.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadFindEvent.java @@ -1,14 +1,13 @@ package org.modularsoft.PlayerHeadHunt.events; +import org.bukkit.Material; import org.modularsoft.PlayerHeadHunt.*; import org.modularsoft.PlayerHeadHunt.helpers.HeadMileStone; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; import java.util.Map; @@ -20,14 +19,17 @@ public class HeadFindEvent implements Listener { private final HeadScoreboardController headScoreboardController; private final Map milestones; + private final HeadQuery headQuery; + public HeadFindEvent(PlayerHeadHuntMain plugin, HeadWorldController headWorldController, HeadChatController headChatController, HeadHatController headHatController, - HeadScoreboardController headScoreboardController) { + HeadScoreboardController headScoreboardController, HeadQuery headQuery) { this.plugin = plugin; this.headWorldController = headWorldController; this.headChatController = headChatController; this.headHatController = headHatController; this.headScoreboardController = headScoreboardController; + this.headQuery = headQuery; this.milestones = plugin.config().getHeadMilestones(); } @@ -40,17 +42,18 @@ public void onHeadFind(PlayerInteractEvent event) { Player player = event.getPlayer(); Block block = event.getClickedBlock(); - int x = block.getX(); // Can't be null. Would have been found by isFindHeadEvent + int x = block.getX(); int y = block.getY(); int z = block.getZ(); - if (HeadQuery.hasAlreadyCollectedHead(plugin, player, x, y, z)) { + + if (headQuery.hasAlreadyCollectedHead(player, x, y, z)) { headChatController.headFoundResponse(player, true, 0, x, y, z); return; } headWorldController.playerCollectedHead(player, block, x, y, z); - int foundHeads = HeadQuery.foundHeadsCount(plugin, player); + int foundHeads = headQuery.foundHeadsCount(player); headScoreboardController.reloadScoreboard(player, foundHeads); if (foundHeads == 1) { @@ -58,8 +61,6 @@ public void onHeadFind(PlayerInteractEvent event) { return; } - // Trigger any milestones if they are relevant. We'll use the milestone text if it's available - // otherwise we'll draw the default text to the screen. if (milestones.containsKey(foundHeads)) { milestones.get(foundHeads).trigger(headChatController, headHatController, player, event); } else { @@ -68,26 +69,23 @@ public void onHeadFind(PlayerInteractEvent event) { } private boolean isFindHeadEvent(PlayerInteractEvent event) { - if (event == null) - return false; - - Block block = event.getClickedBlock(); - if (block == null) + // Check if the event involves a block and if the block is a specific type (e.g., a head block) + if (event.getClickedBlock() == null) { return false; + } - EquipmentSlot equipSlot = event.getHand(); - if (equipSlot == null) - return false; + String headBlockConfig = plugin.getConfig().getString("HEAD.HEADBLOCK"); + if (headBlockConfig == null) { + return false; // Configuration is missing or invalid + } - // This stops the event from firing twice, since the event fires for each hand. - if (equipSlot.equals(EquipmentSlot.OFF_HAND) || - event.getAction().equals(Action.LEFT_CLICK_BLOCK) || - event.getAction().equals(Action.LEFT_CLICK_AIR) || - event.getAction().equals(Action.RIGHT_CLICK_AIR)) - return false; + Material headBlockMaterial; + try { + headBlockMaterial = Material.valueOf(headBlockConfig.toUpperCase()); + } catch (IllegalArgumentException e) { + return false; // Invalid material in the configuration + } - // Only continue if we clicked on a head - String blockType = "" + block.getType(); - return plugin.config().getHeadBlock().equals(blockType); + return event.getClickedBlock().getType() == headBlockMaterial; } } \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadHunterOnJoin.java b/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadHunterOnJoin.java index a534385..074ecf7 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadHunterOnJoin.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadHunterOnJoin.java @@ -13,12 +13,16 @@ public class HeadHunterOnJoin implements Listener { private final PlayerHeadHuntMain plugin; private final HeadChatController headChatController; private final HeadScoreboardController headScoreboardController; + private final HeadQuery headQuery; // Add HeadQuery instance - public HeadHunterOnJoin(PlayerHeadHuntMain plugin, HeadChatController headChatController, - HeadScoreboardController headScoreboardController) { + public HeadHunterOnJoin(PlayerHeadHuntMain plugin, + HeadChatController headChatController, + HeadScoreboardController headScoreboardController, + HeadQuery headQuery) { this.plugin = plugin; this.headChatController = headChatController; this.headScoreboardController = headScoreboardController; + this.headQuery = headQuery; // Initialize HeadQuery } @EventHandler @@ -26,14 +30,14 @@ public void onHeadHunterJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); String username = player.getName(); - // Give the new player a scoreboard - headScoreboardController.reloadScoreboard(player, HeadQuery.foundHeadsCount(plugin, player)); + // Use the instance of HeadQuery to call the method + headScoreboardController.reloadScoreboard(player, headQuery.foundHeadsCount(player)); - if (HeadQuery.addNewHunter(plugin, player)) { + if (headQuery.addNewHunter(player)) { // New player joined plugin.getServer().getConsoleSender().sendMessage(username + " is a new player, creating a player profile."); plugin.getServer().getConsoleSender().sendMessage("Added a new hunter, " + username + "."); headChatController.newPlayerJoinsTheHunt(player); } } -} +} \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/YamlFileManager.java b/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/YamlFileManager.java new file mode 100644 index 0000000..d608253 --- /dev/null +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/YamlFileManager.java @@ -0,0 +1,78 @@ +package org.modularsoft.PlayerHeadHunt.helpers; + +import lombok.Getter; +import lombok.Setter; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.representer.Representer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Map; + +public class YamlFileManager { + private final File file; + private final Yaml yaml; + + @Getter + @Setter + private Map data; + + public YamlFileManager(File file) { + this.file = file; + + // Configure YAML dump options + DumperOptions dumperOptions = new DumperOptions(); + dumperOptions.setIndent(2); + dumperOptions.setPrettyFlow(true); + dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + + // Configure YAML representer + Representer representer = new Representer(dumperOptions); + representer.getPropertyUtils().setSkipMissingProperties(true); + + // Configure YAML loader options + LoaderOptions loaderOptions = new LoaderOptions(); + + // Initialize YAML parser + this.yaml = new Yaml(new SafeConstructor(loaderOptions), representer, dumperOptions); + + // Load the YAML file contents + load(); + } + + @SuppressWarnings("unchecked") + public void load() { + try { + if (!file.exists()) { + file.getParentFile().mkdirs(); + file.createNewFile(); + try (FileWriter writer = new FileWriter(file)) { + writer.write("{}"); + } + } + + try (FileInputStream inputStream = new FileInputStream(file)) { + data = yaml.load(inputStream); + if (data == null) { + data = new java.util.LinkedHashMap<>(); + } + } + } catch (IOException e) { + e.printStackTrace(); + data = new java.util.LinkedHashMap<>(); + } + } + + public void save() { + try (FileWriter writer = new FileWriter(file)) { + yaml.dump(data, writer); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 563f7e8..b963488 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,9 +1,3 @@ -DATABASE: - HOST: "127.0.0.1" - PORT: 3306 - DATABASE: "PlayerHeadHunt" - USERNAME: "root" - PASSWORD: "root" REGION: UPPERREGION: X: -54 From 2c6aa3845ceee0d6e7449d9ac2b7509f35a52154 Mon Sep 17 00:00:00 2001 From: benrobson Date: Fri, 11 Apr 2025 23:22:33 +1000 Subject: [PATCH 2/7] Complete refactor with some tests, still WIP --- .../PlayerHeadHunt/HeadChatController.java | 13 ++- .../modularsoft/PlayerHeadHunt/HeadQuery.java | 107 ++++++++++++++---- .../PlayerHeadHunt/HeadWorldController.java | 27 +++-- .../PlayerHeadHunt/PlayerHeadHuntMain.java | 9 +- .../PlayerHeadHunt/events/HeadFindEvent.java | 19 ++-- 5 files changed, 129 insertions(+), 46 deletions(-) diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadChatController.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadChatController.java index 52dfbdc..d1320e1 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadChatController.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadChatController.java @@ -28,7 +28,8 @@ public void headFoundResponse(Player player, boolean hasAlreadyBeenFound, int he player.playSound(player.getLocation(), plugin.config().getHeadFoundSound(), 1, 1); } - int otherPlayerFoundHead = headQuery.foundHeadsAlreadyCount(x, y, z) - 1; + // Get the number of other players who have found this head + int otherPlayerFoundHead = Math.max(0, headQuery.foundHeadsAlreadyCount(x, y, z) - 1); String otherPlayersHaveFoundSuffix; if (otherPlayerFoundHead == 0) { @@ -39,18 +40,18 @@ public void headFoundResponse(Player player, boolean hasAlreadyBeenFound, int he } } else if (otherPlayerFoundHead == 1) { otherPlayersHaveFoundSuffix = plugin.config().getLangHeadNotFirstFinderSingle() - .replace("%OTHERPLAYERSFOUNDHEAD%", "" + otherPlayerFoundHead); + .replace("%OTHERPLAYERSFOUNDHEAD%", String.valueOf(otherPlayerFoundHead)); } else { otherPlayersHaveFoundSuffix = plugin.config().getLangHeadNotFirstFinderMultiple() - .replace("%OTHERPLAYERSFOUNDHEAD%", "" + otherPlayerFoundHead); + .replace("%OTHERPLAYERSFOUNDHEAD%", String.valueOf(otherPlayerFoundHead)); } + // Replace placeholders in the message String message = baseMessage - .replace("%FOUNDHEADS%", "" + headCount) - .replace("%NUMBEROFHEADS%", "" + plugin.config().getTotalHeads()) + .replace("%FOUNDHEADS%", String.valueOf(headCount)) + .replace("%NUMBEROFHEADS%", String.valueOf(plugin.config().getTotalHeads())) .replace("%ALREADYFOUNDHEADS%", otherPlayersHaveFoundSuffix); - // Play sound for a Player Head that is found. player.sendMessage(message); } diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java index 959d74e..3860bb1 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java @@ -5,6 +5,7 @@ import org.modularsoft.PlayerHeadHunt.helpers.YamlFileManager; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -21,14 +22,38 @@ public int foundHeadsCount(Player player) { String playerUUID = player.getUniqueId().toString(); Map data = yamlFileManager.getData(); Map playerData = (Map) data.get(playerUUID); - return playerData != null ? (int) playerData.getOrDefault("headsCollected", 0) : 0; + + if (playerData == null) { + return 0; // No data for the player, so no heads collected + } + + List> headsCollected = (List>) playerData.get("headsCollected"); + if (headsCollected == null) { + return 0; // No heads collected yet + } + + return headsCollected.size(); // Return the count of collected heads } public int foundHeadsAlreadyCount(int xCord, int yCord, int zCord) { Map data = yamlFileManager.getData(); - List> heads = (List>) data.get("heads"); + Object headsObject = data.get("heads"); + + // Ensure the "heads" object is a list + if (!(headsObject instanceof List)) { + return 0; // Return 0 if the data is not a list + } + + List heads = (List) headsObject; + + // Filter and count matching heads return (int) heads.stream() - .filter(head -> head.get("x").equals(xCord) && head.get("y").equals(yCord) && head.get("z").equals(zCord)) + .filter(head -> head instanceof Map) + .map(head -> (Map) head) + .filter(head -> + head.get("x") instanceof Integer && head.get("y") instanceof Integer && head.get("z") instanceof Integer && + head.get("x").equals(xCord) && head.get("y").equals(yCord) && head.get("z").equals(zCord) + ) .count(); } @@ -43,36 +68,51 @@ public boolean clearHeads(Player player) { public boolean hasAlreadyCollectedHead(Player player, int x, int y, int z) { String playerUUID = player.getUniqueId().toString(); Map data = yamlFileManager.getData(); + Map playerData = (Map) data.get(playerUUID); + + if (playerData == null) { + return false; // Player has no data, so they haven't collected any heads + } - // Safely retrieve the "heads" list - List> heads = (List>) data.get("heads"); - if (heads == null) { - // Initialize the "heads" list if it is null - heads = new ArrayList<>(); - data.put("heads", heads); - yamlFileManager.save(); + List> headsCollected = (List>) playerData.get("headsCollected"); + if (headsCollected == null) { + return false; // No heads collected yet } - // Check if the player has already collected the head - return heads.stream() - .anyMatch(head -> head.get("playerUUID").equals(playerUUID) && - head.get("x").equals(x) && - head.get("y").equals(y) && - head.get("z").equals(z)); + // Check if the head coordinates already exist in the list + return headsCollected.stream().anyMatch(head -> + head.get("x") == x && head.get("y") == y && head.get("z") == z); } public void insertCollectedHead(Player player, int x, int y, int z) { String playerUUID = player.getUniqueId().toString(); Map data = yamlFileManager.getData(); - List> heads = (List>) data.get("heads"); - heads.add(Map.of("playerUUID", playerUUID, "x", x, "y", y, "z", z)); Map playerData = (Map) data.get(playerUUID); + if (playerData == null) { - playerData = Map.of("headsCollected", 1); + playerData = new HashMap<>(); + playerData.put("headsCollected", new ArrayList>()); + playerData.put("headsCollectedCount", 0); data.put(playerUUID, playerData); - } else { - playerData.put("headsCollected", (int) playerData.get("headsCollected") + 1); } + + List> headsCollected = (List>) playerData.get("headsCollected"); + if (headsCollected == null) { + headsCollected = new ArrayList<>(); + playerData.put("headsCollected", headsCollected); + } + + // Add the new head coordinates to the list + Map newHead = new HashMap<>(); + newHead.put("x", x); + newHead.put("y", y); + newHead.put("z", z); + headsCollected.add(newHead); + + // Increment the count of collected heads + int currentCount = (int) playerData.getOrDefault("headsCollectedCount", 0); + playerData.put("headsCollectedCount", currentCount + 1); + yamlFileManager.save(); } @@ -80,10 +120,18 @@ public boolean addNewHunter(Player player) { String playerUUID = player.getUniqueId().toString(); String username = player.getName(); Map data = yamlFileManager.getData(); + if (data.containsKey(playerUUID)) { return false; } - data.put(playerUUID, Map.of("username", username, "headsCollected", 0)); + + // Use a mutable map to store player data + Map newPlayerData = new HashMap<>(); + newPlayerData.put("username", username); + newPlayerData.put("headsCollected", new ArrayList>()); // Initialize an empty list for collected heads + newPlayerData.put("headsCollectedCount", 0); // Optional: Track the count separately for efficiency + data.put(playerUUID, newPlayerData); + yamlFileManager.save(); return true; } @@ -91,13 +139,26 @@ public boolean addNewHunter(Player player) { public List getBestHunters(int topHunters) { Map data = yamlFileManager.getData(); List bestHunters = new ArrayList<>(); + data.forEach((key, value) -> { if (value instanceof Map) { Map playerData = (Map) value; - bestHunters.add(new HeadHunter((String) playerData.get("username"), (int) playerData.get("headsCollected"))); + + String username = (String) playerData.get("username"); + Object headsCollectedObj = playerData.get("headsCollected"); + + // Validate that headsCollected is a list + if (username != null && headsCollectedObj instanceof List) { + int headsCollectedCount = ((List) headsCollectedObj).size(); + bestHunters.add(new HeadHunter(username, headsCollectedCount)); + } } }); + + // Sort hunters by the number of heads collected in descending order bestHunters.sort((a, b) -> Integer.compare(b.headsCollected(), a.headsCollected())); + + // Return the top hunters return bestHunters.subList(0, Math.min(topHunters, bestHunters.size())); } } \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java index 9e6d7bb..2f66aca 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java @@ -55,22 +55,35 @@ public void countHeadsInRegion() { } public void playerCollectedHead(Player player, Block block, int x, int y, int z) { + String playerUUID = player.getUniqueId().toString(); Map data = yamlFileManager.getData(); - List> collectedHeads = (List>) data.get("collectedHeads"); + Map playerData = (Map) data.get(playerUUID); - // Initialize the list if it is null - if (collectedHeads == null) { - collectedHeads = new ArrayList<>(); - data.put("collectedHeads", collectedHeads); + if (playerData == null) { + playerData = new HashMap<>(); + playerData.put("collected", new ArrayList>()); + data.put(playerUUID, playerData); } - collectedHeads.add(Map.of("player", player.getUniqueId().toString(), "x", x, "y", y, "z", z)); + List> collectedHeads = (List>) playerData.get("collected"); + + boolean alreadyCollected = collectedHeads.stream().anyMatch(head -> + head.get("x") == x && head.get("y") == y && head.get("z") == z); + + if (alreadyCollected) { + player.sendMessage(plugin.config().getLangHeadAlreadyFound()); + return; + } + + collectedHeads.add(Map.of("x", x, "y", y, "z", z)); yamlFileManager.save(); + // Increment the player's head count + plugin.getHeadQuery().insertCollectedHead(player, x, y, z); + Material blockType = block.getType(); BlockData blockData = block.getBlockData(); - // Break and set the block to be replaced later int headRespawnTimer = plugin.config().getHeadRespawnTimer(); breakBlock(x, y, z); diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java b/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java index bf2827d..53e8041 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java @@ -1,5 +1,6 @@ package org.modularsoft.PlayerHeadHunt; +import lombok.Getter; import org.modularsoft.PlayerHeadHunt.commands.clearheads; import org.modularsoft.PlayerHeadHunt.commands.heads; import org.modularsoft.PlayerHeadHunt.commands.countheads; @@ -21,13 +22,15 @@ public class PlayerHeadHuntMain extends JavaPlugin { private PluginConfig config; - private Connection connection; private ConsoleCommandSender console; public PluginConfig config() { return config; } + @Getter + private HeadQuery headQuery; + @Override public void onEnable() { // Generate configuration file @@ -36,7 +39,8 @@ public void onEnable() { console = getServer().getConsoleSender(); YamlFileManager yamlFileManager = new YamlFileManager(new File(getDataFolder(), "player-data.yml")); - HeadQuery headQuery = new HeadQuery(yamlFileManager); + headQuery = new HeadQuery(yamlFileManager); + HeadChatController headChatController = new HeadChatController(this, headQuery); HeadWorldController headWorldController = new HeadWorldController(this); HeadHatController headHatController = new HeadHatController(this); @@ -57,6 +61,7 @@ public void onEnable() { Objects.requireNonNull(getCommand("clearheads")).setExecutor(new clearheads(this, headChatController, headHatController, headScoreboardController, headQuery)); Objects.requireNonNull(getCommand("countheads")).setExecutor(new countheads(this, headWorldController, headScoreboardController, headQuery)); Objects.requireNonNull(getCommand("heads")).setExecutor(new heads(this, headChatController)); + Objects.requireNonNull(getCommand("leaderboard")).setExecutor(new leaderboard(this, headChatController, headQuery)); // Register leaderboard command // Plugin Load Message console.sendMessage(ChatColor.GREEN + getDescription().getName() + " is now enabled."); diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadFindEvent.java b/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadFindEvent.java index 13272be..7b8a793 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadFindEvent.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/events/HeadFindEvent.java @@ -35,8 +35,9 @@ public HeadFindEvent(PlayerHeadHuntMain plugin, HeadWorldController headWorldCon @EventHandler public void onHeadFind(PlayerInteractEvent event) { - if (!isFindHeadEvent(event)) + if (!isFindHeadEvent(event)) { return; + } event.setCancelled(true); @@ -46,21 +47,23 @@ public void onHeadFind(PlayerInteractEvent event) { int y = block.getY(); int z = block.getZ(); + // Check if the head has already been collected if (headQuery.hasAlreadyCollectedHead(player, x, y, z)) { - headChatController.headFoundResponse(player, true, 0, x, y, z); - return; + // Send the "head already found" message and stop further processing + headChatController.headFoundResponse(player, true, headQuery.foundHeadsCount(player), x, y, z); + return; // Ensure no further processing occurs } + // Process head collection headWorldController.playerCollectedHead(player, block, x, y, z); + // Retrieve the updated count of heads found int foundHeads = headQuery.foundHeadsCount(player); - headScoreboardController.reloadScoreboard(player, foundHeads); - if (foundHeads == 1) { - headChatController.headMilestoneReachedEvent(player, false, foundHeads); - return; - } + // Update the scoreboard with the new count + headScoreboardController.reloadScoreboard(player, foundHeads); + // Handle milestones or send a success message if (milestones.containsKey(foundHeads)) { milestones.get(foundHeads).trigger(headChatController, headHatController, player, event); } else { From 10d264151154dbdbf236d8dd0c88f1ea10394f93 Mon Sep 17 00:00:00 2001 From: benrobson Date: Sat, 12 Apr 2025 07:14:48 +1000 Subject: [PATCH 3/7] Re,ove mysql and merge clearheads and count heads and fix lb. --- pom.xml | 7 -- .../modularsoft/PlayerHeadHunt/HeadQuery.java | 16 ++++- .../PlayerHeadHunt/HeadWorldController.java | 8 ++- .../PlayerHeadHunt/PlayerHeadHuntMain.java | 12 +--- .../PlayerHeadHunt/commands/clearheads.java | 49 ------------- .../PlayerHeadHunt/commands/countheads.java | 62 ----------------- .../commands/debugheadhunt.java | 68 +++++++++++++++++++ src/main/resources/plugin.yml | 14 ++-- 8 files changed, 96 insertions(+), 140 deletions(-) delete mode 100644 src/main/java/org/modularsoft/PlayerHeadHunt/commands/clearheads.java delete mode 100644 src/main/java/org/modularsoft/PlayerHeadHunt/commands/countheads.java create mode 100644 src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java diff --git a/pom.xml b/pom.xml index 4e970b9..f834f6d 100644 --- a/pom.xml +++ b/pom.xml @@ -60,13 +60,6 @@ - - - mysql - mysql-connector-java - 8.0.33 - - com.sk89q.worldedit diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java index 3860bb1..56b1c0f 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java @@ -60,7 +60,21 @@ public int foundHeadsAlreadyCount(int xCord, int yCord, int zCord) { public boolean clearHeads(Player player) { String playerUUID = player.getUniqueId().toString(); Map data = yamlFileManager.getData(); - data.remove(playerUUID); + Map playerData = (Map) data.get(playerUUID); + + if (playerData == null) { + return false; // No data for the player + } + + // Clear the contents of the headsCollected list + List> headsCollected = (List>) playerData.get("headsCollected"); + if (headsCollected != null) { + headsCollected.clear(); + } + + // Reset the headsCollectedCount to 0 + playerData.put("headsCollectedCount", 0); + yamlFileManager.save(); return true; } diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java index 2f66aca..4afbb0e 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java @@ -61,11 +61,15 @@ public void playerCollectedHead(Player player, Block block, int x, int y, int z) if (playerData == null) { playerData = new HashMap<>(); - playerData.put("collected", new ArrayList>()); + playerData.put("headsCollected", new ArrayList>()); data.put(playerUUID, playerData); } - List> collectedHeads = (List>) playerData.get("collected"); + List> collectedHeads = (List>) playerData.get("headsCollected"); + if (collectedHeads == null) { + collectedHeads = new ArrayList<>(); + playerData.put("headsCollected", collectedHeads); + } boolean alreadyCollected = collectedHeads.stream().anyMatch(head -> head.get("x") == x && head.get("y") == y && head.get("z") == z); diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java b/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java index 53e8041..450403c 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java @@ -1,14 +1,10 @@ package org.modularsoft.PlayerHeadHunt; import lombok.Getter; -import org.modularsoft.PlayerHeadHunt.commands.clearheads; -import org.modularsoft.PlayerHeadHunt.commands.heads; -import org.modularsoft.PlayerHeadHunt.commands.countheads; -import org.modularsoft.PlayerHeadHunt.commands.leaderboard; +import org.modularsoft.PlayerHeadHunt.commands.*; import org.modularsoft.PlayerHeadHunt.events.HeadFindEvent; import org.modularsoft.PlayerHeadHunt.events.HeadHatOnHead; import org.modularsoft.PlayerHeadHunt.events.HeadHunterOnJoin; -import com.mysql.cj.jdbc.MysqlDataSource; import org.bukkit.ChatColor; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.plugin.PluginManager; @@ -16,8 +12,6 @@ import org.modularsoft.PlayerHeadHunt.helpers.YamlFileManager; import java.io.File; -import java.sql.Connection; -import java.sql.SQLException; import java.util.Objects; public class PlayerHeadHuntMain extends JavaPlugin { @@ -58,10 +52,8 @@ public void onEnable() { // Command Registry Objects.requireNonNull(getCommand("heads")).setExecutor(new heads(this, headChatController)); - Objects.requireNonNull(getCommand("clearheads")).setExecutor(new clearheads(this, headChatController, headHatController, headScoreboardController, headQuery)); - Objects.requireNonNull(getCommand("countheads")).setExecutor(new countheads(this, headWorldController, headScoreboardController, headQuery)); - Objects.requireNonNull(getCommand("heads")).setExecutor(new heads(this, headChatController)); Objects.requireNonNull(getCommand("leaderboard")).setExecutor(new leaderboard(this, headChatController, headQuery)); // Register leaderboard command + Objects.requireNonNull(getCommand("debugheadhunt")).setExecutor(new debugheadhunt(this, headChatController, headHatController, headScoreboardController, headWorldController, headQuery)); // Plugin Load Message console.sendMessage(ChatColor.GREEN + getDescription().getName() + " is now enabled."); diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/clearheads.java b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/clearheads.java deleted file mode 100644 index 9bc1592..0000000 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/clearheads.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.modularsoft.PlayerHeadHunt.commands; - -import org.modularsoft.PlayerHeadHunt.*; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -public class clearheads implements CommandExecutor { - private final PlayerHeadHuntMain plugin; - private final HeadChatController headChatController; - private final HeadHatController headHatController; - private final HeadScoreboardController scoreboardController; - private final HeadQuery headQuery; - - public clearheads(PlayerHeadHuntMain plugin, - HeadChatController headChatController, - HeadHatController headHatController, - HeadScoreboardController scoreboardController, - HeadQuery headQuery) { - this.plugin = plugin; - this.headChatController = headChatController; - this.headHatController = headHatController; - this.scoreboardController = scoreboardController; - this.headQuery = headQuery; - } - - @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String s, String[] args) { - if (!(sender instanceof Player player)) { - sender.sendMessage(plugin.config().getLangNotAPlayer()); - return true; - } - - if (!sender.hasPermission("playerheadhunt.clearhead") || !sender.isOp()) { - sender.sendMessage(plugin.config().getLangInsufficientPermissions()); - return true; - } - - if (!headQuery.clearHeads(player)) // Use the HeadQuery instance to call clearHeads - return true; - - headChatController.playerClearedTheirHeadsResponse(player); - headHatController.clearHelmet(player); - scoreboardController.reloadScoreboard(player, headQuery.foundHeadsCount(player)); // Use headQuery for foundHeadsCount - return true; - } -} \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/countheads.java b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/countheads.java deleted file mode 100644 index b2890f3..0000000 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/countheads.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.modularsoft.PlayerHeadHunt.commands; - -import org.modularsoft.PlayerHeadHunt.PlayerHeadHuntMain; -import org.modularsoft.PlayerHeadHunt.HeadWorldController; -import org.modularsoft.PlayerHeadHunt.HeadQuery; -import org.modularsoft.PlayerHeadHunt.HeadScoreboardController; -import com.sk89q.worldedit.math.BlockVector3; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -public class countheads implements CommandExecutor { - private final PlayerHeadHuntMain plugin; - private final HeadWorldController headWorldController; - private final HeadScoreboardController headScoreboardController; - private final HeadQuery headQuery; - - public countheads(PlayerHeadHuntMain plugin, - HeadWorldController headWorldController, - HeadScoreboardController headScoreboardController, - HeadQuery headQuery) { - this.plugin = plugin; - this.headWorldController = headWorldController; - this.headScoreboardController = headScoreboardController; - this.headQuery = headQuery; - } - - @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String s, String[] args) { - if (!(sender instanceof Player)) { - sender.sendMessage(plugin.config().getLangNotAPlayer()); - return true; - } - - if (!sender.hasPermission("playerheadhunt.clearhead") || !sender.isOp()) { - sender.sendMessage(plugin.config().getLangInsufficientPermissions()); - return true; - } - - // Count heads in the region - headWorldController.countHeadsInRegion(); - - // Update HEAD.HEADTOTAL in the configuration - int totalHeads = plugin.config().getTotalHeads(); - plugin.getConfig().set("HEAD.HEADTOTAL", totalHeads); - plugin.saveConfig(); // Save the updated configuration to disk - - // Notify the sender - BlockVector3 upper = plugin.config().getUpperRegion(); - BlockVector3 lower = plugin.config().getLowerRegion(); - sender.sendMessage("There are " + totalHeads + " total heads in " + lower + ", " + upper + "."); - - // Update the scoreboard for all online players - for (Player otherPlayer : plugin.getServer().getOnlinePlayers()) { - int headsFound = headQuery.foundHeadsCount(otherPlayer); - headScoreboardController.reloadScoreboard(otherPlayer, headsFound); - } - return true; - } -} diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java new file mode 100644 index 0000000..dc41ab0 --- /dev/null +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java @@ -0,0 +1,68 @@ +package org.modularsoft.PlayerHeadHunt.commands; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.modularsoft.PlayerHeadHunt.*; + +public class debugheadhunt implements CommandExecutor { + private final PlayerHeadHuntMain plugin; + private final HeadChatController headChatController; + private final HeadHatController headHatController; + private final HeadScoreboardController scoreboardController; + private final HeadWorldController headWorldController; + private final HeadQuery headQuery; + + public debugheadhunt(PlayerHeadHuntMain plugin, + HeadChatController headChatController, + HeadHatController headHatController, + HeadScoreboardController scoreboardController, + HeadWorldController headWorldController, + HeadQuery headQuery) { + this.plugin = plugin; + this.headChatController = headChatController; + this.headHatController = headHatController; + this.scoreboardController = scoreboardController; + this.headWorldController = headWorldController; + this.headQuery = headQuery; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage(plugin.config().getLangNotAPlayer()); + return true; + } + + if (!sender.hasPermission("playerheadhunt.debug") || !sender.isOp()) { + sender.sendMessage(plugin.config().getLangInsufficientPermissions()); + return true; + } + + if (args.length == 0) { + sender.sendMessage("Usage: /debugheadhunt "); + return true; + } + + switch (args[0].toLowerCase()) { + case "clearheads" -> { + if (!headQuery.clearHeads(player)) { + sender.sendMessage("No heads to clear."); + return true; + } + headChatController.playerClearedTheirHeadsResponse(player); + headHatController.clearHelmet(player); + scoreboardController.reloadScoreboard(player, headQuery.foundHeadsCount(player)); + sender.sendMessage("Heads cleared successfully."); + } + case "countheads" -> { + headWorldController.countHeadsInRegion(); + sender.sendMessage("Heads counted successfully."); + } + default -> sender.sendMessage("Invalid subcommand. Use: clearheads or countheads."); + } + return true; + } +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 1ae75b5..d9482f5 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -11,15 +11,11 @@ commands: description: Grab the amount of heads you have. usage: /heads aliases: [eggs, presents] - clearheads: - description: Clear all heads from yourself. - usage: /clearheads - aliases: [cleareggs, clearpresents] - countheads: - description: Recalculates the number of heads in the world. - usage: /countheads - aliases: [counteggs, countpresents] leaderboard: description: Show the 5 best heads hunters on the Server. usage: /leaderboard - aliases: [lb] \ No newline at end of file + aliases: [lb] + debugheadhunt: + description: Debug command for developers. + usage: /debugheadhunt + aliases: [ dhh ] \ No newline at end of file From 4532eef2282fd81a996b70d523a7a687e6d03316 Mon Sep 17 00:00:00 2001 From: benrobson Date: Sat, 12 Apr 2025 12:40:53 +1000 Subject: [PATCH 4/7] Base implementation of webhooks. --- pom.xml | 27 ++++ .../commands/debugheadhunt.java | 17 ++- .../PlayerHeadHunt/helpers/WebhookUtil.java | 115 ++++++++++++++++++ src/main/resources/config.yml | 5 + 4 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java diff --git a/pom.xml b/pom.xml index f834f6d..9cb4d90 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,27 @@ 17 + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + + package + + shade + + + + + org.apache.hc + org.modularsoft.PlayerHeadHunt.shaded.org.apache.hc + + + + + + @@ -99,6 +120,12 @@ snakeyaml 2.0 + + + org.apache.httpcomponents.client5 + httpclient5 + 5.2 + diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java index dc41ab0..2e19b75 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java @@ -6,6 +6,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.modularsoft.PlayerHeadHunt.*; +import org.modularsoft.PlayerHeadHunt.helpers.WebhookUtil; public class debugheadhunt implements CommandExecutor { private final PlayerHeadHuntMain plugin; @@ -14,6 +15,7 @@ public class debugheadhunt implements CommandExecutor { private final HeadScoreboardController scoreboardController; private final HeadWorldController headWorldController; private final HeadQuery headQuery; + private final WebhookUtil webhookUtil; public debugheadhunt(PlayerHeadHuntMain plugin, HeadChatController headChatController, @@ -27,6 +29,7 @@ public debugheadhunt(PlayerHeadHuntMain plugin, this.scoreboardController = scoreboardController; this.headWorldController = headWorldController; this.headQuery = headQuery; + this.webhookUtil = new WebhookUtil(plugin); } @Override @@ -42,7 +45,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N } if (args.length == 0) { - sender.sendMessage("Usage: /debugheadhunt "); + sender.sendMessage("Usage: /debugheadhunt "); return true; } @@ -61,7 +64,17 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N headWorldController.countHeadsInRegion(); sender.sendMessage("Heads counted successfully."); } - default -> sender.sendMessage("Invalid subcommand. Use: clearheads or countheads."); + case "firewebhook" -> { + try { + String webhookUrl = plugin.getConfig().getString("DISCORD.WEBHOOKURL"); + webhookUtil.sendLeaderboardWebhook(webhookUrl, headQuery); + sender.sendMessage("Webhook fired successfully."); + } catch (Exception e) { + sender.sendMessage("Failed to fire webhook: " + e.getMessage()); + e.printStackTrace(); + } + } + default -> sender.sendMessage("Invalid subcommand. Use: clearheads, countheads, or firewebhook."); } return true; } diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java b/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java new file mode 100644 index 0000000..9e05d73 --- /dev/null +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java @@ -0,0 +1,115 @@ +package org.modularsoft.PlayerHeadHunt.helpers; + +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.plugin.java.JavaPlugin; +import org.modularsoft.PlayerHeadHunt.HeadQuery; + +import java.time.LocalTime; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class WebhookUtil { + + private final JavaPlugin plugin; + private final FileConfiguration config; + + public WebhookUtil(JavaPlugin plugin) { + this.plugin = plugin; + this.config = plugin.getConfig(); + } + + public void scheduleDailyWebhook(HeadQuery headQuery) { + if (!config.getBoolean("FEATURE.LEADERBOARDDAILYWEBHOOK")) { + plugin.getLogger().info("Daily leaderboard webhook is disabled in the config."); + return; + } + + String webhookUrl = config.getString("DISCORD.WEBHOOKURL"); + int hour = config.getInt("DISCORD.HOUR"); + int minute = config.getInt("DISCORD.MINUTE"); + + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + // Calculate initial delay + LocalTime targetTime = LocalTime.of(hour, minute); + long initialDelay = calculateInitialDelay(targetTime); + + // Schedule the task + scheduler.scheduleAtFixedRate(() -> { + try { + sendLeaderboardWebhook(webhookUrl, headQuery); + } catch (Exception e) { + e.printStackTrace(); + } + }, initialDelay, TimeUnit.DAYS.toSeconds(1), TimeUnit.SECONDS); + } + + private long calculateInitialDelay(LocalTime targetTime) { + LocalTime now = LocalTime.now(); + long delay = now.until(targetTime, TimeUnit.SECONDS.toChronoUnit()); + return delay > 0 ? delay : TimeUnit.DAYS.toSeconds(1) + delay; + } + + public void sendLeaderboardWebhook(String webhookUrl, HeadQuery headQuery) throws Exception { + // Retrieve the top 5 hunters + List topHunters = headQuery.getBestHunters(5); + + // Build the fields dynamically + StringBuilder fieldsJson = new StringBuilder(); + for (HeadQuery.HeadHunter hunter : topHunters) { + fieldsJson.append(String.format(""" + { + "name": "%s", + "value": "%d heads collected", + "inline": false + } + """, hunter.name(), hunter.headsCollected())); + fieldsJson.append(","); // Add a comma after each field + } + + // Remove the trailing comma + if (fieldsJson.length() > 0 && fieldsJson.charAt(fieldsJson.length() - 1) == ',') { + fieldsJson.setLength(fieldsJson.length() - 1); + } + + String embedJson = String.format(""" + { + "embeds": [ + { + "title": "Leaderboard Standings", + "description": "Here are the current leaderboard standings:", + "color": 3447003, + "fields": [ + %s + ], + "thumbnail": { + "url": "https://github.com/ModularSoftAU/assets/blob/master/playerheadhunt/playerheadhunt-icon-text-256.png?raw=true0" + }, + "footer": { + "text": "Developed by Modular Software" + } + } + ] + } + """, fieldsJson); + + // Log the generated JSON for debugging + plugin.getLogger().info("Generated JSON: " + embedJson); + + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpPost post = new HttpPost(webhookUrl); + post.setEntity(new StringEntity(embedJson, ContentType.APPLICATION_JSON)); + try (CloseableHttpResponse response = httpClient.execute(post)) { + plugin.getLogger().info("Response: " + response.getCode()); + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index b963488..63143bf 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -10,6 +10,11 @@ REGION: FEATURE: MILESTONEHAT: TRUE MILESTONEMESSAGE: TRUE + LEADERBOARDDAILYWEBHOOK: TRUE +DISCORD: + WEBHOOKURL: "https://discord.com/api/webhooks/000000000000000000/000000000000000000" + HOUR: 7 + MINUTE: 15 HEAD: HEADTOTAL: HEADBLOCK: PLAYER_HEAD From 38aeb9d7013a52da61a08a952f0b002552a2e97b Mon Sep 17 00:00:00 2001 From: Venomous-Viper1 Date: Sun, 13 Apr 2025 14:58:20 -0700 Subject: [PATCH 5/7] Updated Milestones and Egg Region (#44) --- src/main/resources/config.yml | 45 +++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 63143bf..8b039b3 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,12 +1,12 @@ REGION: UPPERREGION: - X: -54 + X: 300 Y: 255 # Do not change this, this expands vertically. - Z: 48 + Z: 300 LOWERREGION: - X: -33 + X: -300 Y: 0 # Do not change this, this expands vertically. - Z: 4 + Z: -300 FEATURE: MILESTONEHAT: TRUE MILESTONEMESSAGE: TRUE @@ -38,23 +38,28 @@ SOUND: MAJORCOLLECTIONMILESTONE: "UI_TOAST_CHALLENGE_COMPLETE" MILESTONES: MINOR: - - 16 - - 32 - - 64 - - 96 - - 160 - - 192 - - 224 + - 10 + - 25 + - 50 + - 75 + - 100 + - 200 + - 300 + - 400 + - 500 + - 600 MAJOR: - - 128 - - 256 + - 156 + - 314 + - 468 + - 625 # These need to match up 1 to 1 with a milestone above - LEATHERHELMET: 16 - CHAINMAILHELMET: 32 - IRONHELMET: 64 - GOLDENHELMET: 128 - DIAMONDHELMET: 192 - NETHERITEHELMET: 256 # Should equal the head count for maximum results. + LEATHERHELMET: 25 + CHAINMAILHELMET: 100 + IRONHELMET: 156 + GOLDENHELMET: 314 + DIAMONDHELMET: 468 + NETHERITEHELMET: 625 # Should equal the head count for maximum results. LANG: DATABASE: CONNECTIONERROR: "&cA database error has occurred, please contact an Administrator." @@ -84,4 +89,4 @@ LANG: FIRSTCOLOUR: "&e" SECONDCOLOUR: "&7" THIRDCOLOUR: "&6" - FORMAT: "%COLOUR%&l%RANKING%&r&f &7-&r %PLAYER% &7- %NUMBEROFHEADS%" \ No newline at end of file + FORMAT: "%COLOUR%&l%RANKING%&r&f &7-&r %PLAYER% &7- %NUMBEROFHEADS%" From e2cd604b3ac06a0297c2c532086d23c250489e12 Mon Sep 17 00:00:00 2001 From: benrobson Date: Mon, 14 Apr 2025 16:01:41 +1000 Subject: [PATCH 6/7] Testing leaderboard excemption --- PlayerHeadHunt.iml | 2 - pom.xml | 7 ++ .../modularsoft/PlayerHeadHunt/HeadQuery.java | 82 ++++++++++---- .../PlayerHeadHunt/commands/leaderboard.java | 12 ++- .../PlayerHeadHunt/helpers/WebhookUtil.java | 101 +++++++++--------- 5 files changed, 130 insertions(+), 74 deletions(-) diff --git a/PlayerHeadHunt.iml b/PlayerHeadHunt.iml index 8b7a1e1..3cf00db 100644 --- a/PlayerHeadHunt.iml +++ b/PlayerHeadHunt.iml @@ -5,8 +5,6 @@ PAPER - ADVENTURE - SPIGOT 1 diff --git a/pom.xml b/pom.xml index 9cb4d90..79a78cc 100644 --- a/pom.xml +++ b/pom.xml @@ -126,6 +126,13 @@ httpclient5 5.2 + + + net.luckperms + api + 5.4 + provided + diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java index 56b1c0f..f3e13d9 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java @@ -1,19 +1,32 @@ package org.modularsoft.PlayerHeadHunt; import lombok.Getter; +import net.luckperms.api.LuckPerms; +import net.luckperms.api.LuckPermsProvider; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; import org.modularsoft.PlayerHeadHunt.helpers.YamlFileManager; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; public class HeadQuery { private final YamlFileManager yamlFileManager; + RegisteredServiceProvider provider = Bukkit.getServicesManager().getRegistration(LuckPerms.class); + private final LuckPerms luckPerms; public HeadQuery(YamlFileManager yamlFileManager) { this.yamlFileManager = yamlFileManager; + + // Get the LuckPerms provider from the Bukkit services manager + RegisteredServiceProvider provider = Bukkit.getServicesManager().getRegistration(LuckPerms.class); + if (provider != null) { + this.luckPerms = provider.getProvider(); + } else { + throw new IllegalStateException("LuckPerms API is not available!"); + } } public record HeadHunter(@Getter String name, @Getter int headsCollected) { } @@ -150,29 +163,56 @@ public boolean addNewHunter(Player player) { return true; } - public List getBestHunters(int topHunters) { + public CompletableFuture> getBestHunters(int topHunters) { Map data = yamlFileManager.getData(); - List bestHunters = new ArrayList<>(); + List> futures = new ArrayList<>(); - data.forEach((key, value) -> { - if (value instanceof Map) { - Map playerData = (Map) value; + for (Map.Entry entry : data.entrySet()) { + String uuidStr = entry.getKey(); + Map playerData = (Map) entry.getValue(); - String username = (String) playerData.get("username"); - Object headsCollectedObj = playerData.get("headsCollected"); + if (playerData == null) continue; - // Validate that headsCollected is a list - if (username != null && headsCollectedObj instanceof List) { - int headsCollectedCount = ((List) headsCollectedObj).size(); - bestHunters.add(new HeadHunter(username, headsCollectedCount)); - } + UUID uuid; + try { + uuid = UUID.fromString(uuidStr); + } catch (IllegalArgumentException e) { + continue; } - }); - // Sort hunters by the number of heads collected in descending order - bestHunters.sort((a, b) -> Integer.compare(b.headsCollected(), a.headsCollected())); + String username = (String) playerData.get("username"); + Object headsCollectedObj = playerData.get("headsCollected"); + + if (username == null || !(headsCollectedObj instanceof List)) continue; + + List headsCollected = (List) headsCollectedObj; + Player onlinePlayer = Bukkit.getPlayer(uuid); + + if (onlinePlayer != null) { + if (!onlinePlayer.hasPermission("playerheadhunt.leaderboard.exclude")) { + futures.add(CompletableFuture.completedFuture(new HeadHunter(username, headsCollected.size()))); + } + } else { + CompletableFuture future = luckPerms.getUserManager().loadUser(uuid) + .thenApply(user -> { + boolean isExcluded = user.getCachedData() + .getPermissionData() + .checkPermission("playerheadhunt.leaderboard.exclude") + .asBoolean(); + + return isExcluded ? null : new HeadHunter(username, headsCollected.size()); + }); + + futures.add(future); + } + } - // Return the top hunters - return bestHunters.subList(0, Math.min(topHunters, bestHunters.size())); + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> futures.stream() + .map(CompletableFuture::join) + .filter(Objects::nonNull) + .sorted((a, b) -> Integer.compare(b.headsCollected(), a.headsCollected())) + .limit(topHunters) + .collect(Collectors.toList())); } } \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java index 8b12021..a095846 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java @@ -1,5 +1,6 @@ package org.modularsoft.PlayerHeadHunt.commands; +import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -29,9 +30,14 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N return true; } - // Use the HeadQuery instance to call getBestHunters - List bestHunters = headQuery.getBestHunters(5); - headChatController.showLeaderBoardResponse(player, bestHunters); + // Async leaderboard fetching to avoid blocking main thread + headQuery.getBestHunters(5).thenAccept(bestHunters -> { + // Must run sync to safely interact with Bukkit API + Bukkit.getScheduler().runTask(plugin, () -> { + headChatController.showLeaderBoardResponse(player, bestHunters); + }); + }); + return true; } } \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java b/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java index 9e05d73..88c1112 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java @@ -6,6 +6,7 @@ import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.io.entity.StringEntity; +import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.java.JavaPlugin; import org.modularsoft.PlayerHeadHunt.HeadQuery; @@ -58,58 +59,62 @@ private long calculateInitialDelay(LocalTime targetTime) { return delay > 0 ? delay : TimeUnit.DAYS.toSeconds(1) + delay; } - public void sendLeaderboardWebhook(String webhookUrl, HeadQuery headQuery) throws Exception { - // Retrieve the top 5 hunters - List topHunters = headQuery.getBestHunters(5); - - // Build the fields dynamically - StringBuilder fieldsJson = new StringBuilder(); - for (HeadQuery.HeadHunter hunter : topHunters) { - fieldsJson.append(String.format(""" - { - "name": "%s", - "value": "%d heads collected", - "inline": false - } - """, hunter.name(), hunter.headsCollected())); - fieldsJson.append(","); // Add a comma after each field - } + public void sendLeaderboardWebhook(String webhookUrl, HeadQuery headQuery) { + headQuery.getBestHunters(5).thenAccept((List topHunters) -> { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + try { + // Build the fields dynamically + StringBuilder fieldsJson = new StringBuilder(); + for (HeadQuery.HeadHunter hunter : topHunters) { + fieldsJson.append(String.format(""" + { + "name": "%s", + "value": "%d heads collected", + "inline": false + }, + """, hunter.name(), hunter.headsCollected())); + } - // Remove the trailing comma - if (fieldsJson.length() > 0 && fieldsJson.charAt(fieldsJson.length() - 1) == ',') { - fieldsJson.setLength(fieldsJson.length() - 1); - } + // Remove trailing comma + if (fieldsJson.length() > 0 && fieldsJson.charAt(fieldsJson.length() - 2) == ',') { + fieldsJson.setLength(fieldsJson.length() - 2); + } - String embedJson = String.format(""" - { - "embeds": [ - { - "title": "Leaderboard Standings", - "description": "Here are the current leaderboard standings:", - "color": 3447003, - "fields": [ - %s - ], - "thumbnail": { - "url": "https://github.com/ModularSoftAU/assets/blob/master/playerheadhunt/playerheadhunt-icon-text-256.png?raw=true0" - }, - "footer": { - "text": "Developed by Modular Software" + String embedJson = String.format(""" + { + "embeds": [ + { + "title": "Leaderboard Standings", + "description": "Here are the current leaderboard standings:", + "color": 3447003, + "fields": [ + %s + ], + "thumbnail": { + "url": "https://github.com/ModularSoftAU/assets/blob/master/playerheadhunt/playerheadhunt-icon-text-256.png?raw=true" + }, + "footer": { + "text": "Developed by Modular Software" + } + } + ] } - } - ] - } - """, fieldsJson); + """, fieldsJson); - // Log the generated JSON for debugging - plugin.getLogger().info("Generated JSON: " + embedJson); + plugin.getLogger().info("Generated JSON: " + embedJson); - try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost post = new HttpPost(webhookUrl); - post.setEntity(new StringEntity(embedJson, ContentType.APPLICATION_JSON)); - try (CloseableHttpResponse response = httpClient.execute(post)) { - plugin.getLogger().info("Response: " + response.getCode()); - } - } + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpPost post = new HttpPost(webhookUrl); + post.setEntity(new StringEntity(embedJson, ContentType.APPLICATION_JSON)); + try (CloseableHttpResponse response = httpClient.execute(post)) { + plugin.getLogger().info("Webhook response: " + response.getCode()); + } + } + } catch (Exception e) { + plugin.getLogger().severe("Failed to send webhook: " + e.getMessage()); + e.printStackTrace(); + } + }); + }); } } \ No newline at end of file From eceef1c8991564e9f96657bcf9031f3f7f388435 Mon Sep 17 00:00:00 2001 From: shadowolf Date: Tue, 15 Apr 2025 16:13:22 +1000 Subject: [PATCH 7/7] Add autocomplete in DHH command and fix hunter output. --- .../modularsoft/PlayerHeadHunt/HeadQuery.java | 87 ++++++++++++------- .../PlayerHeadHunt/PlayerHeadHuntMain.java | 7 +- .../commands/debugheadhunt.java | 39 ++++++--- .../PlayerHeadHunt/commands/leaderboard.java | 33 ++++--- .../PlayerHeadHunt/helpers/WebhookUtil.java | 86 ++++++++++-------- src/main/resources/plugin.yml | 9 +- 6 files changed, 162 insertions(+), 99 deletions(-) diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java index f3e13d9..b2a77cc 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java @@ -3,6 +3,7 @@ import lombok.Getter; import net.luckperms.api.LuckPerms; import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.util.Tristate; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.RegisteredServiceProvider; @@ -163,54 +164,74 @@ public boolean addNewHunter(Player player) { return true; } - public CompletableFuture> getBestHunters(int topHunters) { - Map data = yamlFileManager.getData(); - List> futures = new ArrayList<>(); + private boolean isPlayerExcluded(UUID uuid, String username) { + return luckPerms.getUserManager().loadUser(uuid) + .thenApply(user -> { + if (user == null) { + Bukkit.getLogger().warning("LuckPerms failed to load user for UUID: " + uuid); + return false; // Include the player if user data cannot be loaded + } - for (Map.Entry entry : data.entrySet()) { - String uuidStr = entry.getKey(); - Map playerData = (Map) entry.getValue(); + Tristate permissionResult = user.getCachedData() + .getPermissionData() + .checkPermission("playerheadhunt.leaderboard.exclude"); - if (playerData == null) continue; + // Only exclude if the permission is explicitly TRUE + boolean isExcluded = permissionResult == Tristate.TRUE; - UUID uuid; - try { - uuid = UUID.fromString(uuidStr); - } catch (IllegalArgumentException e) { - continue; - } + Bukkit.getLogger().info("Player " + username + " exclusion status: " + isExcluded); + return isExcluded; + }).join(); + } + + private Optional processPlayerData(String uuidStr, Map playerData) { + UUID uuid; + try { + uuid = UUID.fromString(uuidStr); + } catch (IllegalArgumentException e) { + Bukkit.getLogger().warning("Invalid UUID string: " + uuidStr); + return Optional.empty(); + } + + String username = (String) playerData.get("username"); + Object headsCollectedObj = playerData.get("headsCollected"); - String username = (String) playerData.get("username"); - Object headsCollectedObj = playerData.get("headsCollected"); + if (username == null || !(headsCollectedObj instanceof List headsCollected)) { + Bukkit.getLogger().warning("Invalid data for user " + uuidStr + ": username=" + username + ", headsCollected=" + headsCollectedObj); + return Optional.empty(); + } - if (username == null || !(headsCollectedObj instanceof List)) continue; + Bukkit.getLogger().info("Processing player: " + username + ", Heads Collected: " + headsCollected.size()); - List headsCollected = (List) headsCollectedObj; - Player onlinePlayer = Bukkit.getPlayer(uuid); + if (isPlayerExcluded(uuid, username)) { + return Optional.empty(); + } - if (onlinePlayer != null) { - if (!onlinePlayer.hasPermission("playerheadhunt.leaderboard.exclude")) { - futures.add(CompletableFuture.completedFuture(new HeadHunter(username, headsCollected.size()))); - } - } else { - CompletableFuture future = luckPerms.getUserManager().loadUser(uuid) - .thenApply(user -> { - boolean isExcluded = user.getCachedData() - .getPermissionData() - .checkPermission("playerheadhunt.leaderboard.exclude") - .asBoolean(); + return Optional.of(new HeadHunter(username, headsCollected.size())); + } - return isExcluded ? null : new HeadHunter(username, headsCollected.size()); - }); + public CompletableFuture> getBestHunters(int topHunters) { + Map data = yamlFileManager.getData(); + List>> futures = new ArrayList<>(); - futures.add(future); + for (Map.Entry entry : data.entrySet()) { + String uuidStr = entry.getKey(); + Map playerData = (Map) entry.getValue(); + + if (playerData == null) { + Bukkit.getLogger().warning("Missing playerData for UUID: " + uuidStr); + continue; } + + CompletableFuture> future = CompletableFuture.supplyAsync(() -> processPlayerData(uuidStr, playerData)); + futures.add(future); } return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenApply(v -> futures.stream() .map(CompletableFuture::join) - .filter(Objects::nonNull) + .filter(Optional::isPresent) + .map(Optional::get) .sorted((a, b) -> Integer.compare(b.headsCollected(), a.headsCollected())) .limit(topHunters) .collect(Collectors.toList())); diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java b/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java index 450403c..1096f6e 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java @@ -53,7 +53,12 @@ public void onEnable() { // Command Registry Objects.requireNonNull(getCommand("heads")).setExecutor(new heads(this, headChatController)); Objects.requireNonNull(getCommand("leaderboard")).setExecutor(new leaderboard(this, headChatController, headQuery)); // Register leaderboard command - Objects.requireNonNull(getCommand("debugheadhunt")).setExecutor(new debugheadhunt(this, headChatController, headHatController, headScoreboardController, headWorldController, headQuery)); + Objects.requireNonNull(getCommand("debugheadhunt")).setExecutor( + new debugheadhunt(this, headChatController, headHatController, headScoreboardController, headWorldController, headQuery) + ); + Objects.requireNonNull(getCommand("debugheadhunt")).setTabCompleter( + new debugheadhunt(this, headChatController, headHatController, headScoreboardController, headWorldController, headQuery) + ); // Plugin Load Message console.sendMessage(ChatColor.GREEN + getDescription().getName() + " is now enabled."); diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java index 2e19b75..0f8606b 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java @@ -3,12 +3,18 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.modularsoft.PlayerHeadHunt.*; import org.modularsoft.PlayerHeadHunt.helpers.WebhookUtil; -public class debugheadhunt implements CommandExecutor { +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class debugheadhunt implements CommandExecutor, TabCompleter { private final PlayerHeadHuntMain plugin; private final HeadChatController headChatController; private final HeadHatController headHatController; @@ -34,11 +40,6 @@ public debugheadhunt(PlayerHeadHuntMain plugin, @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) { - if (!(sender instanceof Player player)) { - sender.sendMessage(plugin.config().getLangNotAPlayer()); - return true; - } - if (!sender.hasPermission("playerheadhunt.debug") || !sender.isOp()) { sender.sendMessage(plugin.config().getLangInsufficientPermissions()); return true; @@ -51,14 +52,18 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N switch (args[0].toLowerCase()) { case "clearheads" -> { - if (!headQuery.clearHeads(player)) { - sender.sendMessage("No heads to clear."); - return true; + if (sender instanceof Player player) { + if (!headQuery.clearHeads(player)) { + sender.sendMessage("No heads to clear."); + return true; + } + headChatController.playerClearedTheirHeadsResponse(player); + headHatController.clearHelmet(player); + scoreboardController.reloadScoreboard(player, headQuery.foundHeadsCount(player)); + sender.sendMessage("Heads cleared successfully."); + } else { + sender.sendMessage("The 'clearheads' command can only be executed by a player."); } - headChatController.playerClearedTheirHeadsResponse(player); - headHatController.clearHelmet(player); - scoreboardController.reloadScoreboard(player, headQuery.foundHeadsCount(player)); - sender.sendMessage("Heads cleared successfully."); } case "countheads" -> { headWorldController.countHeadsInRegion(); @@ -78,4 +83,12 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N } return true; } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { + if (args.length == 1) { + return Arrays.asList("clearheads", "countheads", "firewebhook"); + } + return Collections.emptyList(); + } } \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java index a095846..93bb5d8 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/leaderboard.java @@ -15,29 +15,34 @@ public class leaderboard implements CommandExecutor { private final PlayerHeadHuntMain plugin; private final HeadChatController headChatController; - private final HeadQuery headQuery; // Add HeadQuery instance + private final HeadQuery headQuery; public leaderboard(PlayerHeadHuntMain plugin, HeadChatController headChatController, HeadQuery headQuery) { this.plugin = plugin; this.headChatController = headChatController; - this.headQuery = headQuery; // Initialize HeadQuery + this.headQuery = headQuery; } @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String s, String[] args) { - if (!(sender instanceof Player player)) { - sender.sendMessage(plugin.config().getLangNotAPlayer()); - return true; - } - - // Async leaderboard fetching to avoid blocking main thread - headQuery.getBestHunters(5).thenAccept(bestHunters -> { - // Must run sync to safely interact with Bukkit API - Bukkit.getScheduler().runTask(plugin, () -> { - headChatController.showLeaderBoardResponse(player, bestHunters); + if (sender instanceof Player player) { + // Handle the command for players + headQuery.getBestHunters(5).thenAccept(bestHunters -> { + // Must run sync to safely interact with Bukkit API + Bukkit.getScheduler().runTask(plugin, () -> { + headChatController.showLeaderBoardResponse(player, bestHunters); + }); }); - }); - + } else { + // Handle the command for the console + headQuery.getBestHunters(5).thenAccept(bestHunters -> { + // Log the leaderboard to the console + plugin.getServer().getConsoleSender().sendMessage("Top 5 Hunters:"); + for (int i = 0; i < bestHunters.size(); i++) { + plugin.getServer().getConsoleSender().sendMessage((i + 1) + ". " + bestHunters.get(i)); + } + }); + } return true; } } \ No newline at end of file diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java b/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java index 88c1112..38db2b0 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java @@ -12,10 +12,9 @@ import org.modularsoft.PlayerHeadHunt.HeadQuery; import java.time.LocalTime; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; public class WebhookUtil { @@ -39,15 +38,14 @@ public void scheduleDailyWebhook(HeadQuery headQuery) { ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - // Calculate initial delay LocalTime targetTime = LocalTime.of(hour, minute); long initialDelay = calculateInitialDelay(targetTime); - // Schedule the task scheduler.scheduleAtFixedRate(() -> { try { sendLeaderboardWebhook(webhookUrl, headQuery); } catch (Exception e) { + plugin.getLogger().severe("Exception in scheduled webhook task: " + e.getMessage()); e.printStackTrace(); } }, initialDelay, TimeUnit.DAYS.toSeconds(1), TimeUnit.SECONDS); @@ -63,43 +61,59 @@ public void sendLeaderboardWebhook(String webhookUrl, HeadQuery headQuery) { headQuery.getBestHunters(5).thenAccept((List topHunters) -> { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { try { - // Build the fields dynamically - StringBuilder fieldsJson = new StringBuilder(); + if (topHunters == null || topHunters.isEmpty()) { + plugin.getLogger().warning("No top hunters found. Skipping webhook."); + return; + } + + plugin.getLogger().info("Top hunters size: " + topHunters.size()); + + List fieldsList = new ArrayList<>(); for (HeadQuery.HeadHunter hunter : topHunters) { - fieldsJson.append(String.format(""" - { - "name": "%s", - "value": "%d heads collected", - "inline": false - }, - """, hunter.name(), hunter.headsCollected())); + if (hunter == null) continue; + + String name = hunter.name(); + int headsCollected = hunter.headsCollected(); + + plugin.getLogger().info("Hunter: " + name + ", Heads Collected: " + headsCollected); + + if (name != null && !name.isEmpty()) { + fieldsList.add(String.format(""" + { + "name": "%s", + "value": "%d heads collected", + "inline": false + }""", name, headsCollected)); + } } - // Remove trailing comma - if (fieldsJson.length() > 0 && fieldsJson.charAt(fieldsJson.length() - 2) == ',') { - fieldsJson.setLength(fieldsJson.length() - 2); + if (fieldsList.isEmpty()) { + plugin.getLogger().warning("Top hunters list was populated, but all entries were invalid or excluded."); + return; } + String fieldsJson = String.join(",", fieldsList); + String embedJson = String.format(""" - { - "embeds": [ - { - "title": "Leaderboard Standings", - "description": "Here are the current leaderboard standings:", - "color": 3447003, - "fields": [ - %s - ], - "thumbnail": { - "url": "https://github.com/ModularSoftAU/assets/blob/master/playerheadhunt/playerheadhunt-icon-text-256.png?raw=true" - }, - "footer": { - "text": "Developed by Modular Software" + { + "embeds": [ + { + "title": "Leaderboard Standings", + "description": "Here are the current leaderboard standings:", + "color": 3447003, + "fields": [ + %s + ], + "thumbnail": { + "url": "https://github.com/ModularSoftAU/assets/blob/master/playerheadhunt/playerheadhunt-icon-text-256.png?raw=true" + }, + "footer": { + "text": "Developed by Modular Software" + } } - } - ] - } - """, fieldsJson); + ] + } + """, fieldsJson); plugin.getLogger().info("Generated JSON: " + embedJson); @@ -117,4 +131,4 @@ public void sendLeaderboardWebhook(String webhookUrl, HeadQuery headQuery) { }); }); } -} \ No newline at end of file +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index d9482f5..a3dcb57 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -17,5 +17,10 @@ commands: aliases: [lb] debugheadhunt: description: Debug command for developers. - usage: /debugheadhunt - aliases: [ dhh ] \ No newline at end of file + usage: /debugheadhunt + aliases: [ dhh ] + +permissions: + playerheadhunt.leaderboard.exempt: + description: Allows the player to be exempt from the leaderboard. + default: false \ No newline at end of file