diff --git a/src/main/kotlin/org/server_info/mOTD/MOTD.kt b/src/main/kotlin/org/server_info/mOTD/MOTD.kt index 7bf6170..3eba9c5 100644 --- a/src/main/kotlin/org/server_info/mOTD/MOTD.kt +++ b/src/main/kotlin/org/server_info/mOTD/MOTD.kt @@ -1,244 +1,259 @@ package org.server_info.MOTD +import MOTDGuiHandler import org.bukkit.Bukkit +import org.bukkit.ChatColor +import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.plugin.java.JavaPlugin -import org.bukkit.configuration.ConfigurationSection +import java.io.File +import java.util.* class MOTD : JavaPlugin() { - private var quotesTask: Int = -1 - private val quotesList = ArrayList() - // Quote data class to store text and color + private val quotes = ArrayList() + private var currentQuoteIndex = 0 + private var quoteTaskId = -1 + data class QuoteItem(val text: String, val color: String) override fun onEnable() { - // Save default config if it doesn't exist + // Create default config if it doesn't exist saveDefaultConfig() + // Initialize quotes system + initializeQuotes() + + // Register command executor + getCommand("motd")?.setExecutor(MOTDCommand(this)) + + // Register GUI handler + val guiHandler = MOTDGuiHandler(this) + server.pluginManager.registerEvents(guiHandler, this) + + logger.info("MOTD Plugin enabled!") + } + + override fun onDisable() { + // Cancel task if running + if (quoteTaskId != -1) { + Bukkit.getScheduler().cancelTask(quoteTaskId) + } + + // Save quotes on shutdown + saveQuotes() + + logger.info("MOTD Plugin disabled!") + } + + // Initialize quotes system + fun initializeQuotes() { + // Make sure the data folder exists + if (!dataFolder.exists()) { + dataFolder.mkdirs() + } + // Load quotes from config loadQuotes() - // Register commands - getCommand("motd")?.setExecutor(MOTDCommand(this)) - - // Register GUI handler - server.pluginManager.registerEvents(MOTDGuiHandler(this), this) - - // Schedule the MOTD update task + // Start the task to change quotes startQuoteTask() - - logger.info("MOTD Quote Plugin enabled! Loaded ${quotesList.size} quotes.") } - override fun onDisable() { - // Cancel the task when the plugin is disabled - if (quotesTask != -1) { - Bukkit.getScheduler().cancelTask(quotesTask) - } - logger.info("MOTD Quote Plugin disabled!") - } + // Save quotes to config + fun saveQuotes() { + try { + val config = YamlConfiguration() + val quotesList = ArrayList>() - private fun loadQuotes() { - // Clear existing quotes - quotesList.clear() - - // Check if we have the new quote format - if (config.contains("quotes") && config.isList("quotes")) { - // Check if it's the old format (simple string list) or new format (list of maps) - val quotesSection = config.getList("quotes") - if (quotesSection != null && quotesSection.isNotEmpty()) { - if (quotesSection[0] is String) { - // Old format - migrate - migrateOldQuotesFormat() - } else { - // New format - load quotes with colors - for (i in 0 until config.getList("quotes")!!.size) { - val quoteSection = config.getConfigurationSection("quotes.$i") - if (quoteSection != null) { - val text = quoteSection.getString("text") ?: continue - val color = quoteSection.getString("color") ?: "§f" // Default to white - quotesList.add(QuoteItem(text, color)) - } - } - } + for (quote in quotes) { + val quoteMap = HashMap() + quoteMap["text"] = quote.text + quoteMap["color"] = quote.color.replace('§', '&') // Store with & instead of § for readability + quotesList.add(quoteMap) } - } else { - // Create default quotes - createDefaultQuotes() - } - // If still empty after loading, create defaults - if (quotesList.isEmpty()) { - createDefaultQuotes() + config.set("quotes", quotesList) + + // Save to file + val file = File(dataFolder, "quotes.yml") + config.save(file) + + // Update the main config to point to this file + val mainConfig = YamlConfiguration.loadConfiguration(File(dataFolder, "config.yml")) + mainConfig.set("quotes_file", "quotes.yml") + mainConfig.save(File(dataFolder, "config.yml")) + + logger.info("Saved ${quotes.size} quotes to quotes.yml") + } catch (e: Exception) { + logger.severe("Failed to save quotes: ${e.message}") + e.printStackTrace() } } - private fun migrateOldQuotesFormat() { - val oldQuotes = config.getStringList("quotes") - val newQuotesList = ArrayList>() + // Load quotes from config + fun loadQuotes() { + try { + val configFile = File(dataFolder, "config.yml") + if (!configFile.exists()) { + saveDefaultConfig() + } - for (quote in oldQuotes) { - newQuotesList.add(mapOf("text" to quote, "color" to "§f")) + val config = YamlConfiguration.loadConfiguration(configFile) + val quotesFile = config.getString("quotes_file", "quotes.yml") + + val quotesConfig = File(dataFolder, quotesFile) + if (!quotesConfig.exists()) { + // Create default quotes file if it doesn't exist + val defaultQuotes = ArrayList>() + val defaultQuote = HashMap() + defaultQuote["text"] = "Welcome to our server!" + defaultQuote["color"] = "&e" + defaultQuotes.add(defaultQuote) + + val tempConfig = YamlConfiguration() + tempConfig.set("quotes", defaultQuotes) + tempConfig.save(quotesConfig) + } + + // Load quotes + val quotesYaml = YamlConfiguration.loadConfiguration(quotesConfig) + val quotesList = quotesYaml.getMapList("quotes") + + quotes.clear() + + for (quoteMap in quotesList) { + @Suppress("UNCHECKED_CAST") + val map = quoteMap as? Map ?: continue + val text = map["text"] ?: continue + val color = (map["color"] ?: "&f").replace('&', '§') + quotes.add(QuoteItem(text, color)) + } + + logger.info("Loaded ${quotes.size} quotes from $quotesFile") + } catch (e: Exception) { + logger.severe("Failed to load quotes: ${e.message}") + e.printStackTrace() + + // Create a default quote if loading fails + if (quotes.isEmpty()) { + quotes.add(QuoteItem("Welcome to our server!", "§e")) + } } - - // Save in new format - config.set("quotes", newQuotesList) - saveConfig() - - // Load the quotes - for (quoteMap in newQuotesList) { - quotesList.add(QuoteItem(quoteMap["text"] ?: "", quoteMap["color"] ?: "§f")) - } - - logger.info("Migrated ${oldQuotes.size} quotes to the new format with colors.") - } - - private fun createDefaultQuotes() { - val defaultQuotes = listOf( - QuoteItem("Welcome to our Minecraft server!", "§e"), - QuoteItem("Have fun and be respectful to others!", "§a"), - QuoteItem("Check out our website for server rules.", "§b") - ) - - quotesList.addAll(defaultQuotes) - - // Save default quotes to config - saveQuotesToConfig() - - logger.info("No quotes found in config, created default quotes list.") } + // Start the task to change quotes periodically fun startQuoteTask() { // Cancel existing task if running - if (quotesTask != -1) { - Bukkit.getScheduler().cancelTask(quotesTask) + if (quoteTaskId != -1) { + Bukkit.getScheduler().cancelTask(quoteTaskId) + quoteTaskId = -1 } - // Get update interval from config (default: 60 minutes) - val intervalMinutes = config.getLong("update-interval-minutes", 60) + // Get update interval from config (in minutes) + val intervalMinutes = config.getInt("update_interval", 15) + val intervalTicks = intervalMinutes * 60 * 20L // Convert to ticks - // Schedule repeating task - quotesTask = Bukkit.getScheduler().scheduleSyncRepeatingTask( - this, - { updateServerMOTD() }, - 20L, // Initial delay (1 second) - intervalMinutes * 60 * 20L // Convert minutes to ticks (20 ticks = 1 second) - ) - - logger.info("MOTD update task scheduled to run every $intervalMinutes minutes.") - } - - fun updateServerMOTD() { - if (quotesList.isEmpty()) { - logger.warning("No quotes available to set MOTD!") + if (intervalMinutes <= 0) { + logger.info("Automatic MOTD updates disabled (interval set to 0)") return } - // Get a random quote - val quoteItem = getRandomQuote() + quoteTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(this, { + updateServerMOTD() + }, intervalTicks, intervalTicks) - // Format the quote with its specific color - val formattedQuote = formatMOTD(quoteItem) - - // Set the server MOTD - val server = Bukkit.getServer() - server.motd = formattedQuote - - logger.info("MOTD updated: $formattedQuote") + logger.info("MOTD will update every $intervalMinutes minutes") } - fun getRandomQuote(): QuoteItem { - return if (quotesList.isNotEmpty()) { - quotesList.random() + // Update the server MOTD with the next quote + fun updateServerMOTD() { + if (quotes.isEmpty()) { + logger.warning("No quotes available for MOTD") + return + } + + // Select next quote or random based on config + val useRandom = config.getBoolean("random_quotes", true) + if (useRandom) { + currentQuoteIndex = Random().nextInt(quotes.size) } else { - QuoteItem("No quotes available", "§c") + currentQuoteIndex = (currentQuoteIndex + 1) % quotes.size + } + + val quote = quotes[currentQuoteIndex] + + // Get the static part of the MOTD from config + val staticPart = ChatColor.translateAlternateColorCodes('&', + config.getString("static_motd", "&6Crossplay Java & Bedrock") ?: "&6Crossplay Java & Bedrock!") + + // Combine static part with quote + val fullMotd = "$staticPart\n${quote.color}${quote.text}" + + try { + // Update the server MOTD + val server = Bukkit.getServer() + val serverClass = server.javaClass + + // Attempt to access Spigot/Paper setMotd method first + try { + val motdMethod = serverClass.getMethod("setMotd", String::class.java) + motdMethod.invoke(server, fullMotd) + logger.info("Updated MOTD: $fullMotd") + return + } catch (e: NoSuchMethodException) { + // Method not found, try other ways + } + + // Try to find the method in a different way for different server implementations + for (method in serverClass.methods) { + if (method.name.equals("setMotd", ignoreCase = true) && method.parameterCount == 1) { + method.invoke(server, fullMotd) + logger.info("Updated MOTD: $fullMotd") + return + } + } + + // If we get here, warn that the MOTD couldn't be updated + logger.warning("Could not update server MOTD. Server implementation not supported.") + + } catch (e: Exception) { + logger.severe("Error updating server MOTD: ${e.message}") + e.printStackTrace() } } - private fun formatMOTD(quoteItem: QuoteItem): String { - // Get prefix and suffix from config - val prefix = config.getString("motd-prefix", "") ?: "" - val suffix = config.getString("motd-suffix", "") ?: "" - - // Apply the quote's specific color - val coloredQuote = quoteItem.color + quoteItem.text - - // Trim and limit the quote length if it's too long - val maxLength = config.getInt("max-motd-length", 50) - var trimmedQuote = coloredQuote - if (trimmedQuote.length > maxLength) { - trimmedQuote = trimmedQuote.substring(0, maxLength - 3) + "..." - } - - return prefix + trimmedQuote + suffix - } - - fun reloadQuotes() { - reloadConfig() - loadQuotes() - } - + // Get the current list of quotes fun getCurrentQuotes(): List { - return quotesList.toList() + return quotes.toList() } + // Add a new quote fun addQuote(text: String, color: String): Boolean { if (text.isBlank()) return false - // Validate color code - val validColor = if (color.startsWith("§") && color.length == 2) { - color - } else { - "§f" // Default to white if invalid - } - - // Add to internal list - quotesList.add(QuoteItem(text, validColor)) - - // Save to config - saveQuotesToConfig() - + quotes.add(QuoteItem(text, color)) return true } + // Remove a quote by index fun removeQuote(index: Int): Boolean { - if (index < 0 || index >= quotesList.size) return false - - quotesList.removeAt(index) - saveQuotesToConfig() + if (index < 0 || index >= quotes.size) return false + quotes.removeAt(index) return true } + // Edit an existing quote fun editQuote(index: Int, text: String, color: String): Boolean { - if (index < 0 || index >= quotesList.size || text.isBlank()) return false - - // Validate color code - val validColor = if (color.startsWith("§") && color.length == 2) { - color - } else { - "§f" // Default to white if invalid - } - - quotesList[index] = QuoteItem(text, validColor) - saveQuotesToConfig() + if (index < 0 || index >= quotes.size || text.isBlank()) return false + quotes[index] = QuoteItem(text, color) return true } - private fun saveQuotesToConfig() { - // Convert quotes to format that can be saved in config - val quotesToSave = ArrayList>() - - for (quote in quotesList) { - quotesToSave.add(mapOf( - "text" to quote.text, - "color" to quote.color - )) - } - - // Save to config - config.set("quotes", quotesToSave) - saveConfig() + // Reload quotes from config + fun reloadQuotes() { + loadQuotes() + updateServerMOTD() // Update the MOTD immediately } } \ No newline at end of file diff --git a/src/main/kotlin/org/server_info/mOTD/MOTDGuiHandler.kt b/src/main/kotlin/org/server_info/mOTD/MOTDGuiHandler.kt index de54bc8..890e22c 100644 --- a/src/main/kotlin/org/server_info/mOTD/MOTDGuiHandler.kt +++ b/src/main/kotlin/org/server_info/mOTD/MOTDGuiHandler.kt @@ -1,5 +1,3 @@ -package org.server_info.MOTD - import org.bukkit.Bukkit import org.bukkit.ChatColor import org.bukkit.Material @@ -9,7 +7,7 @@ import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.InventoryCloseEvent import org.bukkit.inventory.Inventory import org.bukkit.inventory.ItemStack -import org.bukkit.inventory.meta.ItemMeta +import org.server_info.MOTD.MOTD import java.util.* import org.bukkit.conversations.* import org.bukkit.entity.Player @@ -67,6 +65,13 @@ class MOTDGuiHandler(private val plugin: MOTD) : Listener { refreshButton.itemMeta = refreshMeta } + val saveButton = ItemStack(Material.DIAMOND_BLOCK) // New save button + val saveMeta = saveButton.itemMeta + if (saveMeta != null) { + saveMeta.setDisplayName("${ChatColor.BLUE}Save Quotes") + saveButton.itemMeta = saveMeta + } + val exitButton = ItemStack(Material.REDSTONE_BLOCK) val exitMeta = exitButton.itemMeta if (exitMeta != null) { @@ -76,9 +81,10 @@ class MOTDGuiHandler(private val plugin: MOTD) : Listener { // Set buttons in the last row val lastRowStart = inventorySize - 9 - inventory.setItem(lastRowStart + 2, addButton) - inventory.setItem(lastRowStart + 4, refreshButton) - inventory.setItem(lastRowStart + 6, exitButton) + inventory.setItem(lastRowStart + 1, addButton) + inventory.setItem(lastRowStart + 3, refreshButton) + inventory.setItem(lastRowStart + 5, saveButton) // Add new save button + inventory.setItem(lastRowStart + 7, exitButton) player.openInventory(inventory) } @@ -149,14 +155,22 @@ class MOTDGuiHandler(private val plugin: MOTD) : Listener { @EventHandler fun onInventoryClick(event: InventoryClickEvent) { val player = event.whoClicked as? Player ?: return - val clickedInventory = event.clickedInventory ?: return val title = event.view.title // Prevent moving items if (title == "MOTD Quotes Manager" || title == "Select Quote Color") { event.isCancelled = true + + // Check if clicked in the player inventory area + if (event.clickedInventory == player.inventory) { + return + } + } else { + return } + val item = event.currentItem ?: return + when (title) { "MOTD Quotes Manager" -> { handleMainGuiClick(event, player) @@ -170,30 +184,43 @@ class MOTDGuiHandler(private val plugin: MOTD) : Listener { private fun handleMainGuiClick(event: InventoryClickEvent, player: Player) { val item = event.currentItem ?: return - if (item.type == Material.PAPER) { - // Clicked on a quote - val slotIndex = event.slot + when (item.type) { + Material.PAPER -> { + // Clicked on a quote + val slotIndex = event.slot - if (event.isShiftClick) { - // Delete the quote - if (plugin.removeQuote(slotIndex)) { - player.sendMessage("${ChatColor.GREEN}Quote #${slotIndex + 1} has been deleted.") - openQuotesGui(player) // Refresh the GUI + if (event.isShiftClick) { + // Delete the quote + if (plugin.removeQuote(slotIndex)) { + player.sendMessage("${ChatColor.GREEN}Quote #${slotIndex + 1} has been deleted.") + openQuotesGui(player) // Refresh the GUI + } else { + player.sendMessage("${ChatColor.RED}Failed to delete quote.") + } + } else { + // Edit the quote + openColorSelectorGui(player, slotIndex, "edit") } - } else { - // Edit the quote - openColorSelectorGui(player, slotIndex, "edit") } - } else if (item.type == Material.EMERALD_BLOCK) { - // Add new quote - openColorSelectorGui(player, -1, "add") - } else if (item.type == Material.GOLD_BLOCK) { - // Refresh MOTD - plugin.updateServerMOTD() - player.sendMessage("${ChatColor.GREEN}MOTD has been refreshed!") - } else if (item.type == Material.REDSTONE_BLOCK) { - // Close GUI - player.closeInventory() + Material.EMERALD_BLOCK -> { + // Add new quote + openColorSelectorGui(player, -1, "add") + } + Material.GOLD_BLOCK -> { + // Refresh MOTD + plugin.updateServerMOTD() + player.sendMessage("${ChatColor.GREEN}MOTD has been refreshed!") + } + Material.DIAMOND_BLOCK -> { + // Save quotes to config + plugin.saveQuotes() + player.sendMessage("${ChatColor.GREEN}Quotes have been saved to config!") + } + Material.REDSTONE_BLOCK -> { + // Close GUI + player.closeInventory() + } + else -> return } } @@ -212,7 +239,12 @@ class MOTDGuiHandler(private val plugin: MOTD) : Listener { val displayName = meta.displayName if (displayName.isNotEmpty()) { - val colorCode = "§" + displayName[0].toString().replace("§", "") + val colorCode = if (displayName.startsWith("§")) { + "§" + displayName[1].toString() + } else { + "§f" // Default to white if something goes wrong + } + val session = editingSessions[player.uniqueId] ?: return player.closeInventory() @@ -275,20 +307,14 @@ class MOTDGuiHandler(private val plugin: MOTD) : Listener { fun onInventoryClose(event: InventoryCloseEvent) { val player = event.player as? Player ?: return - // If the inventory being closed is the color selector but not via clicking a color option, - // remove the editing session + // If the inventory being closed is the color selector and not caused by clicking a color option if (event.view.title == "Select Quote Color") { - // This check is a bit simplistic; in a production plugin you'd want to be more careful - // to differentiate between programmatic closes (which should keep the session) and - // user-initiated closes (which should clear it) - if (!editingSessions.containsKey(player.uniqueId)) { - Bukkit.getScheduler().runTaskLater(plugin, Runnable { - if (player.openInventory.title != "MOTD Quotes Manager") - if (player.openInventory.title != "MOTD Quotes Manager") { - editingSessions.remove(player.uniqueId) - } - }, 1) - } + // Clean up the session if not transitioning to another GUI + Bukkit.getScheduler().runTaskLater(plugin, Runnable { + if (player.openInventory.title != "MOTD Quotes Manager") { + editingSessions.remove(player.uniqueId) + } + }, 1) } } } \ No newline at end of file diff --git a/target/MOTD-1.0-SNAPSHOT.jar b/target/MOTD-1.0-SNAPSHOT.jar index 8a271fa..25d1d27 100644 Binary files a/target/MOTD-1.0-SNAPSHOT.jar and b/target/MOTD-1.0-SNAPSHOT.jar differ diff --git a/target/classes/org/server_info/MOTD/MOTD.class b/target/classes/org/server_info/MOTD/MOTD.class index d766f0b..b94eabe 100644 Binary files a/target/classes/org/server_info/MOTD/MOTD.class and b/target/classes/org/server_info/MOTD/MOTD.class differ diff --git a/target/classes/org/server_info/MOTD/MOTDCommand.class b/target/classes/org/server_info/MOTD/MOTDCommand.class index e48c908..62984b8 100644 Binary files a/target/classes/org/server_info/MOTD/MOTDCommand.class and b/target/classes/org/server_info/MOTD/MOTDCommand.class differ diff --git a/target/original-MOTD-1.0-SNAPSHOT.jar b/target/original-MOTD-1.0-SNAPSHOT.jar index 82ae993..7c8d1e2 100644 Binary files a/target/original-MOTD-1.0-SNAPSHOT.jar and b/target/original-MOTD-1.0-SNAPSHOT.jar differ