diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-02-06 18:43:36 +0900 |
|---|---|---|
| committer | Sho Sakuma <me@m1sk9.dev> | 2026-02-06 20:33:37 +0900 |
| commit | 2b539cb2cefd56ad4a8322b643d4735581e6804d (patch) | |
| tree | 24e00d5f40f028323081e35ea7a9f2bf56f726c2 /platform-paper/src/main/kotlin | |
| parent | 5edc4a18217aa9f330eef50372d48ab1eec5becf (diff) | |
| download | LunaticChat-2b539cb2cefd56ad4a8322b643d4735581e6804d.tar.gz LunaticChat-2b539cb2cefd56ad4a8322b643d4735581e6804d.tar.bz2 LunaticChat-2b539cb2cefd56ad4a8322b643d4735581e6804d.zip | |
feat: Add cross-server chat
Diffstat (limited to 'platform-paper/src/main/kotlin')
10 files changed, 389 insertions, 41 deletions
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt index aada4b0..42a4c86 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt @@ -31,7 +31,6 @@ import java.util.concurrent.atomic.AtomicBoolean class LunaticChat : JavaPlugin(), Listener { - // Public API - accessed by commands (maintain backward compatibility) lateinit var directMessageHandler: DirectMessageHandler lateinit var languageManager: LanguageManager var channelManager: ChannelManager? = null @@ -40,7 +39,6 @@ class LunaticChat : var channelMessageHandler: ChannelMessageHandler? = null var channelNotificationHandler: ChannelNotificationHandler? = null - // Private services private lateinit var services: ServiceContainer private lateinit var configuration: LunaticChatConfiguration private lateinit var serviceInitializer: ServiceInitializer @@ -166,7 +164,7 @@ class LunaticChat : * Registers all event listeners. */ private fun registerEventListeners() { - EventListenerRegistry.registerAll(this, services, updateAvailable) + EventListenerRegistry.registerAll(this, services, configuration, updateAvailable) } /** diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt index 6843dd3..5ca5e52 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt @@ -9,6 +9,7 @@ import dev.m1sk9.lunaticChat.paper.chat.handler.DirectMessageHandler import dev.m1sk9.lunaticChat.paper.converter.RomanjiConverter import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager +import dev.m1sk9.lunaticChat.paper.velocity.CrossServerChatManager import dev.m1sk9.lunaticChat.paper.velocity.VelocityConnectionManager /** @@ -27,6 +28,7 @@ import dev.m1sk9.lunaticChat.paper.velocity.VelocityConnectionManager * @property channelMessageHandler Optional (only when channel chat feature is enabled) * @property channelNotificationHandler Optional (only when channel chat feature is enabled) * @property velocityConnectionManager Optional (only when Velocity integration is enabled) + * @property crossServerChatManager Optional (only when Velocity integration and cross-server chat are enabled) */ data class ServiceContainer( val languageManager: LanguageManager, @@ -39,4 +41,5 @@ data class ServiceContainer( val channelMessageHandler: ChannelMessageHandler? = null, val channelNotificationHandler: ChannelNotificationHandler? = null, val velocityConnectionManager: VelocityConnectionManager? = null, + val crossServerChatManager: CrossServerChatManager? = null, ) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt index fe6a4e4..dc015ed 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt @@ -16,6 +16,7 @@ import dev.m1sk9.lunaticChat.paper.converter.RomanjiConverter import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager import dev.m1sk9.lunaticChat.paper.settings.YamlPlayerSettingsStorage +import dev.m1sk9.lunaticChat.paper.velocity.CrossServerChatManager import dev.m1sk9.lunaticChat.paper.velocity.VelocityConnectionManager import io.ktor.client.HttpClient import org.bukkit.event.EventHandler @@ -57,6 +58,7 @@ class ServiceInitializer( private var channelNotificationHandler: ChannelNotificationHandler? = null private var channelMessageLogger: ChannelMessageLogger? = null private var velocityConnectionManager: VelocityConnectionManager? = null + private var crossServerChatManager: CrossServerChatManager? = null private val handshakeCompleted = AtomicBoolean(false) /** @@ -123,6 +125,17 @@ class ServiceInitializer( null } + // 7. Initialize cross-server chat manager (optional) + val crossServerManager = + if (configuration.features.velocityIntegration.enabled && + configuration.features.velocityIntegration.crossServerGlobalChat && + velocityManager != null + ) { + initializeCrossServerChatManager(velocityManager) + } else { + null + } + return ServiceContainer( languageManager = languageManager, playerSettingsManager = playerSettingsManager, @@ -134,6 +147,7 @@ class ServiceInitializer( channelMessageHandler = channelMessageHandler, channelNotificationHandler = channelNotificationHandler, velocityConnectionManager = velocityManager, + crossServerChatManager = crossServerManager, ) } @@ -341,6 +355,31 @@ class ServiceInitializer( } /** + * Initializes cross-server chat manager. + * + * @param velocityManager The Velocity connection manager + * @return The initialized CrossServerChatManager + */ + private fun initializeCrossServerChatManager(velocityManager: VelocityConnectionManager): CrossServerChatManager { + val manager = + CrossServerChatManager( + plugin = plugin, + logger = logger, + configuration = configuration, + cacheSize = configuration.features.velocityIntegration.messageDeduplicationCacheSize, + ) + crossServerChatManager = manager + + // Set the manager in VelocityConnectionManager to handle incoming messages + velocityManager.setCrossServerChatManager(manager) + + logger.info( + "Cross-server global chat initialized (cache size: ${configuration.features.velocityIntegration.messageDeduplicationCacheSize})", + ) + return manager + } + + /** * Performs handshake with Velocity proxy. */ private fun performVelocityHandshake( diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManager.kt index 164a0a8..33952be 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManager.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManager.kt @@ -55,6 +55,21 @@ class ConfigManager { velocityIntegration = VelocityIntegrationConfig( enabled = configFile.getBoolean("features.velocityIntegration.enabled", false), + crossServerGlobalChat = + configFile.getBoolean( + "features.velocityIntegration.crossServerGlobalChat", + false, + ), + serverName = + configFile.getString( + "features.velocityIntegration.serverName", + "Unknown", + ) ?: "Unknown", + messageDeduplicationCacheSize = + configFile.getInt( + "features.velocityIntegration.messageDeduplicationCacheSize", + 100, + ), ), ), messageFormat = @@ -69,6 +84,11 @@ class ConfigManager { "messageFormat.channelMessageFormat", "§7[§b#{channel}§7] §e{sender}: §f{message}", )!!, + crossServerGlobalChatFormat = + configFile.getString( + "messageFormat.crossServerGlobalChatFormat", + "§7[§6{server}§7] §e{sender}: §f{message}", + )!!, ), debug = configFile.getBoolean("debug", false), checkForUpdates = configFile.getBoolean("checkForUpdates", false), diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/MessageFormatConfig.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/MessageFormatConfig.kt index 2e623fa..d31ec4e 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/MessageFormatConfig.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/MessageFormatConfig.kt @@ -3,4 +3,5 @@ package dev.m1sk9.lunaticChat.paper.config.key data class MessageFormatConfig( val directMessageFormat: String = "§7[§e{sender} §7>> §e{recipient}§7] §f{message}", val channelMessageFormat: String = "§7[§b#{channel}§7] §e{sender}: §f{message}", + val crossServerGlobalChatFormat: String = "§7[§6{server}§7] §e{sender}: §f{message}", ) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/VelocityIntegrationConfig.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/VelocityIntegrationConfig.kt index 3cf42bf..51a67b6 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/VelocityIntegrationConfig.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/VelocityIntegrationConfig.kt @@ -2,4 +2,7 @@ package dev.m1sk9.lunaticChat.paper.config.key data class VelocityIntegrationConfig( val enabled: Boolean = false, + val crossServerGlobalChat: Boolean = false, + val serverName: String = "Unknown", + val messageDeduplicationCacheSize: Int = 100, ) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt index 1979b6b..b9999bc 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt @@ -3,6 +3,7 @@ package dev.m1sk9.lunaticChat.paper.listener import dev.m1sk9.lunaticChat.paper.LunaticChat import dev.m1sk9.lunaticChat.paper.ServiceContainer import dev.m1sk9.lunaticChat.paper.common.SpyPermissionManager +import dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration import java.util.concurrent.atomic.AtomicBoolean /** @@ -17,11 +18,13 @@ object EventListenerRegistry { * * @param plugin The plugin instance * @param services The initialized services + * @param configuration The plugin configuration * @param updateAvailable Atomic flag for update availability */ fun registerAll( plugin: LunaticChat, services: ServiceContainer, + configuration: LunaticChatConfiguration, updateAvailable: AtomicBoolean, ) { val pluginManager = plugin.server.pluginManager @@ -40,12 +43,21 @@ object EventListenerRegistry { plugin, ) - // Conditionally register chat listener when all required components are available - if (services.chatModeManager != null && - services.channelManager != null && - services.channelMessageHandler != null && - services.romajiConverter != null - ) { + // Register chat listener when channel chat is enabled OR velocity cross-server chat is enabled + // Channel-related services (chatModeManager, channelManager, channelMessageHandler) can be null + // when channel chat is disabled, but cross-server chat can still work + val shouldRegisterChatListener = + ( + services.chatModeManager != null && + services.channelManager != null && + services.channelMessageHandler != null + ) || + ( + configuration.features.velocityIntegration.enabled && + configuration.features.velocityIntegration.crossServerGlobalChat + ) + + if (shouldRegisterChatListener) { pluginManager.registerEvents( PlayerChatListener( chatModeManager = services.chatModeManager, @@ -54,6 +66,8 @@ object EventListenerRegistry { romajiConverter = services.romajiConverter, settingsManager = services.playerSettingsManager, languageManager = services.languageManager, + configuration = configuration, + crossServerChatManager = services.crossServerChatManager, ), plugin, ) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt index 03423e5..c812ae1 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt @@ -4,25 +4,30 @@ import dev.m1sk9.lunaticChat.engine.chat.ChatMode import dev.m1sk9.lunaticChat.paper.chat.ChatModeManager import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelMessageHandler +import dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration import dev.m1sk9.lunaticChat.paper.converter.RomanjiConverter import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager +import dev.m1sk9.lunaticChat.paper.velocity.CrossServerChatManager import io.papermc.paper.event.player.AsyncChatEvent import kotlinx.coroutines.runBlocking import net.kyori.adventure.text.Component import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer +import org.bukkit.Bukkit import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener class PlayerChatListener( - private val chatModeManager: ChatModeManager, - private val channelManager: ChannelManager, - private val channelMessageHandler: ChannelMessageHandler, - private val romajiConverter: RomanjiConverter, + private val chatModeManager: ChatModeManager?, + private val channelManager: ChannelManager?, + private val channelMessageHandler: ChannelMessageHandler?, + private val romajiConverter: RomanjiConverter?, private val settingsManager: PlayerSettingsManager, private val languageManager: LanguageManager, + private val configuration: LunaticChatConfiguration, + private val crossServerChatManager: CrossServerChatManager?, ) : Listener { private val plainTextSerializer = PlainTextComponentSerializer.plainText() @@ -48,24 +53,29 @@ class PlayerChatListener( } val effectiveMode = - if (hasPrefix) { - val currentMode = chatModeManager.getChatMode(player.uniqueId) - currentMode.toggle() + if (chatModeManager != null) { + if (hasPrefix) { + val currentMode = chatModeManager.getChatMode(player.uniqueId) + currentMode.toggle() + } else { + chatModeManager.getChatMode(player.uniqueId) + } } else { - chatModeManager.getChatMode(player.uniqueId) + // Default to GLOBAL when chatModeManager is not available + ChatMode.GLOBAL } // Handle romaji conversion if enabled // Uses explicit timeout to prevent long blocking (1s max instead of 3s) // Note: AsyncChatEvent runs on async thread, so runBlocking here doesn't block main thread val displayMessage = - if (settings.japaneseConversionEnabled) { + if (settings.japaneseConversionEnabled && romajiConverter != null) { runCatching { runBlocking { kotlinx.coroutines .withTimeoutOrNull(1000) { romajiConverter - .convert(messageWithoutPrefix) + ?.convert(messageWithoutPrefix) }?.let { "$messageWithoutPrefix §e($it)" } ?: messageWithoutPrefix } }.getOrElse { messageWithoutPrefix } @@ -76,30 +86,59 @@ class PlayerChatListener( // Route message based on chat mode when (effectiveMode) { ChatMode.GLOBAL -> { - event.message(Component.text(displayMessage)) + val velocityIntegrationEnabled = configuration.features.velocityIntegration.enabled + val crossServerChatEnabled = configuration.features.velocityIntegration.crossServerGlobalChat + + Bukkit.getLogger().info( + "[LunaticChat DEBUG] Chat mode: GLOBAL, " + + "velocityEnabled=$velocityIntegrationEnabled, " + + "crossServerEnabled=$crossServerChatEnabled, " + + "managerNull=${crossServerChatManager == null}", + ) + + if (velocityIntegrationEnabled && crossServerChatEnabled && crossServerChatManager != null) { + // Send to Velocity for cross-server broadcast + crossServerChatManager.sendGlobalMessage( + player.uniqueId, + player.name, + displayMessage, + ) + + // Display as normal chat on the sender's server (no special formatting) + event.message(Component.text(displayMessage)) + } else { + // Existing behavior: normal Minecraft chat + event.message(Component.text(displayMessage)) + } } ChatMode.CHANNEL -> { - val hasActiveChannel = channelManager.getPlayerChannel(player.uniqueId) != null + // Channel chat requires channelManager and channelMessageHandler + if (channelManager != null && channelMessageHandler != null) { + val hasActiveChannel = channelManager.getPlayerChannel(player.uniqueId) != null + + if (hasActiveChannel) { + // Cancel event and clear all data to prevent other plugins from capturing it + // Even MONITOR priority listeners with ignoreCancelled=false won't get useful data + // Message will be delivered by ChannelMessageHandler instead + // Logging is handled by our own ChannelMessageLogger + event.isCancelled = true + event.viewers().clear() + event.message(Component.empty()) + channelMessageHandler.sendChannelMessage(player, messageWithoutPrefix) + } else { + // Auto-fallback to global chat + event.message(Component.text(displayMessage)) - if (hasActiveChannel) { - // Cancel event and clear all data to prevent other plugins from capturing it - // Even MONITOR priority listeners with ignoreCancelled=false won't get useful data - // Message will be delivered by ChannelMessageHandler instead - // Logging is handled by our own ChannelMessageLogger - event.isCancelled = true - event.viewers().clear() - event.message(Component.empty()) - channelMessageHandler.sendChannelMessage(player, messageWithoutPrefix) + // Send warning to player + player.sendMessage( + MessageFormatter.format( + languageManager.getMessage("channel.autoFallback"), + ), + ) + } } else { - // Auto-fallback to global chat + // Channel chat not available, fallback to normal chat event.message(Component.text(displayMessage)) - - // Send warning to player - player.sendMessage( - MessageFormatter.format( - languageManager.getMessage("channel.autoFallback"), - ), - ) } } } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/velocity/CrossServerChatManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/velocity/CrossServerChatManager.kt new file mode 100644 index 0000000..0f4c34e --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/velocity/CrossServerChatManager.kt @@ -0,0 +1,193 @@ +package dev.m1sk9.lunaticChat.paper.velocity + +import dev.m1sk9.lunaticChat.engine.protocol.PluginMessage +import dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer +import org.bukkit.plugin.Plugin +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import java.util.logging.Logger + +/** + * Manages cross-server global chat messages + * + * Handles: + * - Sending global chat messages to Velocity + * - Processing incoming messages from Velocity + * - Message deduplication using LRU cache + */ +class CrossServerChatManager( + private val plugin: Plugin, + private val logger: Logger, + private val configuration: LunaticChatConfiguration, + private val cacheSize: Int = 100, +) { + companion object { + private const val CLEANUP_THRESHOLD_MILLIS = 60_000L + } + + /** + * Cache of recently processed message IDs (messageId -> timestamp) + * Used for deduplication + */ + private val processedMessages = ConcurrentHashMap<String, Long>() + + /** + * Sends a global chat message to Velocity for cross-server broadcast + * + * @param playerId Player UUID + * @param playerName Player name + * @param message Chat message content + */ + fun sendGlobalMessage( + playerId: UUID, + playerName: String, + message: String, + ) { + try { + val messageId = UUID.randomUUID().toString() + val serverName = configuration.features.velocityIntegration.serverName + + // Mark as processed immediately to prevent echo + processedMessages[messageId] = System.currentTimeMillis() + + val globalChatMessage = + PluginMessage.GlobalChatMessage( + messageId = messageId, + serverName = serverName, + playerId = playerId.toString(), + playerName = playerName, + message = message, + ) + + // Send to Velocity + val player = plugin.server.getPlayer(playerId) + if (player != null) { + player.sendPluginMessage( + plugin, + "lunaticchat:main", + dev.m1sk9.lunaticChat.engine.protocol.PluginMessageCodec + .encode(globalChatMessage), + ) + logger.info("Sent global chat message to Velocity: messageId=$messageId, player=$playerName") + } else { + logger.warning("Cannot send global chat message: player $playerId not found") + } + + // Cleanup old messages if cache is too large + if (processedMessages.size > cacheSize) { + cleanupOldMessages() + } + } catch (e: Exception) { + logger.severe("Failed to send global chat message: ${e.message}") + e.printStackTrace() + } + } + + /** + * Handles incoming global chat message from Velocity + * + * @param message Global chat message + */ + fun handleIncomingMessage(message: PluginMessage.GlobalChatMessage) { + try { + // Check if already processed (deduplication) + if (!shouldProcessMessage(message.messageId)) { + logger.fine("Ignoring duplicate message: messageId=${message.messageId}") + return + } + + // Mark as processed + processedMessages[message.messageId] = System.currentTimeMillis() + + // Broadcast to all players on this server + val formattedMessage = formatCrossServerMessage(message) + + plugin.server.scheduler.runTask( + plugin, + Runnable { + plugin.server.onlinePlayers.forEach { player -> + player.sendMessage(formattedMessage) + } + }, + ) + + logger.info( + "Broadcasted global chat message from ${message.serverName}: " + + "player=${message.playerName}, messageId=${message.messageId}", + ) + + // Cleanup if needed + if (processedMessages.size > cacheSize) { + cleanupOldMessages() + } + } catch (e: Exception) { + logger.severe("Failed to handle incoming global chat message: ${e.message}") + e.printStackTrace() + } + } + + /** + * Formats a cross-server chat message using the configured format + * + * @param message Global chat message + * @return Formatted Component + */ + private fun formatCrossServerMessage(message: PluginMessage.GlobalChatMessage): Component { + val format = configuration.messageFormat.crossServerGlobalChatFormat + val formattedText = + format + .replace("{server}", message.serverName) + .replace("{sender}", message.playerName) + .replace("{message}", message.message) + + return LegacyComponentSerializer.legacySection().deserialize(formattedText) + } + + /** + * Checks if a message should be processed (not a duplicate) + * + * @param messageId Message ID to check + * @return true if message should be processed, false if it's a duplicate + */ + private fun shouldProcessMessage(messageId: String): Boolean = !processedMessages.containsKey(messageId) + + /** + * Removes old messages from the cache (LRU cleanup) + * Keeps only the most recent messages + */ + private fun cleanupOldMessages() { + try { + val currentTime = System.currentTimeMillis() + val cutoffTime = currentTime - CLEANUP_THRESHOLD_MILLIS + + val iterator = processedMessages.entries.iterator() + var removedCount = 0 + + while (iterator.hasNext()) { + val entry = iterator.next() + if (entry.value < cutoffTime) { + iterator.remove() + removedCount++ + } + } + + if (processedMessages.size > cacheSize) { + val sortedEntries = processedMessages.entries.sortedBy { it.value } + val toRemove = processedMessages.size - cacheSize + + sortedEntries.take(toRemove).forEach { entry -> + processedMessages.remove(entry.key) + removedCount++ + } + } + + if (removedCount > 0) { + logger.fine("Cleaned up $removedCount old messages from deduplication cache") + } + } catch (e: Exception) { + logger.warning("Failed to cleanup old messages: ${e.message}") + } + } +} diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/velocity/VelocityConnectionManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/velocity/VelocityConnectionManager.kt index e77910b..1a7e667 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/velocity/VelocityConnectionManager.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/velocity/VelocityConnectionManager.kt @@ -16,6 +16,7 @@ class VelocityConnectionManager( private val plugin: Plugin, private val pluginVersion: String, private val logger: Logger, + private var crossServerChatManager: CrossServerChatManager? = null, ) : PluginMessageListener { companion object { private const val CHANNEL = "lunaticchat:main" @@ -61,6 +62,16 @@ class VelocityConnectionManager( } /** + * Sets the cross-server chat manager + * Called after initialization to avoid circular dependency + * + * @param manager Cross-server chat manager + */ + fun setCrossServerChatManager(manager: CrossServerChatManager) { + this.crossServerChatManager = manager + } + + /** * Performs handshake * * @param player Player to use for sending messages @@ -150,6 +161,10 @@ class VelocityConnectionManager( /** * Plugin message received + * + * @param channel plugin message channel + * @param player player who sent the message + * @param message message byte array */ override fun onPluginMessageReceived( channel: String, @@ -159,11 +174,10 @@ class VelocityConnectionManager( if (channel != CHANNEL) return try { - val pluginMessage = PluginMessageCodec.decode(message) - - when (pluginMessage) { + when (val pluginMessage = PluginMessageCodec.decode(message)) { is PluginMessage.HandshakeResponse -> handleHandshakeResponse(pluginMessage) is PluginMessage.StatusResponse -> handleStatusResponse(pluginMessage) + is PluginMessage.GlobalChatMessage -> handleGlobalChatMessage(pluginMessage) else -> logger.warning("Unexpected message type: ${pluginMessage::class.simpleName}") } } catch (e: Exception) { @@ -174,6 +188,8 @@ class VelocityConnectionManager( /** * Handles handshake response + * + * @param response Handshake response message */ private fun handleHandshakeResponse(response: PluginMessage.HandshakeResponse) { val future = handshakeFuture ?: return @@ -195,6 +211,8 @@ class VelocityConnectionManager( /** * Handles status response + * + * @param response Status response message */ private fun handleStatusResponse(response: PluginMessage.StatusResponse) { logger.info( @@ -211,6 +229,20 @@ class VelocityConnectionManager( } /** + * Handles global chat message from Velocity + * + * @param message Global chat message + */ + private fun handleGlobalChatMessage(message: PluginMessage.GlobalChatMessage) { + val manager = crossServerChatManager + if (manager != null) { + manager.handleIncomingMessage(message) + } else { + logger.warning("Received global chat message but CrossServerChatManager is not initialized") + } + } + + /** * Shutdown */ fun shutdown() { @@ -221,16 +253,22 @@ class VelocityConnectionManager( /** * Gets current connection state + * + * @return Connection state */ fun getState(): ConnectionState = state /** * Gets Velocity version (if connected) + * + * @return Velocity version or null if not connected */ fun getVelocityVersion(): String? = velocityVersion /** * Gets last error message + * + * @return Last error message or null if none */ fun getLastError(): String? = lastError } |
