diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
index bf1660e..2a3cb2a 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.md
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -25,9 +25,9 @@ about: Create a report to help us improve XRayMonitor
### Server Information
* **Server version/platform:**
-* **XRayMonitor Version:**
+* **XRayMonitor Version:**
* **LogBlock Version:**
-* **Other plugins(if any):**
+
### Additional Info
diff --git a/pom.xml b/pom.xml
index 468298d..feaf7b0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
me.drendov.xRayMonitor
XRayMonitor
- 1.1.1
+ 1.2.0
jar
XRayMonitor
Let's fight X-Ray cheat with simple Math!
diff --git a/src/config.yml b/src/config.yml
index 50ecdac..fee2b25 100644
--- a/src/config.yml
+++ b/src/config.yml
@@ -49,3 +49,4 @@ logOreBreaks:
mossy: false
spawners: false
logging_plugin: logblock
+debug: false
diff --git a/src/main/java/me/drendov/XRayMonitor/Cmd.java b/src/main/java/me/drendov/XRayMonitor/Cmd.java
index 6978062..d7827b3 100644
--- a/src/main/java/me/drendov/XRayMonitor/Cmd.java
+++ b/src/main/java/me/drendov/XRayMonitor/Cmd.java
@@ -28,36 +28,67 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
// xrm
if (cmd.getName().equalsIgnoreCase("xrm")) {
if (sender.hasPermission("xrm.check") || sender.isOp()) {
+
+ // Handle subcommands
+ if (args.length == 1 && args[0].equalsIgnoreCase("reload")) {
+ plugin.config.load();
+ XRayMonitor.sendMessage(player, TextMode.Success, Messages.Reloaded);
+ return true;
+ }
+ if (args.length == 1 && args[0].equalsIgnoreCase("help")) {
+ plugin.showHelp(sender);
+ return true;
+ }
+ if (args.length == 2 && args[0].equalsIgnoreCase("clear")) {
+ if (sender.hasPermission("xrm.clear") || sender.isOp()) {
+ try {
+ plugin.clearPlayer(sender, args[1]);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+ XRayMonitor.sendMessage(player, TextMode.Err, Messages.NoPermissionForCommand);
+ return true;
+ }
+
+ // Handle "check" subcommand - shift args if it's present
+ String[] shiftedArgs = args;
+ if (args.length > 0 && args[0].equalsIgnoreCase("check")) {
+ if (args.length < 2) {
+ plugin.showInfo(sender);
+ return true;
+ }
+ // Remove "check" from args, shift everything down
+ shiftedArgs = new String[args.length - 1];
+ System.arraycopy(args, 1, shiftedArgs, 0, args.length - 1);
+ }
+
String playerName = "";
- if (args.length > 0) {
- if (!args[0].contains(":")) {
- playerName = args[0];
+ if (shiftedArgs.length > 0) {
+ if (!shiftedArgs[0].contains(":")) {
+ playerName = shiftedArgs[0];
}
} else {
plugin.showInfo(sender);
return true;
}
+
String world = "";
int hours = -1;
String oreName = "";
float rate = 0.0f;
HashMap hm = new HashMap();
- String[] nonPlayerArgs = new String[args.length];
+ String[] nonPlayerArgs = new String[shiftedArgs.length];
try {
int i;
- if (!args[0].contains(":")) {
- if (args[0].equals("clear")) {
- for (i = 2; i < args.length; ++i) {
- nonPlayerArgs[i - 2] = args[i];
- }
- } else {
- for (i = 1; i < args.length; ++i) {
- nonPlayerArgs[i - 1] = args[i];
- }
+ if (!shiftedArgs[0].contains(":")) {
+ for (i = 1; i < shiftedArgs.length; ++i) {
+ nonPlayerArgs[i - 1] = shiftedArgs[i];
}
} else {
- for (i = 0; i < args.length; ++i) {
- nonPlayerArgs[i] = args[i];
+ for (i = 0; i < shiftedArgs.length; ++i) {
+ nonPlayerArgs[i] = shiftedArgs[i];
}
}
for (String arg : nonPlayerArgs) {
@@ -72,7 +103,9 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
}
if (hm.containsKey("rate")) {
rate = Float.parseFloat(hm.get("rate"));
- logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " rate=" + rate);
+ if (plugin.config.isDebug()) {
+ logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " rate=" + rate);
+ }
if ( rate <= 0 ) {
XRayMonitor.sendMessage(player, TextMode.Err, Messages.ErrRatePositive);
return true;
@@ -80,11 +113,15 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
}
if (hm.containsKey("since")) {
hours = Integer.parseInt(hm.get("since"));
- logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " hours=" + hours);
+ if (plugin.config.isDebug()) {
+ logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " hours=" + hours);
+ }
}
if (hm.containsKey("world")) {
world = hm.get("world");
- logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " world=" + world);
+ if (plugin.config.isDebug()) {
+ logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " world=" + world);
+ }
if (!plugin.checkWorld(world)) {
XRayMonitor.sendMessage(player, TextMode.Err, Messages.WorldNotFound);
return true;
@@ -92,29 +129,11 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
}
if (hm.containsKey("ore")) {
oreName = hm.get("ore");
- logger.info("DEBUG: oreName=" + oreName);
- }
- if (args.length == 1 && args[0].equalsIgnoreCase("reload")) {
- plugin.config.load();
- XRayMonitor.sendMessage(player, TextMode.Success, Messages.Reloaded);
- return true;
- }
- if (args.length == 1 && args[0].equalsIgnoreCase("help")) {
- plugin.showHelp(sender);
- return true;
- }
- if (args.length == 2 && args[0].equalsIgnoreCase("clear")) {
- if (sender.hasPermission("xrm.clear") || sender.isOp()) {
- try {
- plugin.clearPlayer(sender, args[1]);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return true;
+ if (plugin.config.isDebug()) {
+ logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " oreName=" + oreName);
}
- XRayMonitor.sendMessage(player, TextMode.Err, Messages.NoPermissionForCommand);
- return true;
}
+
if (playerName.length() == 0) {
this.plugin.showInfo(sender);
return true;
@@ -123,7 +142,9 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
try {
if (ClearedPlayerFile.wasPlayerCleared(playerName)) {
hours = ClearedPlayerFile.getHoursFromClear(playerName);
- logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " hours for cleared playerName=" + hours);
+ if (plugin.config.isDebug()) {
+ logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " hours for cleared playerName=" + hours);
+ }
}
world = Config.defaultWorld;
if (world != null && !plugin.checkWorld(world)) {
@@ -131,7 +152,9 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
return true;
}
- logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " Run global check for " + playerName + " world=" + world + " hours=" + hours);
+ if (plugin.config.isDebug()) {
+ logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " Run global check for " + playerName + " world=" + world + " hours=" + hours);
+ }
this.checker.checkGlobal(playerName, sender, world, hours);
return true;
} catch (Exception e) {
@@ -147,7 +170,9 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
if (ClearedPlayerFile.wasPlayerCleared(playerName)) {
hours = ClearedPlayerFile.getHoursFromClear(playerName);
}
- logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " Run global check for " + playerName + " sender=" + sender + " world=" + world + " hours=" + hours);
+ if (plugin.config.isDebug()) {
+ logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " Run global check for " + playerName + " sender=" + sender + " world=" + world + " hours=" + hours);
+ }
this.checker.checkGlobal(playerName, sender, world, hours);
return true;
} catch (Exception e) {
@@ -156,12 +181,16 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String
}
if (world.length() > 0 && !oreName.isEmpty()) {
if (playerName.equalsIgnoreCase("all") && rate > 0.0f) {
- logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " List All XRay-ers check for world=" + world + " oreName=" + oreName + " rate=" + rate + " hours=" + hours);
+ if (plugin.config.isDebug()) {
+ logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " List All XRay-ers check for world=" + world + " oreName=" + oreName + " rate=" + rate + " hours=" + hours);
+ }
new Thread(new CustomRunnable(sender, world, oreName, rate, hours) {
@Override
public void run() {
- logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " List All XRay-ers check for world=" + this.world + " this.oreName=" + this.oreName + " this.rate=" + this.rate + " this.hours=" + this.hours);
+ if (plugin.config.isDebug()) {
+ logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " List All XRay-ers check for world=" + this.world + " this.oreName=" + this.oreName + " this.rate=" + this.rate + " this.hours=" + this.hours);
+ }
Cmd.this.checker.listAllXRayers(this.sender, this.world, this.oreName, this.rate, this.hours);
}
}).start();
@@ -170,7 +199,9 @@ public void run() {
if (ClearedPlayerFile.wasPlayerCleared(playerName)) {
hours = ClearedPlayerFile.getHoursFromClear(playerName);
}
- logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " Run checkSingle for " + playerName + " oreName=" + oreName + " world=" + world + " hours=" + hours);
+ if (plugin.config.isDebug()) {
+ logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " Run checkSingle for " + playerName + " oreName=" + oreName + " world=" + world + " hours=" + hours);
+ }
this.checker.checkSingle(playerName, sender, oreName, world, hours);
return true;
}
diff --git a/src/main/java/me/drendov/XRayMonitor/Config.java b/src/main/java/me/drendov/XRayMonitor/Config.java
index e314f68..cdcc975 100644
--- a/src/main/java/me/drendov/XRayMonitor/Config.java
+++ b/src/main/java/me/drendov/XRayMonitor/Config.java
@@ -15,6 +15,7 @@ void load() {
this.plugin.reloadConfig();
this.config = this.plugin.getConfig();
this.config.addDefault("logging_plugin", "logblock");
+ this.config.addDefault("debug", false);
this.config.addDefault("default_world", "world");
this.config.addDefault("checkOnPlayerJoin", true);
this.config.addDefault("checkOnPlayerJoin.warningMessage", "%player% has higher than average stats for %ores% and may be a cheater. Watch carefully.");
@@ -77,6 +78,10 @@ public double getRate(String type, String ore) {
return this.config.getDouble(ore + "_" + type);
}
+ public boolean isDebug() {
+ return this.config.getBoolean("debug");
+ }
+
String getCmd(String name) {
return this.config.getString(name);
}
diff --git a/src/main/java/me/drendov/XRayMonitor/Listeners.java b/src/main/java/me/drendov/XRayMonitor/Listeners.java
index c72a3c7..06b84d1 100644
--- a/src/main/java/me/drendov/XRayMonitor/Listeners.java
+++ b/src/main/java/me/drendov/XRayMonitor/Listeners.java
@@ -67,40 +67,46 @@ public void run() {
int count_netherrack = 0;
int count_basalt = 0;
if (Listeners.this.plugin.getConfig().getString("logging_plugin").equalsIgnoreCase("logblock")) {
- count_stone = Listeners.this.lb.oreLookup(playerName, "stone", world, hours);
- count_andesite = Listeners.this.lb.oreLookup(playerName, "andesite", world, hours);
- count_diorite = Listeners.this.lb.oreLookup(playerName, "diorite", world, hours);
- count_granite = Listeners.this.lb.oreLookup(playerName, "granite", world, hours);
- count_deepslate = Listeners.this.lb.oreLookup(playerName, "deepslate", world, hours);
- count_deepslate_bricks = Listeners.this.lb.oreLookup(playerName, "deepslate_bricks", world, hours);
- count_deepslate_tiles = Listeners.this.lb.oreLookup(playerName, "deepslate_tiles", world, hours);
- count_polished_deepslate = Listeners.this.lb.oreLookup(playerName, "polished_deepslate", world, hours);
- count_chiseled_deepslate = Listeners.this.lb.oreLookup(playerName, "chiseled_deepslate", world, hours);
- count_calcite = Listeners.this.lb.oreLookup(playerName, "calcite", world, hours);
- count_blackstone = Listeners.this.lb.oreLookup(playerName, "blackstone", world, hours);
- count_stones = count_stone + count_andesite + count_diorite + count_granite + count_deepslate + count_deepslate_bricks + count_deepslate_tiles + count_polished_deepslate + count_chiseled_deepslate + count_calcite + count_blackstone;
-
- diamond_count = Listeners.this.lb.oreLookup(playerName, "diamond_ore", world, hours);
- diamond_count += Listeners.this.lb.oreLookup(playerName, "deepslate_diamond_ore", world, hours);
- gold_count = Listeners.this.lb.oreLookup(playerName, "gold_ore", world, hours);
- gold_count += Listeners.this.lb.oreLookup(playerName, "deepslate_gold_ore", world, hours);
- gold_count += Listeners.this.lb.oreLookup(playerName, "nether_gold_ore", world, hours);
- lapis_count = Listeners.this.lb.oreLookup(playerName, "lapis_ore", world, hours);
- lapis_count += Listeners.this.lb.oreLookup(playerName, "deepslate_lapis_ore", world, hours);
- copper_count = Listeners.this.lb.oreLookup(playerName, "copper_ore", world, hours);
- copper_count += Listeners.this.lb.oreLookup(playerName, "deepslate_copper_ore", world, hours);
- iron_count = Listeners.this.lb.oreLookup(playerName, "iron_ore", world, hours);
- iron_count += Listeners.this.lb.oreLookup(playerName, "deepslate_iron_ore", world, hours); redstone_count = Listeners.this.lb.oreLookup(playerName, "redstone_ore", world, hours);
- coal_count = Listeners.this.lb.oreLookup(playerName, "coal_ore", world, hours);
- coal_count += Listeners.this.lb.oreLookup(playerName, "deepslate_coal_ore", world, hours);
- mossy_count = Listeners.this.lb.oreLookup(playerName, "mossy_cobblestone", world, hours);
- emerald_count = Listeners.this.lb.oreLookup(playerName, "emerald_ore", world, hours);
- emerald_count += Listeners.this.lb.oreLookup(playerName, "deepslate_emerald_ore", world, hours);
- ancient_debris_count = Listeners.this.lb.oreLookup(playerName, "ancient_debris", world, hours);
- spawner_count = Listeners.this.lb.oreLookup(playerName, "spawner", world, hours);
- count_netherrack = Listeners.this.lb.oreLookup(playerName, "netherrack", world, hours);
- count_basalt = Listeners.this.lb.oreLookup(playerName, "basalt", world, hours);
+ try {
+ count_stone = Listeners.this.lb.oreLookup(playerName, "stone", world, hours);
+ count_andesite = Listeners.this.lb.oreLookup(playerName, "andesite", world, hours);
+ count_diorite = Listeners.this.lb.oreLookup(playerName, "diorite", world, hours);
+ count_granite = Listeners.this.lb.oreLookup(playerName, "granite", world, hours);
+ count_deepslate = Listeners.this.lb.oreLookup(playerName, "deepslate", world, hours);
+ count_deepslate_bricks = Listeners.this.lb.oreLookup(playerName, "deepslate_bricks", world, hours);
+ count_deepslate_tiles = Listeners.this.lb.oreLookup(playerName, "deepslate_tiles", world, hours);
+ count_polished_deepslate = Listeners.this.lb.oreLookup(playerName, "polished_deepslate", world, hours);
+ count_chiseled_deepslate = Listeners.this.lb.oreLookup(playerName, "chiseled_deepslate", world, hours);
+ count_calcite = Listeners.this.lb.oreLookup(playerName, "calcite", world, hours);
+ count_blackstone = Listeners.this.lb.oreLookup(playerName, "blackstone", world, hours);
+ count_stones = count_stone + count_andesite + count_diorite + count_granite + count_deepslate + count_deepslate_bricks + count_deepslate_tiles + count_polished_deepslate + count_chiseled_deepslate + count_calcite + count_blackstone;
+ diamond_count = Listeners.this.lb.oreLookup(playerName, "diamond_ore", world, hours);
+ diamond_count += Listeners.this.lb.oreLookup(playerName, "deepslate_diamond_ore", world, hours);
+ gold_count = Listeners.this.lb.oreLookup(playerName, "gold_ore", world, hours);
+ gold_count += Listeners.this.lb.oreLookup(playerName, "deepslate_gold_ore", world, hours);
+ gold_count += Listeners.this.lb.oreLookup(playerName, "nether_gold_ore", world, hours);
+ lapis_count = Listeners.this.lb.oreLookup(playerName, "lapis_ore", world, hours);
+ lapis_count += Listeners.this.lb.oreLookup(playerName, "deepslate_lapis_ore", world, hours);
+ copper_count = Listeners.this.lb.oreLookup(playerName, "copper_ore", world, hours);
+ copper_count += Listeners.this.lb.oreLookup(playerName, "deepslate_copper_ore", world, hours);
+ iron_count = Listeners.this.lb.oreLookup(playerName, "iron_ore", world, hours);
+ iron_count += Listeners.this.lb.oreLookup(playerName, "deepslate_iron_ore", world, hours);
+ redstone_count = Listeners.this.lb.oreLookup(playerName, "redstone_ore", world, hours);
+ redstone_count += Listeners.this.lb.oreLookup(playerName, "deepslate_redstone_ore", world, hours);
+ coal_count = Listeners.this.lb.oreLookup(playerName, "coal_ore", world, hours);
+ coal_count += Listeners.this.lb.oreLookup(playerName, "deepslate_coal_ore", world, hours);
+ mossy_count = Listeners.this.lb.oreLookup(playerName, "mossy_cobblestone", world, hours);
+ emerald_count = Listeners.this.lb.oreLookup(playerName, "emerald_ore", world, hours);
+ emerald_count += Listeners.this.lb.oreLookup(playerName, "deepslate_emerald_ore", world, hours);
+ ancient_debris_count = Listeners.this.lb.oreLookup(playerName, "ancient_debris", world, hours);
+ spawner_count = Listeners.this.lb.oreLookup(playerName, "spawner", world, hours);
+ count_netherrack = Listeners.this.lb.oreLookup(playerName, "netherrack", world, hours);
+ count_basalt = Listeners.this.lb.oreLookup(playerName, "basalt", world, hours);
+ } catch (Exception ex) {
+ Listeners.this.plugin.getLogger().severe("Error during ore lookup for player " + playerName + ": " + ex.getMessage());
+ ex.printStackTrace();
+ }
}
String dia = "";
String gld = "";
diff --git a/src/main/java/me/drendov/XRayMonitor/lookups/Checkers.java b/src/main/java/me/drendov/XRayMonitor/lookups/Checkers.java
index 09caa00..c88040d 100644
--- a/src/main/java/me/drendov/XRayMonitor/lookups/Checkers.java
+++ b/src/main/java/me/drendov/XRayMonitor/lookups/Checkers.java
@@ -259,8 +259,20 @@ private ChatColor determineOreColor(String oreType, float percentage) {
* Format percentage to 2 decimal places
*/
private String formatPercentage(float percentage) {
+ // Handle special cases
+ if (Float.isInfinite(percentage) || Float.isNaN(percentage) || percentage < 0) {
+ return "0.00";
+ }
+ if (percentage > 100) {
+ return "100.00";
+ }
+
String s = percentage + "000000000";
- return String.valueOf(Float.parseFloat(s.substring(0, s.lastIndexOf('.') + 3)));
+ int dotIndex = s.lastIndexOf('.');
+ if (dotIndex == -1) {
+ return String.valueOf((int) percentage);
+ }
+ return String.valueOf(Float.parseFloat(s.substring(0, dotIndex + 3)));
}
/**
diff --git a/src/main/java/me/drendov/XRayMonitor/lookups/LogBlockLookup.java b/src/main/java/me/drendov/XRayMonitor/lookups/LogBlockLookup.java
index 03db405..1eebf04 100644
--- a/src/main/java/me/drendov/XRayMonitor/lookups/LogBlockLookup.java
+++ b/src/main/java/me/drendov/XRayMonitor/lookups/LogBlockLookup.java
@@ -9,32 +9,59 @@
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
+import java.util.logging.Logger;
+import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
public class LogBlockLookup {
private XRayMonitor plugin = XRayMonitor.getInstance();
+ private Logger logger = plugin.getLogger();
public int oreLookup(String player, String oreName, String world, int hours) throws SQLException {
- LogBlock logBlock = (LogBlock)this.plugin.getServer().getPluginManager().getPlugin("LogBlock");
- QueryParams params = new QueryParams(logBlock);
- params.setPlayer(player);
- params.bct = QueryParams.BlockChangeType.DESTROYED;
- params.limit = -1;
- params.since = hours * 60;
- params.world = this.plugin.getServer().getWorld(world);
+ try {
+ LogBlock logBlock = (LogBlock)this.plugin.getServer().getPluginManager().getPlugin("LogBlock");
+ if (logBlock == null) {
+ this.plugin.getLogger().warning("LogBlockLookup: LogBlock plugin not found!");
+ return 0;
+ }
- final Material mat = Material.matchMaterial(oreName);
- if (mat == null) {
- throw new IllegalArgumentException("No material matching: '" + oreName + "'");
- }
- ArrayList lookupListTypes = new ArrayList<>();
- lookupListTypes.add(mat);
- params.types = lookupListTypes;
+ QueryParams params = new QueryParams(logBlock);
+ params.setPlayer(player);
+ params.bct = QueryParams.BlockChangeType.DESTROYED;
+ params.limit = -1;
+ params.since = hours * 60;
+ params.world = this.plugin.getServer().getWorld(world);
+
+ if (params.world == null) {
+ this.plugin.getLogger().warning("LogBlockLookup: World '" + world + "' not found!");
+ return 0;
+ }
+
+ final Material mat = Material.matchMaterial(oreName);
+ if (mat == null) {
+ this.plugin.getLogger().warning("LogBlockLookup: No material matching '" + oreName + "' for player " + player);
+ return 0;
+ }
- params.needCount = true;
- int count = logBlock.getCount(params);
- return count;
+ ArrayList lookupListTypes = new ArrayList<>();
+ lookupListTypes.add(mat);
+ params.types = lookupListTypes;
+
+ params.needCount = true;
+ int count = logBlock.getCount(params);
+
+ // Debug logging
+ if (this.plugin.config.isDebug()) {
+ logger.info(ChatColor.RED + "[DEBUG]" + ChatColor.WHITE + " LogBlock lookup - Player: " + player + ", Ore: " + oreName + " (" + mat + "), World: " + world + ", Hours: " + hours + ", Count: " + count);
+ }
+
+ return count;
+ } catch (Exception e) {
+ this.plugin.getLogger().severe("Error in oreLookup for player " + player + ", ore " + oreName + ": " + e.getMessage());
+ e.printStackTrace();
+ return 0;
+ }
}
public List playerLookup(CommandSender sender, String oreName, String world) {
diff --git a/src/test/java/me/drendov/XRayMonitor/CmdTest.java b/src/test/java/me/drendov/XRayMonitor/CmdTest.java
new file mode 100644
index 0000000..51f254a
--- /dev/null
+++ b/src/test/java/me/drendov/XRayMonitor/CmdTest.java
@@ -0,0 +1,119 @@
+package me.drendov.XRayMonitor;
+
+import org.junit.jupiter.api.DisplayName;
+
+/**
+ * Unit Test Cases for Cmd Command Handler
+ *
+ * This test class documents all potential command scenarios and their expected behavior.
+ *
+ * XRayMonitor provides the following commands:
+ *
+ * 1. /xrm reload
+ * - Reloads the configuration file
+ * - Requires: xrm.check permission or OP status
+ * - Expected: true (command handled)
+ * - Action: Calls plugin.config.load()
+ *
+ * 2. /xrm help
+ * - Displays help information
+ * - Requires: xrm.check permission or OP status
+ * - Expected: true (command handled)
+ * - Action: Calls plugin.showHelp()
+ *
+ * 3. /xrm clear
+ * - Clears player's recorded data
+ * - Requires: xrm.check AND xrm.clear permissions or OP status
+ * - Expected: true (command handled)
+ * - Action: Calls plugin.clearPlayer(player)
+ *
+ * 4. /xrm check [params...]
+ * - Checks for suspicious ore mining patterns
+ * - Optional subcommand: "check" can be omitted
+ * - Requires: xrm.check permission or OP status
+ *
+ * Parameters (all optional, use key:value format):
+ * - world: - Specify which world to check (default: default world)
+ * - ore: - Check specific ore type (default: global check for all ores)
+ * - since: - Check last N hours (default: -1 for all time)
+ * - rate: - Confidence threshold for listing (default: 0.0)
+ *
+ * Examples:
+ * - /xrm check WaterDemon
+ * - /xrm check WaterDemon world:world ore:diamond
+ * - /xrm check WaterDemon ore:diamond world:world since:24 rate:3.5
+ * - /xrm WaterDemon (check is implicit)
+ * - /xrm all ore:diamond rate:3.5 world:world
+ * (Special: when player name is "all", lists all players matching criteria)
+ *
+ * TEST SCENARIOS:
+ *
+ * A. SUBCOMMAND TESTS
+ * 1. Reload command succeeds with permission
+ * 2. Help command succeeds
+ * 3. Clear command with both permissions
+ * 4. Clear command without xrm.clear permission
+ * 5. Clear command missing player argument (ignored)
+ * 6. Command fails without xrm.check permission
+ * 7. Command succeeds with OP status (bypasses permissions)
+ *
+ * B. CHECK COMMAND PARAMETER TESTS
+ * 1. Check with player name only (implicit check)
+ * 2. Check with "check" subcommand explicit
+ * 3. Check without "check" subcommand (implicit)
+ * 4. Check with world parameter
+ * 5. Check with ore parameter
+ * 6. Check with rate parameter
+ * 7. Check with since parameter
+ * 8. Check with all parameters combined
+ * 9. Check invalid world (should fail silently or show error)
+ * 10. Check rate = 0 (invalid, triggers error message)
+ * 11. Check rate < 0 (invalid, triggers error message)
+ *
+ * C. SPECIAL CASES
+ * 1. Player name "all" with ore and rate (lists all matching players)
+ * 2. Player name "all" without rate (does single check for all)
+ * 3. Missing player name shows info
+ * 4. Check without any arguments shows info
+ * 5. Parameter without colon is ignored
+ * 6. Parameter with multiple colons (only first one splits key:value)
+ *
+ * D. CASE SENSITIVITY TESTS
+ * 1. "reload", "RELOAD", "Reload" all work
+ * 2. "help", "HELP", "Help" all work
+ * 3. "check", "CHECK", "Check" all work
+ * 4. "clear", "CLEAR", "Clear" all work
+ *
+ * E. EDGE CASES
+ * 1. Very large arguments array (100+ items)
+ * 2. Empty player name string
+ * 3. Player name with special characters (_-123)
+ * 4. Non-xrm command returns false
+ * 5. Rate parameter with many decimals (3.14159265)
+ * 6. Since parameter with very large hours (99999)
+ * 7. Multiple parameters in different orders
+ *
+ * F. DEBUG LOGGING
+ * 1. DEBUG logs output when config.isDebug() = true
+ * 2. DEBUG logs suppressed when config.isDebug() = false
+ *
+ * G. PERMISSION TESTS
+ * 1. xrm.check allows check commands
+ * 2. xrm.clear allows clear command
+ * 3. OP status bypasses all permission checks
+ * 4. Missing both permission and OP denies command
+ *
+ * IMPLEMENTATION NOTES:
+ * - The Cmd class uses a static initializer that requires XRayMonitor singleton
+ * - For integration testing, use the full plugin environment
+ * - Unit test coverage focuses on command parsing logic and parameter handling
+ * - Parameter parsing handles malformed input gracefully
+ * - All commands are case-insensitive via equalsIgnoreCase()
+ */
+@DisplayName("Cmd Command Handler - Test Case Documentation")
+public class CmdTest {
+ // This file serves as documentation for all command test scenarios
+ // For actual test execution, use integration tests with the full plugin environment
+ // or use Bukkit test frameworks that support the full server initialization
+}
+