From 196410e28f3d243355552e19c8e8df972d6c5cb7 Mon Sep 17 00:00:00 2001 From: LMBishop <13875753+LMBishop@users.noreply.github.com> Date: Tue, 5 Jul 2022 19:56:39 +0100 Subject: Add debug report & quest commands --- .../bukkit/command/AdminCommandSwitcher.java | 4 +- .../bukkit/command/AdminDebugCommandHandler.java | 390 --------------------- .../bukkit/command/AdminDebugCommandSwitcher.java | 60 ++++ .../command/AdminDebugQuestCommandHandler.java | 101 ++++++ .../command/AdminDebugReportCommandHandler.java | 386 ++++++++++++++++++++ .../quests/bukkit/tasktype/BukkitTaskType.java | 8 + .../bukkit/tasktype/BukkitTaskTypeManager.java | 30 +- 7 files changed, 585 insertions(+), 394 deletions(-) delete mode 100644 bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugCommandHandler.java create mode 100644 bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugCommandSwitcher.java create mode 100644 bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugQuestCommandHandler.java create mode 100644 bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugReportCommandHandler.java (limited to 'bukkit/src/main/java') diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminCommandSwitcher.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminCommandSwitcher.java index 797ebcd7..d786636f 100644 --- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminCommandSwitcher.java +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminCommandSwitcher.java @@ -24,7 +24,7 @@ public class AdminCommandSwitcher extends CommandSwitcher { super.subcommands.put("update", new AdminUpdateCommandHandler(plugin)); super.subcommands.put("wiki", new AdminWikiCommandHandler(plugin)); super.subcommands.put("about", new AdminAboutCommandHandler(plugin)); - super.subcommands.put("debug", new AdminDebugCommandHandler(plugin)); + super.subcommands.put("debug", new AdminDebugCommandSwitcher(plugin)); } @Override @@ -43,7 +43,7 @@ public class AdminCommandSwitcher extends CommandSwitcher { sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a update " + ChatColor.DARK_GRAY + ": check for updates"); sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a wiki " + ChatColor.DARK_GRAY + ": get a link to the Quests wiki"); sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a about " + ChatColor.DARK_GRAY + ": get information about Quests"); - sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a debug " + ChatColor.DARK_GRAY + ": generate a debug report"); + sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a debug " + ChatColor.DARK_GRAY + ": view help for debugging"); } @Override diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugCommandHandler.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugCommandHandler.java deleted file mode 100644 index 140a1e95..00000000 --- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugCommandHandler.java +++ /dev/null @@ -1,390 +0,0 @@ -package com.leonardobishop.quests.bukkit.command; - -import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin; -import com.leonardobishop.quests.bukkit.item.QuestItem; -import com.leonardobishop.quests.bukkit.questcompleter.BukkitQuestCompleter; -import com.leonardobishop.quests.bukkit.util.LogHistory; -import com.leonardobishop.quests.common.config.ConfigProblem; -import com.leonardobishop.quests.common.player.QPlayer; -import com.leonardobishop.quests.common.player.QPlayerPreferences; -import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress; -import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile; -import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress; -import com.leonardobishop.quests.common.quest.Category; -import com.leonardobishop.quests.common.quest.Quest; -import com.leonardobishop.quests.common.quest.Task; -import com.leonardobishop.quests.common.tasktype.TaskType; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileAttribute; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.function.Function; - -public class AdminDebugCommandHandler implements CommandHandler { - - private final BukkitQuestsPlugin plugin; - - private List errors; - private CommandSender currentReportGenerator; - - public AdminDebugCommandHandler(BukkitQuestsPlugin plugin) { - this.plugin = plugin; - } - - @Override - public void handle(CommandSender sender, String[] args) { - if (currentReportGenerator != null) { - sender.sendMessage(ChatColor.RED + "A report is already being generated. Please wait until it is finished."); - return; - } - - errors = new ArrayList<>(); - currentReportGenerator = sender; - - try { - sender.sendMessage(ChatColor.GRAY + "Generating Quests debug report..."); - if (!plugin.getLogHistory().isEnabled()) { - sender.sendMessage(ChatColor.RED + "Log history is not enabled. Quests will only generate a basic debug report. " + - "If you were asked to generate one with log history, this must be turned on in your configuration."); - } - long start = System.currentTimeMillis(); - Path path = new File(plugin.getDataFolder() + File.separator + "debug" + File.separator + "debug_" + start + ".txt").toPath(); - try { - Path directory = new File(plugin.getDataFolder() + File.separator + "debug").toPath(); - Files.createDirectories(directory); - Files.createFile(path); - } catch (IOException e) { - sender.sendMessage(ChatColor.DARK_RED + "Failed to create debug file!"); - e.printStackTrace(); - return; - } - - List lines = new ArrayList<>(); - lines.add("################################"); - lines.add("# Server Information #"); - lines.add("################################"); - lines.add(""); - lines.add("Server name: " + plugin.getServer().getName()); - lines.add("Server version: " + plugin.getServer().getVersion()); - lines.add("Bukkit version: " + plugin.getServer().getBukkitVersion()); - lines.add("Minecraft version: " + plugin.getServer().getMinecraftVersion()); - lines.add("Player count: " + plugin.getServer().getOnlinePlayers().size()); - lines.add(""); - - lines.add("################################"); - lines.add("# Quests Information #"); - lines.add("################################"); - lines.add(""); - lines.add("Quests version: " + plugin.getDescription().getVersion()); - lines.add("Valid configuration: " + plugin.isValidConfiguration()); - lines.add(""); - printList(lines, 0, "Task types available", plugin.getTaskTypeManager().getTaskTypes(), TaskType::getType); - lines.add(""); - printList(lines, 0, "Quests", plugin.getQuestManager().getQuests().values(), Quest::getId); - lines.add(""); - printList(lines, 0, "Categories", plugin.getQuestManager().getCategories(), Category::getId); - lines.add(""); - BukkitQuestCompleter completer = (BukkitQuestCompleter) plugin.getQuestCompleter(); - printList(lines, 0, "Completion queue", completer.getCompletionQueue(), questProgress -> questProgress.getPlayer().toString()); - lines.add(""); - printList(lines, 0, "Full check queue", completer.getFullCheckQueue(), questProgressFile -> questProgressFile.getPlayerUUID().toString()); - lines.add(""); - printList(lines, 0, "Expired check queue", completer.getExpiredCheckQueue(), UUID::toString); - lines.add(""); - - lines.add("################################"); - lines.add("# Storage #"); - lines.add("################################"); - lines.add(""); - lines.add("Storage provider: " + plugin.getStorageProvider().getName()); - lines.add(""); - - lines.add("################################"); - lines.add("# Hook #"); - lines.add("################################"); - lines.add(""); - lines.add("Core protect hook: " + (plugin.getCoreProtectHook() != null)); - lines.add("Essentials hook: " + (plugin.getEssentialsHook() != null)); - lines.add("PlaceholderAPI hook: " + (plugin.getPlaceholderAPIHook() != null)); - lines.add("Item getter: " + plugin.getItemGetter().getClass().getSimpleName()); - lines.add("Title handler: " + plugin.getTitleHandle().getClass().getSimpleName()); - lines.add("Version specific handler: " + plugin.getVersionSpecificHandler().getClass().getSimpleName()); - lines.add(""); - - lines.add("################################"); - lines.add("# Options #"); - lines.add("################################"); - lines.add(""); - if (plugin.isValidConfiguration()) { - lines.add("GUI use placeholder API: " + plugin.getQuestsConfig().getBoolean("options.gui-use-placeholderapi", false)); - lines.add("Quests use placeholder API: " + plugin.getQuestsConfig().getBoolean("options.quests-use-placeholderapi", false)); - lines.add("Quests autostart: " + plugin.getQuestsConfig().getBoolean("options.quest-autostart", false)); - lines.add("Quests autotrack: " + plugin.getQuestsConfig().getBoolean("options.quest-autotrack", true)); - lines.add("Verify quests exist on load: " + plugin.getQuestsConfig().getBoolean("options.verify-quest-exists-on-load", true)); - lines.add("Queue executor interval: " + plugin.getQuestsConfig().getInt("options.performance-tweaking.quest-queue-executor-interval", 1) + " ticks"); - lines.add("Autosave interval: " + plugin.getQuestsConfig().getInt("options.performance-tweaking.quest-autosave-interval", 12000) + " ticks"); - lines.add("Override errors: " + plugin.getQuestsConfig().getBoolean("options.error-checking.override-errors", false)); - lines.add("Placeholder cache time: " + plugin.getQuestsConfig().getInt("options.placeholder-cache-time") + " seconds"); - lines.add("Quest mode: " + plugin.getQuestsConfig().getInt("quest-mode.mode")); - } else { - lines.add("Configuration unavailable."); - } - lines.add(""); - - lines.add("################################"); - lines.add("# Items #"); - lines.add("################################"); - lines.add(""); - lines.add("Number of items: " + plugin.getQuestItemRegistry().getAllItems().size()); - lines.add(""); - for (QuestItem questItem : plugin.getQuestItemRegistry().getAllItems()) { - Map values = getFieldValues(questItem.getClass(), questItem); - values.putAll(getFieldValues(questItem.getClass().getSuperclass(), questItem)); - printMap(lines, 0, "Item " + questItem.getId() + " (" + questItem.getClass().getSimpleName() + ")", values); - lines.add(""); - } - - lines.add("################################"); - lines.add("# Configuration Problems #"); - lines.add("################################"); - lines.add(""); - lines.add("Number of problems: " + plugin.getConfigProblems().size()); - lines.add(""); - for (Map.Entry> entry : plugin.getConfigProblems().entrySet()) { - String id = entry.getKey(); - List problems = entry.getValue(); - - printList(lines, 0, "Problems for '" + id + "'", problems, - (ConfigProblem problem) -> String.format("%s: %s (:%s)", problem.getType(), problem.getDescription(), problem.getLocation())); - lines.add(""); - } - - lines.add("################################"); - lines.add("# Quests #"); - lines.add("################################"); - lines.add(""); - lines.add("Number of quests: " + plugin.getQuestManager().getQuests().size()); - lines.add(""); - for (Quest quest : plugin.getQuestManager().getQuests().values()) { - Map questValues = getFieldValues(quest.getClass(), quest, "tasks", "tasksByType"); - try { - Field tasksField = quest.getClass().getDeclaredField("tasks"); - tasksField.setAccessible(true); - Map tasksMap = (Map) tasksField.get(quest); - Map tasksValues = new HashMap<>(); - for (Map.Entry taskEntry : tasksMap.entrySet()) { - Task task = taskEntry.getValue(); - tasksValues.put(task.getId(), getFieldValues(task.getClass(), task)); - } - questValues.put("tasks", tasksValues); - } catch (NoSuchFieldException | IllegalAccessException e) { - error("Failed to get tasks for quest " + quest.getId() + ": " + e.getClass().getSimpleName() + "(" + e.getMessage() + ")"); - e.printStackTrace(); - } - - printMap(lines, 0, "Quest '" + quest.getId() + "'", questValues); - lines.add(""); - } - - lines.add("################################"); - lines.add("# Players #"); - lines.add("################################"); - lines.add(""); - printList(lines, 0, "Players online", Bukkit.getOnlinePlayers(), player -> player.getUniqueId().toString()); - lines.add(""); - printList(lines, 0, "QPlayers loaded", plugin.getPlayerManager().getQPlayers(), qPlayer -> qPlayer.getPlayerUUID().toString()); - lines.add(""); - for (QPlayer qPlayer : plugin.getPlayerManager().getQPlayers()) { - lines.add("QPlayer " + qPlayer.getPlayerUUID() + ":"); - QPlayerPreferences preferences = qPlayer.getPlayerPreferences(); - printMap(lines, 1, "Preferences", getFieldValues(preferences.getClass(), preferences)); - - QuestProgressFile questProgressFile = qPlayer.getQuestProgressFile(); - try { - Field questProgressField = questProgressFile.getClass().getDeclaredField("questProgress"); - questProgressField.setAccessible(true); - Map questProgressMap = (Map) questProgressField.get(questProgressFile); - Map questProgressValues = new LinkedHashMap<>(); - for (Map.Entry entry : questProgressMap.entrySet()) { - QuestProgress questProgress = entry.getValue(); - Map questProgressValue = getFieldValues(questProgress.getClass(), questProgress, "plugin", "taskProgress"); - - Field taskProgressField = questProgress.getClass().getDeclaredField("taskProgress"); - taskProgressField.setAccessible(true); - Map taskProgressMap = (Map) taskProgressField.get(questProgress); - Map taskProgressValues = new LinkedHashMap<>(); - for (Map.Entry taskEntry : taskProgressMap.entrySet()) { - TaskProgress taskProgress = taskEntry.getValue(); - taskProgressValues.put(taskEntry.getKey(), getFieldValues(taskProgress.getClass(), taskProgress, "plugin", "linkedQuestProgress")); - } - questProgressValue.put("taskProgress", taskProgressValues); - - questProgressValues.put(entry.getKey(), questProgressValue); - } - printMap(lines, 1, "Quest progress", questProgressValues); - } catch (NoSuchFieldException | IllegalAccessException e) { - error("Failed to get quest progress for QPlayer " + qPlayer.getPlayerUUID() + ": " + e.getClass().getSimpleName() + "(" + e.getMessage() + ")"); - e.printStackTrace(); - } - lines.add(" Quest controller: " + qPlayer.getQuestController().getName()); - lines.add(""); - } - - if (plugin.getLogHistory().isEnabled()) { - lines.add("################################"); - lines.add("# Log History #"); - lines.add("################################"); - lines.add(""); - int timeMaxLength = 1; - int typeMaxLength = 1; - int threadMaxLength = 1; - for (LogHistory.LogEntry line : plugin.getLogHistory().getEntries()) { - timeMaxLength = Math.max(timeMaxLength, String.valueOf(line.getTime()).length()); - typeMaxLength = Math.max(typeMaxLength, line.getType().toString().length()); - threadMaxLength = Math.max(threadMaxLength, line.getThread().length()); - } - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - for (LogHistory.LogEntry line : plugin.getLogHistory().getEntries()) { - lines.add(String.format("%-" + timeMaxLength + "s %-" + typeMaxLength + "s %-" + threadMaxLength + "s | %s", - dateFormat.format(new Date(line.getTime())), line.getType().toString(), line.getThread(), line.getEntry())); - } - } - - List errors = new ArrayList<>(); - lines.add(0, ""); - lines.add(0, "Log history: " + plugin.getLogHistory().isEnabled()); - printList(errors, 0, "Errors generating report", this.errors, String::valueOf); - lines.addAll(0, errors); - lines.add(0, "Time taken: " + (System.currentTimeMillis() - start) + "ms"); - lines.add(0, "Generated at: " + new Date(start) + " (" + start + ")"); - lines.add(0, ""); - lines.add(0, "################################"); - lines.add(0, "# Report Information #"); - lines.add(0, "################################"); - try { - Files.write(path, lines, StandardCharsets.UTF_8); - } catch (IOException e) { - error("Failed to write report to " + path + "!"); - e.printStackTrace(); - return; - } - - sender.sendMessage(ChatColor.GREEN + "Debug file created at " + path + ". (Took " + (System.currentTimeMillis() - start) + "ms.)"); - } finally { - currentReportGenerator = null; - errors = new ArrayList<>(); - } - } - - private Map getFieldValues(Class clazz, Object object, String... excludeFields) { - Field[] fields = clazz.getDeclaredFields(); - Map values = new LinkedHashMap<>(); - for (Field field : fields) { - if (Arrays.asList(excludeFields).contains(field.getName())) { - continue; - } - try { - field.setAccessible(true); - values.put(field.getName(), field.get(object)); - } catch (IllegalAccessException e) { - error("Failed to get field value for " + clazz.getSimpleName() + "." + field.getName() + ": " + e.getClass().getSimpleName() + "(" + e.getMessage() + ")"); - e.printStackTrace(); - } - } - return values; - } - - private void printList(List lines, int depth, String title, Collection list) { - printList(lines, depth, title, list, Object::toString); - } - - private void printList(List lines, int depth, String title, Collection list, Function getter) { - depth = depth * 4; - if (list.size() == 0) { - lines.add(String.format("%s%s (0): (empty)", " ".repeat(depth), title)); - return; - } - lines.add(String.format("%s%s (%d):", " ".repeat(depth), title, list.size())); - for (E element : list) { - lines.add(String.format("%s - %s", " ".repeat(depth), getter.apply(element))); - } - } - - private void printMap(List lines, int depth, String title, Map map) { - depth = depth * 4; - if (map.size() == 0) { - lines.add(String.format("%s%s (0): (empty)", " ".repeat(depth), title)); - return; - } - lines.add(String.format("%s%s:", " ".repeat(depth), title)); - int keyMaxLength = 1; - int valueMaxLength = 1; - Map stringifiedValues = new HashMap<>(); - for (Map.Entry entry : map.entrySet()) { - String key = String.valueOf(entry.getKey()); - if (key.length() > keyMaxLength) { - keyMaxLength = key.length(); - } - } - for (Map.Entry entry : map.entrySet()) { - String value; - if (entry.getValue() instanceof Map) { - List subLines = new ArrayList<>(); - printMap(subLines, 0, "Map", (Map) entry.getValue()); - value = String.join("\n", subLines); - } else if (entry.getValue() instanceof List) { - List subLines = new ArrayList<>(); - printList(subLines, 0, "List", (Collection) entry.getValue()); - value = String.join("\n", subLines); - } else { - value = String.valueOf(entry.getValue()); - } - - for (String line : value.split("\n")) { - if (line.length() > valueMaxLength) { - valueMaxLength = line.length(); - } - } - stringifiedValues.put(String.valueOf(entry.getKey()), value); - } - String separator = String.format("%s|-%-" + keyMaxLength + "s-+-%-" + valueMaxLength + "s-|", " ".repeat(depth), "-".repeat(keyMaxLength), "-".repeat(valueMaxLength)); - lines.add(separator); - for (Map.Entry entry : stringifiedValues.entrySet()) { - String value = entry.getValue(); - boolean firstLine = true; - for (String line : value.split("\n")) { - lines.add(String.format("%s| %-" + keyMaxLength + "s | %-" + valueMaxLength + "s |", " ".repeat(depth), firstLine ? entry.getKey() : "", line)); - firstLine = false; - } - lines.add(separator); - } - } - - private void error(String error) { - errors.add(error); - currentReportGenerator.sendMessage(ChatColor.RED + error); - } - - @Override - public List tabComplete(CommandSender sender, String[] args) { - return Collections.emptyList(); - } - - @Override - public @Nullable String getPermission() { - return "quests.admin"; - } -} diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugCommandSwitcher.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugCommandSwitcher.java new file mode 100644 index 00000000..5a7d0de5 --- /dev/null +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugCommandSwitcher.java @@ -0,0 +1,60 @@ +package com.leonardobishop.quests.bukkit.command; + +import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin; +import com.leonardobishop.quests.bukkit.item.QuestItem; +import com.leonardobishop.quests.bukkit.questcompleter.BukkitQuestCompleter; +import com.leonardobishop.quests.bukkit.util.LogHistory; +import com.leonardobishop.quests.common.config.ConfigProblem; +import com.leonardobishop.quests.common.player.QPlayer; +import com.leonardobishop.quests.common.player.QPlayerPreferences; +import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress; +import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile; +import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress; +import com.leonardobishop.quests.common.quest.Category; +import com.leonardobishop.quests.common.quest.Quest; +import com.leonardobishop.quests.common.quest.Task; +import com.leonardobishop.quests.common.tasktype.TaskType; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileAttribute; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.function.Function; + +public class AdminDebugCommandSwitcher extends CommandSwitcher { + + public AdminDebugCommandSwitcher(BukkitQuestsPlugin plugin) { + super(2); + + super.subcommands.put("quest", new AdminDebugQuestCommandHandler(plugin)); + super.subcommands.put("report", new AdminDebugReportCommandHandler(plugin)); + } + + @Override + public void showHelp(CommandSender sender) { + sender.sendMessage(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "------------=[" + ChatColor.RED + " Quests Admin: debug " + ChatColor + .GRAY.toString() + ChatColor.STRIKETHROUGH + "]=------------"); + sender.sendMessage(ChatColor.GRAY + "The following commands are available: "); + sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a debug report " + ChatColor.DARK_GRAY + + ": generate a debug report"); + sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a debug quest " + ChatColor.DARK_GRAY + + ": enable debug logging for a specific quest"); + } + + @Override + public @Nullable String getPermission() { + return "quests.admin"; + } +} diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugQuestCommandHandler.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugQuestCommandHandler.java new file mode 100644 index 00000000..dc7b8178 --- /dev/null +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugQuestCommandHandler.java @@ -0,0 +1,101 @@ +package com.leonardobishop.quests.bukkit.command; + +import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin; +import com.leonardobishop.quests.bukkit.item.QuestItem; +import com.leonardobishop.quests.bukkit.questcompleter.BukkitQuestCompleter; +import com.leonardobishop.quests.bukkit.util.LogHistory; +import com.leonardobishop.quests.common.config.ConfigProblem; +import com.leonardobishop.quests.common.player.QPlayer; +import com.leonardobishop.quests.common.player.QPlayerPreferences; +import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress; +import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile; +import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress; +import com.leonardobishop.quests.common.quest.Category; +import com.leonardobishop.quests.common.quest.Quest; +import com.leonardobishop.quests.common.quest.Task; +import com.leonardobishop.quests.common.tasktype.TaskType; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.function.Function; + +public class AdminDebugQuestCommandHandler implements CommandHandler { + + private final BukkitQuestsPlugin plugin; + + + public AdminDebugQuestCommandHandler(BukkitQuestsPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void handle(CommandSender sender, String[] args) { + if (sender instanceof Player player) { + QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId()); + if (qPlayer == null) { + sender.sendMessage(ChatColor.RED + "Your data file is not loaded."); + return; + } + + String questId = args[3]; + Quest quest = plugin.getQuestManager().getQuestById(questId); + if (quest == null) { + sender.sendMessage(ChatColor.RED + "Quest " + questId + " does not exist."); + return; + } + + QPlayerPreferences preferences = qPlayer.getPlayerPreferences(); + QPlayerPreferences.DebugType currentDebugType = preferences.getDebug(questId); + if (currentDebugType == null) { + String debugType = args[4]; + QPlayerPreferences.DebugType debugTypeEnum; + + try { + debugTypeEnum = QPlayerPreferences.DebugType.valueOf(debugType.toUpperCase()); + } catch (IllegalArgumentException e) { + sender.sendMessage(ChatColor.RED + "Invalid debug type."); + return; + } + + preferences.setDebug(questId, debugTypeEnum); + sender.sendMessage(ChatColor.GREEN + "Debugging enabled for quest '" + questId + "'."); + sender.sendMessage(ChatColor.GRAY + "You will now see debug logs for quest '" + quest + "' for " + + (debugTypeEnum == QPlayerPreferences.DebugType.SELF ? "yourself" : "everybody on the server") + + ". This may generate a lot of spam."); + sender.sendMessage(ChatColor.DARK_GRAY + "Use '/quests admin debug " + questId + "' to disable."); + } else { + preferences.setDebug(questId, null); + sender.sendMessage(ChatColor.GREEN + "Debugging disabled for quest '" + questId + "'."); + } + + } else { + sender.sendMessage(ChatColor.RED + "You must be a player to use this command."); + } + } + + @Override + public List tabComplete(CommandSender sender, String[] args) { + if (args.length == 4) { + return TabHelper.tabCompleteQuests(args[3]); + } else if (args.length == 5) { + return TabHelper.matchTabComplete(args[2], Arrays.asList("self", "all")); + } + return Collections.emptyList(); + } + + @Override + public @Nullable String getPermission() { + return "quests.admin"; + } +} diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugReportCommandHandler.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugReportCommandHandler.java new file mode 100644 index 00000000..0533e9dd --- /dev/null +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/AdminDebugReportCommandHandler.java @@ -0,0 +1,386 @@ +package com.leonardobishop.quests.bukkit.command; + +import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin; +import com.leonardobishop.quests.bukkit.item.QuestItem; +import com.leonardobishop.quests.bukkit.questcompleter.BukkitQuestCompleter; +import com.leonardobishop.quests.bukkit.util.LogHistory; +import com.leonardobishop.quests.common.config.ConfigProblem; +import com.leonardobishop.quests.common.player.QPlayer; +import com.leonardobishop.quests.common.player.QPlayerPreferences; +import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress; +import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile; +import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress; +import com.leonardobishop.quests.common.quest.Category; +import com.leonardobishop.quests.common.quest.Quest; +import com.leonardobishop.quests.common.quest.Task; +import com.leonardobishop.quests.common.tasktype.TaskType; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.function.Function; + +public class AdminDebugReportCommandHandler implements CommandHandler { + + private final BukkitQuestsPlugin plugin; + + private List errors; + private CommandSender currentReportGenerator; + + public AdminDebugReportCommandHandler(BukkitQuestsPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void handle(CommandSender sender, String[] args) { + if (currentReportGenerator != null) { + sender.sendMessage(ChatColor.RED + "A report is already being generated. Please wait until it is finished."); + return; + } + + errors = new ArrayList<>(); + currentReportGenerator = sender; + + try { + sender.sendMessage(ChatColor.GRAY + "Generating Quests debug report..."); + if (!plugin.getLogHistory().isEnabled()) { + sender.sendMessage(ChatColor.RED + "Log history is not enabled. Quests will only generate a basic debug report. " + + "If you were asked to generate one with log history, this must be turned on in your configuration."); + } + long start = System.currentTimeMillis(); + Path path = new File(plugin.getDataFolder() + File.separator + "debug" + File.separator + "debug_" + start + ".txt").toPath(); + try { + Path directory = new File(plugin.getDataFolder() + File.separator + "debug").toPath(); + Files.createDirectories(directory); + Files.createFile(path); + } catch (IOException e) { + sender.sendMessage(ChatColor.DARK_RED + "Failed to create debug file!"); + e.printStackTrace(); + return; + } + + List lines = new ArrayList<>(); + lines.add("################################"); + lines.add("# Server Information #"); + lines.add("################################"); + lines.add(""); + lines.add("Server name: " + plugin.getServer().getName()); + lines.add("Server version: " + plugin.getServer().getVersion()); + lines.add("Bukkit version: " + plugin.getServer().getBukkitVersion()); + lines.add("Minecraft version: " + plugin.getServer().getMinecraftVersion()); + lines.add("Player count: " + plugin.getServer().getOnlinePlayers().size()); + lines.add(""); + + lines.add("################################"); + lines.add("# Quests Information #"); + lines.add("################################"); + lines.add(""); + lines.add("Quests version: " + plugin.getDescription().getVersion()); + lines.add("Valid configuration: " + plugin.isValidConfiguration()); + lines.add(""); + printList(lines, 0, "Task types available", plugin.getTaskTypeManager().getTaskTypes(), TaskType::getType); + lines.add(""); + printList(lines, 0, "Quests", plugin.getQuestManager().getQuests().values(), Quest::getId); + lines.add(""); + printList(lines, 0, "Categories", plugin.getQuestManager().getCategories(), Category::getId); + lines.add(""); + BukkitQuestCompleter completer = (BukkitQuestCompleter) plugin.getQuestCompleter(); + printList(lines, 0, "Completion queue", completer.getCompletionQueue(), questProgress -> questProgress.getPlayer().toString()); + lines.add(""); + printList(lines, 0, "Full check queue", completer.getFullCheckQueue(), questProgressFile -> questProgressFile.getPlayerUUID().toString()); + lines.add(""); + printList(lines, 0, "Expired check queue", completer.getExpiredCheckQueue(), UUID::toString); + lines.add(""); + + lines.add("################################"); + lines.add("# Storage #"); + lines.add("################################"); + lines.add(""); + lines.add("Storage provider: " + plugin.getStorageProvider().getName()); + lines.add(""); + + lines.add("################################"); + lines.add("# Hook #"); + lines.add("################################"); + lines.add(""); + lines.add("Core protect hook: " + (plugin.getCoreProtectHook() != null)); + lines.add("Essentials hook: " + (plugin.getEssentialsHook() != null)); + lines.add("PlaceholderAPI hook: " + (plugin.getPlaceholderAPIHook() != null)); + lines.add("Item getter: " + plugin.getItemGetter().getClass().getSimpleName()); + lines.add("Title handler: " + plugin.getTitleHandle().getClass().getSimpleName()); + lines.add("Version specific handler: " + plugin.getVersionSpecificHandler().getClass().getSimpleName()); + lines.add(""); + + lines.add("################################"); + lines.add("# Options #"); + lines.add("################################"); + lines.add(""); + if (plugin.isValidConfiguration()) { + lines.add("GUI use placeholder API: " + plugin.getQuestsConfig().getBoolean("options.gui-use-placeholderapi", false)); + lines.add("Quests use placeholder API: " + plugin.getQuestsConfig().getBoolean("options.quests-use-placeholderapi", false)); + lines.add("Quests autostart: " + plugin.getQuestsConfig().getBoolean("options.quest-autostart", false)); + lines.add("Quests autotrack: " + plugin.getQuestsConfig().getBoolean("options.quest-autotrack", true)); + lines.add("Verify quests exist on load: " + plugin.getQuestsConfig().getBoolean("options.verify-quest-exists-on-load", true)); + lines.add("Queue executor interval: " + plugin.getQuestsConfig().getInt("options.performance-tweaking.quest-queue-executor-interval", 1) + " ticks"); + lines.add("Autosave interval: " + plugin.getQuestsConfig().getInt("options.performance-tweaking.quest-autosave-interval", 12000) + " ticks"); + lines.add("Override errors: " + plugin.getQuestsConfig().getBoolean("options.error-checking.override-errors", false)); + lines.add("Placeholder cache time: " + plugin.getQuestsConfig().getInt("options.placeholder-cache-time") + " seconds"); + lines.add("Quest mode: " + plugin.getQuestsConfig().getInt("quest-mode.mode")); + } else { + lines.add("Configuration unavailable."); + } + lines.add(""); + + lines.add("################################"); + lines.add("# Items #"); + lines.add("################################"); + lines.add(""); + lines.add("Number of items: " + plugin.getQuestItemRegistry().getAllItems().size()); + lines.add(""); + for (QuestItem questItem : plugin.getQuestItemRegistry().getAllItems()) { + Map values = getFieldValues(questItem.getClass(), questItem); + values.putAll(getFieldValues(questItem.getClass().getSuperclass(), questItem)); + printMap(lines, 0, "Item " + questItem.getId() + " (" + questItem.getClass().getSimpleName() + ")", values); + lines.add(""); + } + + lines.add("################################"); + lines.add("# Configuration Problems #"); + lines.add("################################"); + lines.add(""); + lines.add("Number of problems: " + plugin.getConfigProblems().size()); + lines.add(""); + for (Map.Entry> entry : plugin.getConfigProblems().entrySet()) { + String id = entry.getKey(); + List problems = entry.getValue(); + + printList(lines, 0, "Problems for '" + id + "'", problems, + (ConfigProblem problem) -> String.format("%s: %s (:%s)", problem.getType(), problem.getDescription(), problem.getLocation())); + lines.add(""); + } + + lines.add("################################"); + lines.add("# Quests #"); + lines.add("################################"); + lines.add(""); + lines.add("Number of quests: " + plugin.getQuestManager().getQuests().size()); + lines.add(""); + for (Quest quest : plugin.getQuestManager().getQuests().values()) { + Map questValues = getFieldValues(quest.getClass(), quest, "tasks", "tasksByType"); + try { + Field tasksField = quest.getClass().getDeclaredField("tasks"); + tasksField.setAccessible(true); + Map tasksMap = (Map) tasksField.get(quest); + Map tasksValues = new HashMap<>(); + for (Map.Entry taskEntry : tasksMap.entrySet()) { + Task task = taskEntry.getValue(); + tasksValues.put(task.getId(), getFieldValues(task.getClass(), task)); + } + questValues.put("tasks", tasksValues); + } catch (NoSuchFieldException | IllegalAccessException e) { + error("Failed to get tasks for quest " + quest.getId() + ": " + e.getClass().getSimpleName() + "(" + e.getMessage() + ")"); + e.printStackTrace(); + } + + printMap(lines, 0, "Quest '" + quest.getId() + "'", questValues); + lines.add(""); + } + + lines.add("################################"); + lines.add("# Players #"); + lines.add("################################"); + lines.add(""); + printList(lines, 0, "Players online", Bukkit.getOnlinePlayers(), player -> player.getUniqueId().toString()); + lines.add(""); + printList(lines, 0, "QPlayers loaded", plugin.getPlayerManager().getQPlayers(), qPlayer -> qPlayer.getPlayerUUID().toString()); + lines.add(""); + for (QPlayer qPlayer : plugin.getPlayerManager().getQPlayers()) { + lines.add("QPlayer " + qPlayer.getPlayerUUID() + ":"); + QPlayerPreferences preferences = qPlayer.getPlayerPreferences(); + printMap(lines, 1, "Preferences", getFieldValues(preferences.getClass(), preferences)); + + QuestProgressFile questProgressFile = qPlayer.getQuestProgressFile(); + try { + Field questProgressField = questProgressFile.getClass().getDeclaredField("questProgress"); + questProgressField.setAccessible(true); + Map questProgressMap = (Map) questProgressField.get(questProgressFile); + Map questProgressValues = new LinkedHashMap<>(); + for (Map.Entry entry : questProgressMap.entrySet()) { + QuestProgress questProgress = entry.getValue(); + Map questProgressValue = getFieldValues(questProgress.getClass(), questProgress, "plugin", "taskProgress"); + + Field taskProgressField = questProgress.getClass().getDeclaredField("taskProgress"); + taskProgressField.setAccessible(true); + Map taskProgressMap = (Map) taskProgressField.get(questProgress); + Map taskProgressValues = new LinkedHashMap<>(); + for (Map.Entry taskEntry : taskProgressMap.entrySet()) { + TaskProgress taskProgress = taskEntry.getValue(); + taskProgressValues.put(taskEntry.getKey(), getFieldValues(taskProgress.getClass(), taskProgress, "plugin", "linkedQuestProgress")); + } + questProgressValue.put("taskProgress", taskProgressValues); + + questProgressValues.put(entry.getKey(), questProgressValue); + } + printMap(lines, 1, "Quest progress", questProgressValues); + } catch (NoSuchFieldException | IllegalAccessException e) { + error("Failed to get quest progress for QPlayer " + qPlayer.getPlayerUUID() + ": " + e.getClass().getSimpleName() + "(" + e.getMessage() + ")"); + e.printStackTrace(); + } + lines.add(" Quest controller: " + qPlayer.getQuestController().getName()); + lines.add(""); + } + + if (plugin.getLogHistory().isEnabled()) { + lines.add("################################"); + lines.add("# Log History #"); + lines.add("################################"); + lines.add(""); + int timeMaxLength = 1; + int typeMaxLength = 1; + int threadMaxLength = 1; + for (LogHistory.LogEntry line : plugin.getLogHistory().getEntries()) { + timeMaxLength = Math.max(timeMaxLength, String.valueOf(line.getTime()).length()); + typeMaxLength = Math.max(typeMaxLength, line.getType().toString().length()); + threadMaxLength = Math.max(threadMaxLength, line.getThread().length()); + } + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + for (LogHistory.LogEntry line : plugin.getLogHistory().getEntries()) { + lines.add(String.format("%-" + timeMaxLength + "s %-" + typeMaxLength + "s %-" + threadMaxLength + "s | %s", + dateFormat.format(new Date(line.getTime())), line.getType().toString(), line.getThread(), line.getEntry())); + } + } + + List errors = new ArrayList<>(); + lines.add(0, ""); + lines.add(0, "Log history: " + plugin.getLogHistory().isEnabled()); + printList(errors, 0, "Errors generating report", this.errors, String::valueOf); + lines.addAll(0, errors); + lines.add(0, "Time taken: " + (System.currentTimeMillis() - start) + "ms"); + lines.add(0, "Generated at: " + new Date(start) + " (" + start + ")"); + lines.add(0, ""); + lines.add(0, "################################"); + lines.add(0, "# Report Information #"); + lines.add(0, "################################"); + try { + Files.write(path, lines, StandardCharsets.UTF_8); + } catch (IOException e) { + error("Failed to write report to " + path + "!"); + e.printStackTrace(); + return; + } + + sender.sendMessage(ChatColor.GREEN + "Debug file created at " + path + ". (Took " + (System.currentTimeMillis() - start) + "ms.)"); + } finally { + currentReportGenerator = null; + errors = new ArrayList<>(); + } + } + + private Map getFieldValues(Class clazz, Object object, String... excludeFields) { + Field[] fields = clazz.getDeclaredFields(); + Map values = new LinkedHashMap<>(); + for (Field field : fields) { + if (Arrays.asList(excludeFields).contains(field.getName())) { + continue; + } + try { + field.setAccessible(true); + values.put(field.getName(), field.get(object)); + } catch (IllegalAccessException e) { + error("Failed to get field value for " + clazz.getSimpleName() + "." + field.getName() + ": " + e.getClass().getSimpleName() + "(" + e.getMessage() + ")"); + e.printStackTrace(); + } + } + return values; + } + + private void printList(List lines, int depth, String title, Collection list) { + printList(lines, depth, title, list, Object::toString); + } + + private void printList(List lines, int depth, String title, Collection list, Function getter) { + depth = depth * 4; + if (list.size() == 0) { + lines.add(String.format("%s%s (0): (empty)", " ".repeat(depth), title)); + return; + } + lines.add(String.format("%s%s (%d):", " ".repeat(depth), title, list.size())); + for (E element : list) { + lines.add(String.format("%s - %s", " ".repeat(depth), getter.apply(element))); + } + } + + private void printMap(List lines, int depth, String title, Map map) { + depth = depth * 4; + if (map.size() == 0) { + lines.add(String.format("%s%s (0): (empty)", " ".repeat(depth), title)); + return; + } + lines.add(String.format("%s%s:", " ".repeat(depth), title)); + int keyMaxLength = 1; + int valueMaxLength = 1; + Map stringifiedValues = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + String key = String.valueOf(entry.getKey()); + if (key.length() > keyMaxLength) { + keyMaxLength = key.length(); + } + } + for (Map.Entry entry : map.entrySet()) { + String value; + if (entry.getValue() instanceof Map) { + List subLines = new ArrayList<>(); + printMap(subLines, 0, "Map", (Map) entry.getValue()); + value = String.join("\n", subLines); + } else if (entry.getValue() instanceof List) { + List subLines = new ArrayList<>(); + printList(subLines, 0, "List", (Collection) entry.getValue()); + value = String.join("\n", subLines); + } else { + value = String.valueOf(entry.getValue()); + } + + for (String line : value.split("\n")) { + if (line.length() > valueMaxLength) { + valueMaxLength = line.length(); + } + } + stringifiedValues.put(String.valueOf(entry.getKey()), value); + } + String separator = String.format("%s|-%-" + keyMaxLength + "s-+-%-" + valueMaxLength + "s-|", " ".repeat(depth), "-".repeat(keyMaxLength), "-".repeat(valueMaxLength)); + lines.add(separator); + for (Map.Entry entry : stringifiedValues.entrySet()) { + String value = entry.getValue(); + boolean firstLine = true; + for (String line : value.split("\n")) { + lines.add(String.format("%s| %-" + keyMaxLength + "s | %-" + valueMaxLength + "s |", " ".repeat(depth), firstLine ? entry.getKey() : "", line)); + firstLine = false; + } + lines.add(separator); + } + } + + private void error(String error) { + errors.add(error); + currentReportGenerator.sendMessage(ChatColor.RED + error); + } + + @Override + public List tabComplete(CommandSender sender, String[] args) { + return Collections.emptyList(); + } + + @Override + public @Nullable String getPermission() { + return "quests.admin"; + } +} diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskType.java index 681658de..b187b794 100644 --- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskType.java +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskType.java @@ -4,8 +4,12 @@ import com.leonardobishop.quests.common.tasktype.TaskType; import org.bukkit.event.Listener; import org.jetbrains.annotations.NotNull; +import java.util.UUID; + public abstract class BukkitTaskType extends TaskType implements Listener { + protected BukkitTaskTypeManager taskTypeManager; + public BukkitTaskType(@NotNull String type, String author, String description) { super(type, author, description); } @@ -14,4 +18,8 @@ public abstract class BukkitTaskType extends TaskType implements Listener { super(type); } + public final void debug(@NotNull String message, String questId, @NotNull UUID player) { + taskTypeManager.sendDebug(message, super.getType(), questId, player); + } + } diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskTypeManager.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskTypeManager.java index 91e511c8..46ad24ae 100644 --- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskTypeManager.java +++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskTypeManager.java @@ -1,11 +1,16 @@ package com.leonardobishop.quests.bukkit.tasktype; import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin; +import com.leonardobishop.quests.common.player.QPlayer; +import com.leonardobishop.quests.common.player.QPlayerPreferences; import com.leonardobishop.quests.common.tasktype.TaskType; import com.leonardobishop.quests.common.tasktype.TaskTypeManager; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import java.util.List; +import java.util.UUID; public class BukkitTaskTypeManager extends TaskTypeManager { @@ -22,14 +27,35 @@ public class BukkitTaskTypeManager extends TaskTypeManager { @Override public boolean registerTaskType(@NotNull TaskType taskType) { - if (!(taskType instanceof BukkitTaskType)) throw new RuntimeException("BukkitTaskTypeManager implementation can only accept instances of BukkitTaskType!"); + if (!(taskType instanceof BukkitTaskType bukkitTaskType)) throw new RuntimeException("BukkitTaskTypeManager implementation can only accept instances of BukkitTaskType!"); - BukkitTaskType bukkitTaskType = (BukkitTaskType) taskType; if (super.registerTaskType(taskType)) { + bukkitTaskType.taskTypeManager = this; plugin.getServer().getPluginManager().registerEvents(bukkitTaskType, plugin); return true; } return false; } + public void sendDebug(@NotNull String message, @NotNull String taskType, @NotNull String questId, @NotNull UUID associatedPlayer) { + for (QPlayer qPlayer : plugin.getPlayerManager().getQPlayers()) { + QPlayerPreferences.DebugType debugType = qPlayer.getPlayerPreferences().getDebug(questId); + Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID()); + Player otherPlayer = Bukkit.getPlayer(associatedPlayer); + String associatedName = otherPlayer == null ? associatedPlayer.toString() : otherPlayer.getName(); + + String chatMessage = "[" + taskType + ": " + associatedName + " on '" + questId + "'] " + message; + if (player != null && debugType != null) { + switch (debugType) { + case ALL -> player.sendMessage(chatMessage); + case SELF -> { + if (player.getUniqueId().equals(associatedPlayer)) { + player.sendMessage(chatMessage); + } + } + } + } + } + } + } -- cgit v1.2.3-70-g09d2