From 47c92c9a1e61757259fedae68a952362e31ab0ab Mon Sep 17 00:00:00 2001 From: Ben Robson Date: Mon, 31 Mar 2025 21:46:06 +1100 Subject: [PATCH 1/3] Update POM for latest version. --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index ad06e4f..be2b865 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ com.sk89q.worldedit worldedit-bukkit - 7.3.0 + 7.3.10 provided @@ -84,7 +84,7 @@ com.sk89q.worldedit worldedit-core - 7.3.0 + 7.3.10 provided @@ -97,7 +97,7 @@ org.projectlombok lombok - 1.18.22 + 1.18.30 provided From 75c51ccf621902a5c5b2c47682aaf518e412ae8a Mon Sep 17 00:00:00 2001 From: Ben Robson Date: Mon, 14 Apr 2025 08:00:46 +1000 Subject: [PATCH 2/3] Removal of MySQL and refactor to YML-based implementation (#46) * Refactor to use YML and remove SQL. Base implementation needs additional fixes. * Complete refactor with some tests, still WIP * Re,ove mysql and merge clearheads and count heads and fix lb. --- pom.xml | 13 +- .../PlayerHeadHunt/HeadChatController.java | 23 +- .../modularsoft/PlayerHeadHunt/HeadQuery.java | 314 ++++++++---------- .../PlayerHeadHunt/HeadWorldController.java | 61 ++-- .../PlayerHeadHunt/PlayerHeadHuntMain.java | 63 +--- .../PlayerHeadHunt/PluginConfig.java | 12 - .../PlayerHeadHunt/commands/clearheads.java | 44 --- .../PlayerHeadHunt/commands/countheads.java | 49 --- .../commands/debugheadhunt.java | 68 ++++ .../PlayerHeadHunt/commands/leaderboard.java | 9 +- .../PlayerHeadHunt/events/HeadFindEvent.java | 67 ++-- .../events/HeadHunterOnJoin.java | 16 +- .../helpers/YamlFileManager.java | 78 +++++ src/main/resources/config.yml | 6 - src/main/resources/plugin.yml | 14 +- 15 files changed, 412 insertions(+), 425 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 create mode 100644 src/main/java/org/modularsoft/PlayerHeadHunt/helpers/YamlFileManager.java diff --git a/pom.xml b/pom.xml index be2b865..f834f6d 100644 --- a/pom.xml +++ b/pom.xml @@ -60,13 +60,6 @@ - - - mysql - mysql-connector-java - 8.0.33 - - com.sk89q.worldedit @@ -100,6 +93,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..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..56b1c0f 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/HeadQuery.java @@ -2,217 +2,177 @@ 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.HashMap; import java.util.List; +import java.util.Map; public class HeadQuery { + private final YamlFileManager yamlFileManager; + + public HeadQuery(YamlFileManager yamlFileManager) { + this.yamlFileManager = yamlFileManager; + } + 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(); - - if (results.next()) return results.getInt("heads"); - } catch (SQLException e) { - e.printStackTrace(); - player.sendMessage(plugin.config().getLangDatabaseConnectionError()); + public int foundHeadsCount(Player player) { + String playerUUID = player.getUniqueId().toString(); + Map data = yamlFileManager.getData(); + Map playerData = (Map) data.get(playerUUID); + + 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 } - return false; + // 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; } - /** - * 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) { + 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; + + 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)); + } } - } catch (SQLException e) { - e.printStackTrace(); - player.sendMessage(plugin.config().getLangDatabaseConnectionError()); - } - return bestHunters; + }); + + // 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 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..450403c 100644 --- a/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java +++ b/src/main/java/org/modularsoft/PlayerHeadHunt/PlayerHeadHuntMain.java @@ -1,61 +1,59 @@ 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)); // Plugin Load Message console.sendMessage(ChatColor.GREEN + getDescription().getName() + " is now enabled."); @@ -69,33 +67,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..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/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..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/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 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 19a6b063b3a501d7a6feb8a360c0c6fe61530d8d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 23:30:27 +0000 Subject: [PATCH 3/3] Create a detailed README.md with a user guide --- README.md | 162 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 130 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 870f6de..0279600 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,140 @@ -![img](https://github.com/ModularSoftAU/assets/blob/master/playerheadhunt/playerheadhunt-icon-text-256.png?raw=true) +# PlayerHeadHunt + +## About + +PlayerHeadHunt is a Minecraft minigame plugin that allows server administrators to set up a head-hunting game. Players can search for hidden heads within a defined region and compete to find them all. The plugin is highly configurable, allowing for custom messages, sounds, and rewards. + +## Features + +* **Customizable Hunt Region:** Define a specific area for the head hunt using WorldEdit. +* **Configurable Heads:** Use any block as a "head," not just player heads. Customize the skins of player heads. +* **Milestone Rewards:** Reward players with items (like helmets) and broadcast messages when they reach certain milestones. +* **Leaderboard:** Display the top head hunters on the server. +* **Customizable Messages and Sounds:** Configure all user-facing messages and sounds to match your server's theme. +* **Database Support:** Player data is stored in a `player-data.yml` file. + +## Commands + +| Command | Description | Usage | Aliases | +| ------------------- | ------------------------------------------------- | ----------------------------------------- | --------------- | +| `/heads` | Shows the number of heads you have found. | `/heads` | `eggs`, `presents` | +| `/leaderboard` | Shows the top 5 head hunters on the server. | `/leaderboard` | `lb` | +| `/debugheadhunt` | Debug command for developers. | `/debugheadhunt ` | `dhh` | ## Installation -* Clone this repo. -* Configure your config. -* Run the dbinit. -## Requirements -* MySQL Database. +1. Install [WorldEdit](https://dev.bukkit.org/projects/worldedit). +2. Download the latest version of PlayerHeadHunt. +3. Place the `PlayerHeadHunt.jar` file in your server's `plugins` directory. +4. Restart your server. +5. Configure the plugin by editing the `config.yml` file in the `plugins/PlayerHeadHunt` directory. -## Dependencies -- `WorldEdit` is **required** since it is used to count all the heads in the hunting region. +## Configuration -## Gameplay -When a player right-clicks a Player Head, the coordinates are logged in a database and a number incremented in their name. +The `config.yml` file is heavily commented and allows you to customize many aspects of the plugin. -#### Milestones -When a player finds over an X amount of heads, a message will broadcast to all online players that they have reached a milestone. -The milestones are hardcoded and at this time cannot be changed, these milestones are `10, 50, 100, 150, 200, 500`. +### `REGION` -##### Milestone Helmets -To symbolise to other people in the Head Hunt where people are at, every goal achieved in the table below is given a helmet to visualise how much someone has progressed in the Hunt. +This section defines the area where heads will be placed. You need to set the coordinates for the upper and lower corners of the region. -| Number | Helmet | -|--------|-----------| -| 50 | Leather | -| 100 | Chainmail | -| 150 | Iron | -| 200 | Gold | -| 250 | Diamond | -| 300 | Netherite | +```yaml +REGION: + UPPERREGION: + X: -54 + Y: 255 # Do not change this, this expands vertically. + Z: 48 + LOWERREGION: + X: -33 + Y: 0 # Do not change this, this expands vertically. + Z: 4 +``` -#### Head Collection Cooldown -To avoid hunters following other players to collect their heads, heads will disappear and reappear in a configurable option `HEAD.RESPAWNTIMER` +### `FEATURE` -## Commands -| Command | Description | Permission | -|--------------|------------------------------------------------|-----------------------------| -| /heads | Grab the amount of heads you have. | | -| /clearheads | Clear all heads from yourself. | `playerheadhunt.clearhead` | -| /countheads | Recalculates the number of heads in the world. | `playerheadhunt.countheads` | -| /leaderboard | Show the 5 best head hunters on the Server. | | \ No newline at end of file +Enable or disable certain features of the plugin. + +```yaml +FEATURE: + MILESTONEHAT: TRUE + MILESTONEMESSAGE: TRUE +``` + +* `MILESTONEHAT`: If `true`, players will receive a helmet as a reward for reaching a milestone. +* `MILESTONEMESSAGE`: If `true`, a message will be broadcasted when a player reaches a milestone. + +### `HEAD` + +Configure the heads themselves. + +```yaml +HEAD: + HEADTOTAL: + HEADBLOCK: PLAYER_HEAD + RESPAWNTIMER: 1200 # Default is 1200 (1 minute) + SKINSMAX: 9 + SKINS: + 0: "..." + 1: "..." + # ... +``` + +* `HEADTOTAL`: The total number of heads in the region. This is calculated automatically. +* `HEADBLOCK`: The material of the block to be used as a head. A list of materials can be found [here](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html). +* `RESPAWNTIMER`: The time in seconds it takes for a head to respawn after being found. +* `SKINSMAX`: The number of skins to use for the heads. +* `SKINS`: A list of base64 encoded skins for the player heads. + +### `SOUND` + +Customize the sounds played by the plugin. A list of sounds can be found [here](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Sound.html). + +```yaml +SOUND: + HEADFOUND: "BLOCK_NOTE_BLOCK_SNARE" + HEADALREADYFOUND: "BLOCK_NOTE_BLOCK_BASS" + MINORCOLLECTIONMILESTONE: "ENTITY_PLAYER_LEVELUP" + MAJORCOLLECTIONMILESTONE: "UI_TOAST_CHALLENGE_COMPLETE" +``` + +### `MILESTONES` + +Define the milestones for the head hunt. + +```yaml +MILESTONES: + MINOR: + - 16 + - 32 + # ... + MAJOR: + - 128 + - 256 + # ... + LEATHERHELMET: 16 + CHAINMAILHELMET: 32 + # ... +``` + +* `MINOR` and `MAJOR`: Lists of head counts that trigger milestone events. +* `LEATHERHELMET`, `CHAINMAILHELMET`, etc.: The number of heads a player must find to receive the corresponding helmet. + +### `LANG` + +Customize all user-facing messages. + +```yaml +LANG: + DATABASE: + CONNECTIONERROR: "&cA database error has occurred, please contact an Administrator." + # ... + COMMAND: + # ... + HEAD: + # ... + LEADERBOARD: + # ... +``` + +## For Developers + +The source code is available on [GitHub](https://github.com/ModularSoftAU/PlayerHeadHunt). Feel free to contribute to the project by creating issues or pull requests. \ No newline at end of file