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 be2b865..79a78cc 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 + + + + + + @@ -60,13 +81,6 @@ - - - mysql - mysql-connector-java - 8.0.33 - - com.sk89q.worldedit @@ -100,6 +114,25 @@ 1.18.30 provided + + + org.yaml + snakeyaml + 2.0 + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.2 + + + + net.luckperms + api + 5.4 + provided + diff --git a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadChatController.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadChatController.java index a545a0f..d1320e1 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,8 @@ 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; + // 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); } @@ -100,9 +101,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..b2a77cc 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java @@ -1,218 +1,239 @@ package org.modularsoft.PlayerHeadHunt; 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; +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.*; +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) { } - /** - * @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(); + public int foundHeadsCount(Player player) { + String playerUUID = player.getUniqueId().toString(); + Map data = yamlFileManager.getData(); + Map playerData = (Map) data.get(playerUUID); - 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(); - - if (results.next()) return results.getInt("heads"); - } catch (SQLException e) { - e.printStackTrace(); - player.sendMessage(plugin.config().getLangDatabaseConnectionError()); + 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 0; + + return headsCollected.size(); // Return the count of collected heads } - /** - * @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(); - - if (results.next()) return results.getInt("total_heads"); - } catch (SQLException e) { - e.printStackTrace(); + public int foundHeadsAlreadyCount(int xCord, int yCord, int zCord) { + Map data = yamlFileManager.getData(); + 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 } - return 0; + + List heads = (List) headsObject; + + // Filter and count matching heads + return (int) heads.stream() + .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(); } - /** - * 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()); + public boolean clearHeads(Player player) { + String playerUUID = player.getUniqueId().toString(); + Map data = yamlFileManager.getData(); + 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(); } - return false; + // Reset the headsCollectedCount to 0 + playerData.put("headsCollectedCount", 0); + + yamlFileManager.save(); + return true; } - /** - * 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 hasAlreadyCollectedHead(Player player, int x, int y, int z) { String playerUUID = player.getUniqueId().toString(); + Map data = yamlFileManager.getData(); + Map playerData = (Map) data.get(playerUUID); - 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()); + if (playerData == null) { + return false; // Player has no data, so they haven't collected any heads } - return false; + + List> headsCollected = (List>) playerData.get("headsCollected"); + if (headsCollected == null) { + return false; // No heads collected yet + } + + // 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); } - /** - * 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 void insertCollectedHead(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) { + playerData = new HashMap<>(); + playerData.put("headsCollected", new ArrayList>()); + playerData.put("headsCollectedCount", 0); + data.put(playerUUID, playerData); + } - 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(); - - 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()); + 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(); } - /** - * @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(); + Map data = yamlFileManager.getData(); - 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()); + if (data.containsKey(playerUUID)) { + return false; } - return false; + + // 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; } - /** - * @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) { - List bestHunters = 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 + } + + Tristate permissionResult = user.getCachedData() + .getPermissionData() + .checkPermission("playerheadhunt.leaderboard.exclude"); + // Only exclude if the permission is explicitly TRUE + boolean isExcluded = permissionResult == Tristate.TRUE; + + Bukkit.getLogger().info("Player " + username + " exclusion status: " + isExcluded); + return isExcluded; + }).join(); + } + + private Optional processPlayerData(String uuidStr, Map playerData) { + UUID uuid; 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)); + 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"); + + if (username == null || !(headsCollectedObj instanceof List headsCollected)) { + Bukkit.getLogger().warning("Invalid data for user " + uuidStr + ": username=" + username + ", headsCollected=" + headsCollectedObj); + return Optional.empty(); + } + + Bukkit.getLogger().info("Processing player: " + username + ", Heads Collected: " + headsCollected.size()); + + if (isPlayerExcluded(uuid, username)) { + return Optional.empty(); + } + + return Optional.of(new HeadHunter(username, headsCollected.size())); + } + + public CompletableFuture> getBestHunters(int topHunters) { + Map data = yamlFileManager.getData(); + List>> futures = new ArrayList<>(); + + 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; } - } catch (SQLException e) { - e.printStackTrace(); - player.sendMessage(plugin.config().getLangDatabaseConnectionError()); + + CompletableFuture> future = CompletableFuture.supplyAsync(() -> processPlayerData(uuidStr, playerData)); + futures.add(future); } - return bestHunters; + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> futures.stream() + .map(CompletableFuture::join) + .filter(Optional::isPresent) + .map(Optional::get) + .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/HeadWorldController.java b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadWorldController.java index ac130bd..4afbb0e 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,21 +45,49 @@ 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); + String playerUUID = player.getUniqueId().toString(); + Map data = yamlFileManager.getData(); + Map playerData = (Map) data.get(playerUUID); + + if (playerData == null) { + playerData = new HashMap<>(); + playerData.put("headsCollected", new ArrayList>()); + data.put(playerUUID, playerData); + } + + 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); + + 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); @@ -77,7 +97,6 @@ public void run() { replaceHeadBlock(blockType, blockData, x, y, z); } }.runTaskLater(plugin, headRespawnTimer); - } private void breakBlock(int x, int y, int z) { @@ -105,4 +124,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..1096f6e 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java @@ -1,61 +1,64 @@ package org.modularsoft.PlayerHeadHunt; -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 lombok.Getter; +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; import org.bukkit.plugin.java.JavaPlugin; +import org.modularsoft.PlayerHeadHunt.helpers.YamlFileManager; -import java.sql.Connection; -import java.sql.SQLException; +import java.io.File; import java.util.Objects; 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 - // 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 = 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("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")).setTabCompleter( + new debugheadhunt(this, headChatController, headHatController, headScoreboardController, headWorldController, headQuery) + ); // Plugin Load Message console.sendMessage(ChatColor.GREEN + getDescription().getName() + " is now enabled."); @@ -69,33 +72,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 deleted file mode 100644 index 1770cbf..0000000 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/clearheads.java +++ /dev/null @@ -1,44 +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; - - public clearheads(PlayerHeadHuntMain plugin, HeadChatController headChatController, - HeadHatController headHatController, HeadScoreboardController scoreboardController) { - this.plugin = plugin; - this.headChatController = headChatController; - this.headHatController = headHatController; - this.scoreboardController = scoreboardController; - } - - @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(plugin, player)) - return true; - - headChatController.playerClearedTheirHeadsResponse(player); - headHatController.clearHelmet(player); - scoreboardController.reloadScoreboard(player, HeadQuery.foundHeadsCount(plugin, player)); - return true; - } -} 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 1bb92c9..0000000 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/commands/countheads.java +++ /dev/null @@ -1,49 +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; - - public countheads(PlayerHeadHuntMain plugin, HeadWorldController headWorldController, HeadScoreboardController headScoreboardController) { - this.plugin = plugin; - this.headWorldController = headWorldController; - this.headScoreboardController = headScoreboardController; - } - - @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; - } - - headWorldController.countHeadsInRegion(); - BlockVector3 upper = plugin.config().getUpperRegion(); - BlockVector3 lower = plugin.config().getLowerRegion(); - sender.sendMessage("There are " + plugin.config().getTotalHeads() + - " total heads in " + lower + ", " + upper + "."); - - for (Player otherPlayer : plugin.getServer().getOnlinePlayers()) { - int headsFound = HeadQuery.foundHeadsCount(plugin, 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..0f8606b --- /dev/null +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/commands/debugheadhunt.java @@ -0,0 +1,94 @@ +package org.modularsoft.PlayerHeadHunt.commands; + +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; + +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; + private final HeadScoreboardController scoreboardController; + private final HeadWorldController headWorldController; + private final HeadQuery headQuery; + private final WebhookUtil webhookUtil; + + 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; + this.webhookUtil = new WebhookUtil(plugin); + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) { + 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 (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."); + } + } + case "countheads" -> { + headWorldController.countHeadsInRegion(); + sender.sendMessage("Heads counted successfully."); + } + 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; + } + + @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 c8a582c..93bb5d8 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; @@ -14,21 +15,34 @@ public class leaderboard implements CommandExecutor { private final PlayerHeadHuntMain plugin; private final HeadChatController headChatController; + private final HeadQuery headQuery; - public leaderboard(PlayerHeadHuntMain plugin, HeadChatController headChatController) { + public leaderboard(PlayerHeadHuntMain plugin, HeadChatController headChatController, HeadQuery headQuery) { this.plugin = plugin; this.headChatController = headChatController; + 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 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)); + } + }); } - - List bestHunters = HeadQuery.getBestHunters(plugin, player, 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..7b8a793 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,46 +19,51 @@ 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(); } @EventHandler public void onHeadFind(PlayerInteractEvent event) { - if (!isFindHeadEvent(event)) + if (!isFindHeadEvent(event)) { return; + } event.setCancelled(true); 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)) { - headChatController.headFoundResponse(player, true, 0, x, y, z); - return; + + // Check if the head has already been collected + if (headQuery.hasAlreadyCollectedHead(player, x, y, z)) { + // 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); - int foundHeads = HeadQuery.foundHeadsCount(plugin, player); - headScoreboardController.reloadScoreboard(player, foundHeads); + // Retrieve the updated count of heads found + int foundHeads = headQuery.foundHeadsCount(player); - if (foundHeads == 1) { - headChatController.headMilestoneReachedEvent(player, false, foundHeads); - return; - } + // Update the scoreboard with the new count + headScoreboardController.reloadScoreboard(player, foundHeads); - // 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. + // Handle milestones or send a success message if (milestones.containsKey(foundHeads)) { milestones.get(foundHeads).trigger(headChatController, headHatController, player, event); } else { @@ -68,26 +72,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/WebhookUtil.java b/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java new file mode 100644 index 0000000..38db2b0 --- /dev/null +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/helpers/WebhookUtil.java @@ -0,0 +1,134 @@ +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.Bukkit; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.plugin.java.JavaPlugin; +import org.modularsoft.PlayerHeadHunt.HeadQuery; + +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +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); + + LocalTime targetTime = LocalTime.of(hour, minute); + long initialDelay = calculateInitialDelay(targetTime); + + 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); + } + + 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) { + headQuery.getBestHunters(5).thenAccept((List topHunters) -> { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + try { + 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) { + 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)); + } + } + + 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" + } + } + ] + } + """, fieldsJson); + + 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("Webhook response: " + response.getCode()); + } + } + } catch (Exception e) { + plugin.getLogger().severe("Failed to send webhook: " + e.getMessage()); + e.printStackTrace(); + } + }); + }); + } +} 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..8b039b3 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,21 +1,20 @@ -DATABASE: - HOST: "127.0.0.1" - PORT: 3306 - DATABASE: "PlayerHeadHunt" - USERNAME: "root" - PASSWORD: "root" 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 + LEADERBOARDDAILYWEBHOOK: TRUE +DISCORD: + WEBHOOKURL: "https://discord.com/api/webhooks/000000000000000000/000000000000000000" + HOUR: 7 + MINUTE: 15 HEAD: HEADTOTAL: HEADBLOCK: PLAYER_HEAD @@ -39,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." @@ -85,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%" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 1ae75b5..a3dcb57 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -11,15 +11,16 @@ 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 ] + +permissions: + playerheadhunt.leaderboard.exempt: + description: Allows the player to be exempt from the leaderboard. + default: false \ No newline at end of file