summaryrefslogtreecommitdiff
path: root/platform-paper
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-01-26 03:52:29 +0900
committerSho Sakuma <me@m1sk9.dev>2026-01-26 03:52:29 +0900
commit817eb7bc479fa55bda106aa1f6c5bd8dc9fbbe4c (patch)
treedde37372880748ace9e2f5b9de762214d1d04b3f /platform-paper
parent18a4bbc4ba1a6766af920fb0849215f058ff260e (diff)
downloadLunaticChat-817eb7bc479fa55bda106aa1f6c5bd8dc9fbbe4c.tar.gz
LunaticChat-817eb7bc479fa55bda106aa1f6c5bd8dc9fbbe4c.tar.bz2
LunaticChat-817eb7bc479fa55bda106aa1f6c5bd8dc9fbbe4c.zip
feat: Add channel-chat configuration
Diffstat (limited to 'platform-paper')
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt10
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManager.kt16
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMembershipManager.kt21
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt32
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChannelCommand.kt16
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChatModeCommand.kt8
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt20
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInfoCommand.kt179
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt22
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommand.kt97
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt43
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/ConfigManager.kt3
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/ChannelChatFeatureConfig.kt3
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt10
-rw-r--r--platform-paper/src/main/resources/config.yml6
-rw-r--r--platform-paper/src/main/resources/languages/en.yml14
-rw-r--r--platform-paper/src/main/resources/languages/ja.yml14
-rw-r--r--platform-paper/src/main/resources/paper-plugin.yml4
18 files changed, 486 insertions, 32 deletions
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 35f70d8..8135fe0 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
@@ -84,7 +84,7 @@ class ServiceInitializer(
// 4. Initialize channel manager, membership manager, chat mode manager, and channel message handler
val channelComponents =
if (configuration.features.channelChat.enabled) {
- initializeChannelManager(playerSettingsManager)
+ initializeChannelManager(playerSettingsManager, romajiConverter)
} else {
null
}
@@ -175,7 +175,10 @@ class ServiceInitializer(
/**
* Initializes channel manager, membership manager, chat mode manager, and channel message handler with storage.
*/
- private fun initializeChannelManager(settingsManager: PlayerSettingsManager): ChannelComponents {
+ private fun initializeChannelManager(
+ settingsManager: PlayerSettingsManager,
+ romajiConverter: RomanjiConverter?,
+ ): ChannelComponents {
val channelsFile = plugin.dataFolder.resolve("channels.json").toPath()
val storage =
ChannelStorage(
@@ -188,6 +191,7 @@ class ServiceInitializer(
ChannelManager(
storage = storage,
logger = logger,
+ config = configuration.features.channelChat,
)
manager.initialize()
channelManager = manager
@@ -196,6 +200,7 @@ class ServiceInitializer(
ChannelMembershipManager(
channelManager = manager,
logger = logger,
+ config = configuration.features.channelChat,
)
channelMembershipManager = membershipManager
@@ -218,6 +223,7 @@ class ServiceInitializer(
ChannelMessageHandler(
settingsManager = settingsManager,
channelManager = manager,
+ romanjiConverter = romajiConverter,
logger =
io.ktor.util.logging
.KtorSimpleLogger("ChannelMessageHandler"),
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManager.kt
index fc8d6da..679d300 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManager.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelManager.kt
@@ -5,8 +5,11 @@ import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelContext
import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelData
import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelMember
import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole
+import dev.m1sk9.lunaticChat.engine.exception.ChannelLimitExceededException
+import dev.m1sk9.lunaticChat.engine.exception.ChannelMemberLimitExceededException
import dev.m1sk9.lunaticChat.engine.exception.ChannelNoOwnerPermissionException
import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException
+import dev.m1sk9.lunaticChat.paper.config.key.ChannelChatFeatureConfig
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
@@ -16,6 +19,7 @@ import kotlin.collections.forEach
class ChannelManager(
private val storage: ChannelStorage,
private val logger: Logger,
+ private val config: ChannelChatFeatureConfig,
) {
private val channelsCache = ConcurrentHashMap<String, Channel>()
private val membersCache = ConcurrentHashMap<String, CopyOnWriteArrayList<ChannelMember>>()
@@ -47,12 +51,18 @@ class ChannelManager(
* @param channel The channel to create.
* @return Result containing the created channel or an error if the channel already exists.
* @throws ChannelNotFoundException if a channel with the same ID already exists.
+ * @throws ChannelLimitExceededException if the server has reached the maximum channel limit.
*/
fun createChannel(channel: Channel): Result<Channel> {
if (channelsCache.containsKey(channel.id)) {
return Result.failure(ChannelNotFoundException(channel.id))
}
+ // Check if max channels limit is reached (0 means unlimited)
+ if (config.maxChannelsPerServer > 0 && channelsCache.size >= config.maxChannelsPerServer) {
+ return Result.failure(ChannelLimitExceededException(config.maxChannelsPerServer))
+ }
+
channelsCache[channel.id] = channel
val ownerMember =
@@ -159,6 +169,7 @@ class ChannelManager(
* @param role The role of the new member.
* @return Result indicating success or failure of the operation.
* @throws ChannelNotFoundException if the channel does not exist.
+ * @throws ChannelMemberLimitExceededException if the channel has reached the maximum member limit.
*/
fun addMember(
channelId: String,
@@ -172,6 +183,11 @@ class ChannelManager(
membersCache.getOrPut(channelId) {
CopyOnWriteArrayList()
}
+
+ // Check if max members limit is reached (0 means unlimited)
+ if (config.maxMembersPerChannel > 0 && members.size >= config.maxMembersPerChannel) {
+ return Result.failure(ChannelMemberLimitExceededException(channelId, config.maxMembersPerChannel))
+ }
val newMember =
ChannelMember(
channelId = channelId,
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMembershipManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMembershipManager.kt
index 6d6ad8d..3c45eb4 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMembershipManager.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/channel/ChannelMembershipManager.kt
@@ -6,12 +6,15 @@ import dev.m1sk9.lunaticChat.engine.exception.ChannelMemberAlreadyException
import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException
import dev.m1sk9.lunaticChat.engine.exception.ChannelNotMemberException
import dev.m1sk9.lunaticChat.engine.exception.ChannelRuntimeException
+import dev.m1sk9.lunaticChat.engine.exception.PlayerChannelLimitExceededException
+import dev.m1sk9.lunaticChat.paper.config.key.ChannelChatFeatureConfig
import java.util.UUID
import java.util.logging.Logger
class ChannelMembershipManager(
private val channelManager: ChannelManager,
private val logger: Logger,
+ private val config: ChannelChatFeatureConfig,
) {
/**
* Checks if a player is a member of a channel.
@@ -83,6 +86,7 @@ class ChannelMembershipManager(
* @return Result indicating success or failure.
* @throws ChannelNotFoundException if the channel does not exist.
* @throws ChannelMemberAlreadyException if the player is already a member of this channel.
+ * @throws PlayerChannelLimitExceededException if the player has reached the maximum channel membership limit.
* @throws ChannelRuntimeException for other runtime errors.
*/
fun joinChannel(
@@ -120,6 +124,23 @@ class ChannelMembershipManager(
)
}
+ // Check if player has reached max membership limit (0 means unlimited)
+ if (config.maxMembershipPerPlayer > 0) {
+ val playerChannelCount =
+ getPlayerChannels(playerId)
+ .getOrElse {
+ return Result.failure(
+ ChannelRuntimeException("Failed to get player channels for $playerId", it),
+ )
+ }.size
+
+ if (playerChannelCount >= config.maxMembershipPerPlayer) {
+ return Result.failure(
+ PlayerChannelLimitExceededException(playerId, config.maxMembershipPerPlayer),
+ )
+ }
+ }
+
// Add as member
channelManager.addMember(channelId, playerId, ChannelRole.MEMBER).getOrElse {
return Result.failure(it)
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt
index 70756d9..45e9d6d 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt
@@ -5,6 +5,7 @@ import dev.m1sk9.lunaticChat.paper.common.SpyPermissionManager
import dev.m1sk9.lunaticChat.paper.common.playChannelReceiveNotification
import dev.m1sk9.lunaticChat.paper.common.playMessageSendNotification
import dev.m1sk9.lunaticChat.paper.config.ConfigManager
+import dev.m1sk9.lunaticChat.paper.converter.RomanjiConverter
import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager
import io.ktor.util.logging.Logger
import net.kyori.adventure.text.Component
@@ -14,6 +15,7 @@ import org.bukkit.entity.Player
class ChannelMessageHandler(
private val settingsManager: PlayerSettingsManager?,
private val channelManager: ChannelManager,
+ private val romanjiConverter: RomanjiConverter?,
private val logger: Logger,
) {
private var lunaticChatConfiguration = ConfigManager.getConfiguration()
@@ -26,21 +28,37 @@ class ChannelMessageHandler(
val context =
channelManager.getPlayerChannelContext(playerId)
?: return false
- val formattedMessage = formatChannelMessage(player.name, context.channel.name, message)
- // Play notification sound to sender if enabled
- settingsManager?.let { manager ->
- val senderSettings = manager.getSettings(playerId)
- if (senderSettings.channelMessageNotificationEnabled) {
- player.playMessageSendNotification()
+ val senderSettings = settingsManager?.getSettings(playerId)
+
+ // Handle romaji conversion if enabled (requires blocking for HTTP call)
+ val displayMessage =
+ if (senderSettings?.japaneseConversionEnabled == true && romanjiConverter != null) {
+ runCatching {
+ kotlinx.coroutines.runBlocking {
+ romanjiConverter
+ ?.convert(message)
+ ?.let { "$message §e($it)" }
+ ?: message
+ }
+ }.getOrNull() ?: message
+ } else {
+ message
}
+
+ val formattedMessage = formatChannelMessage(player.name, context.channel.name, displayMessage)
+ val spyMessage = formatChannelMessage(player.name, context.channel.name, message)
+
+ // Play notification sound to sender if enabled
+ if (senderSettings?.channelMessageNotificationEnabled == true) {
+ player.playMessageSendNotification()
}
SpyPermissionManager
.getDirectMessageSpyPlayers()
.values
.filter { it.isOnline && it.uniqueId != playerId }
- .forEach { it.sendMessage(formattedMessage) }
+ .forEach { it.sendMessage(spyMessage) }
context.members.forEach { member ->
Bukkit.getPlayer(member.playerId)?.let { memberPlayer ->
if (memberPlayer.isOnline) {
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChannelCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChannelCommand.kt
index 3e47c7e..90d8f31 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChannelCommand.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChannelCommand.kt
@@ -12,6 +12,7 @@ import dev.m1sk9.lunaticChat.paper.command.core.CommandContext
import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand
import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelCreateCommand
import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelDeleteCommand
+import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelInfoCommand
import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelJoinCommand
import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelLeaveCommand
import dev.m1sk9.lunaticChat.paper.command.impl.lc.channel.ChannelListCommand
@@ -82,6 +83,12 @@ class ChannelCommand(
languageManager,
).buildWithPermissionCheck(),
).then(
+ ChannelInfoCommand(
+ plugin,
+ channelManager,
+ languageManager,
+ ).buildWithPermissionCheck(),
+ ).then(
ChannelDeleteCommand(
plugin,
channelManager,
@@ -168,6 +175,15 @@ class ChannelCommand(
.text(" ")
.append(
MessageFormatter.formatSuccess(
+ languageManager.getMessage("channel.help.info"),
+ ),
+ ),
+ )
+ sender.sendMessage(
+ Component
+ .text(" ")
+ .append(
+ MessageFormatter.formatSuccess(
languageManager.getMessage("channel.help.delete"),
),
),
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChatModeCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChatModeCommand.kt
index 73a50e5..d0357f6 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChatModeCommand.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/ChatModeCommand.kt
@@ -11,6 +11,7 @@ import dev.m1sk9.lunaticChat.paper.command.core.CommandContext
import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand
import dev.m1sk9.lunaticChat.paper.command.impl.lc.chatmode.ChatModeToggleCommand
import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager
+import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter
import io.papermc.paper.command.brigadier.CommandSourceStack
import io.papermc.paper.command.brigadier.Commands
import net.kyori.adventure.text.Component
@@ -69,9 +70,10 @@ class ChatModeCommand(
}
sender.sendMessage(
- Component
- .text(languageManager.getMessage("chatmode.current") + ": ", NamedTextColor.GRAY)
- .append(Component.text(languageManager.getMessage(modeKey), modeColor)),
+ MessageFormatter
+ .format(
+ languageManager.getMessage("chatmode.current") + ": ",
+ ).append(Component.text(languageManager.getMessage(modeKey), modeColor)),
)
return CommandResult.Success
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt
index 29da990..21c147a 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelCreateCommand.kt
@@ -5,6 +5,7 @@ import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import dev.m1sk9.lunaticChat.engine.chat.channel.Channel
import dev.m1sk9.lunaticChat.engine.command.CommandResult
+import dev.m1sk9.lunaticChat.engine.exception.ChannelLimitExceededException
import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode
import dev.m1sk9.lunaticChat.paper.LunaticChat
import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager
@@ -123,12 +124,23 @@ class ChannelCreateCommand(
)
},
onFailure = { error ->
+ val messageKey =
+ when (error) {
+ is ChannelLimitExceededException ->
+ "channel.create.limitExceeded"
+ else ->
+ "channel.create.alreadyExists"
+ }
+ val params =
+ when (error) {
+ is ChannelLimitExceededException ->
+ mapOf("limit" to error.limit.toString())
+ else ->
+ mapOf("id" to channelId)
+ }
CommandResult.Failure(
MessageFormatter.formatError(
- languageManager.getMessage(
- "channel.create.alreadyExists",
- mapOf("id" to channelId),
- ),
+ languageManager.getMessage(messageKey, params),
),
)
},
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInfoCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInfoCommand.kt
new file mode 100644
index 0000000..cb040d6
--- /dev/null
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelInfoCommand.kt
@@ -0,0 +1,179 @@
+package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel
+
+import com.mojang.brigadier.arguments.StringArgumentType
+import com.mojang.brigadier.builder.LiteralArgumentBuilder
+import dev.m1sk9.lunaticChat.engine.command.CommandResult
+import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode
+import dev.m1sk9.lunaticChat.paper.LunaticChat
+import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager
+import dev.m1sk9.lunaticChat.paper.command.annotation.Permission
+import dev.m1sk9.lunaticChat.paper.command.annotation.PlayerOnly
+import dev.m1sk9.lunaticChat.paper.command.core.CommandContext
+import dev.m1sk9.lunaticChat.paper.command.core.LunaticCommand
+import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager
+import dev.m1sk9.lunaticChat.paper.i18n.MessageFormatter
+import io.papermc.paper.command.brigadier.CommandSourceStack
+import io.papermc.paper.command.brigadier.Commands
+import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.format.NamedTextColor
+import org.bukkit.Bukkit
+
+@PlayerOnly
+class ChannelInfoCommand(
+ plugin: LunaticChat,
+ private val channelManager: ChannelManager,
+ private val languageManager: LanguageManager,
+) : LunaticCommand(plugin) {
+ companion object {
+ private const val MAX_MEMBERS_DISPLAY = 10
+ }
+
+ fun buildWithPermissionCheck(): LiteralArgumentBuilder<CommandSourceStack> {
+ val builder = build()
+ return applyMethodPermission("build", builder)
+ }
+
+ @Permission(LunaticChatPermissionNode.ChannelInfo::class)
+ fun build(): LiteralArgumentBuilder<CommandSourceStack> =
+ Commands
+ .literal("info")
+ .executes { ctx ->
+ val context = wrapContext(ctx)
+ checkPlayerOnly(context)?.let { return@executes handleResult(context, it) }
+
+ val result = execute(context, null)
+ handleResult(context, result)
+ }.then(
+ Commands
+ .argument("channelId", StringArgumentType.word())
+ .suggests { _, builder ->
+ // Tab completion: suggest all public channel IDs
+ val channels = channelManager.getAllChannels().getOrNull() ?: emptyList()
+ channels.forEach { channel ->
+ builder.suggest(channel.id)
+ }
+ builder.buildFuture()
+ }.executes { ctx ->
+ val context = wrapContext(ctx)
+ checkPlayerOnly(context)?.let { return@executes handleResult(context, it) }
+
+ val channelId = StringArgumentType.getString(ctx, "channelId")
+ val result = execute(context, channelId)
+ handleResult(context, result)
+ },
+ )
+
+ private fun execute(
+ ctx: CommandContext,
+ channelIdArg: String?,
+ ): CommandResult {
+ val sender = ctx.requirePlayer()
+
+ // Determine which channel to show info for
+ val channelId =
+ channelIdArg ?: channelManager.getPlayerChannel(sender.uniqueId)
+ ?: return CommandResult.Failure(
+ MessageFormatter.formatError(
+ languageManager.getMessage("channel.info.noActiveChannel"),
+ ),
+ )
+
+ // Get channel
+ val channel =
+ channelManager.getChannel(channelId).getOrElse {
+ return CommandResult.Failure(
+ MessageFormatter.formatError(
+ languageManager.getMessage(
+ "channel.info.notFound",
+ mapOf("channelId" to channelId),
+ ),
+ ),
+ )
+ }
+
+ // Get members
+ val members =
+ channelManager.getChannelMembers(channelId).getOrElse {
+ return CommandResult.Failure(
+ MessageFormatter.formatError(
+ languageManager.getMessage("channel.info.error"),
+ ),
+ )
+ }
+
+ // Get owner name
+ val ownerName = Bukkit.getOfflinePlayer(channel.ownerId).name ?: channel.ownerId.toString()
+
+ // Display header
+ sender.sendMessage(
+ MessageFormatter.format(
+ languageManager.getMessage("channel.info.header"),
+ ),
+ )
+
+ // Display channel name
+ sender.sendMessage(
+ Component
+ .text(" ")
+ .append(Component.text(languageManager.getMessage("channel.info.name"), NamedTextColor.GRAY))
+ .append(Component.text(": ", NamedTextColor.GRAY))
+ .append(Component.text(channel.name, NamedTextColor.AQUA)),
+ )
+
+ // Display channel ID
+ sender.sendMessage(
+ Component
+ .text(" ")
+ .append(Component.text(languageManager.getMessage("channel.info.id"), NamedTextColor.GRAY))
+ .append(Component.text(": ", NamedTextColor.GRAY))
+ .append(Component.text(channel.id, NamedTextColor.YELLOW)),
+ )
+
+ // Display owner
+ sender.sendMessage(
+ Component
+ .text(" ")
+ .append(Component.text(languageManager.getMessage("channel.info.owner"), NamedTextColor.GRAY))
+ .append(Component.text(": ", NamedTextColor.GRAY))
+ .append(Component.text(ownerName, NamedTextColor.GOLD)),
+ )
+
+ // Display members
+ val memberNames =
+ members.mapNotNull { member ->
+ Bukkit.getOfflinePlayer(member.playerId).name
+ }
+
+ val membersText =
+ if (memberNames.size > MAX_MEMBERS_DISPLAY) {
+ val displayNames = memberNames.take(MAX_MEMBERS_DISPLAY)
+ val message =
+ languageManager.getMessage(
+ "channel.info.membersOmitted",
+ mapOf("count" to memberNames.size.toString()),
+ )
+ Component
+ .text(" ")
+ .append(Component.text(languageManager.getMessage("channel.info.members"), NamedTextColor.GRAY))
+ .append(Component.text(": ", NamedTextColor.GRAY))
+ .append(Component.text(displayNames.joinToString(", "), NamedTextColor.WHITE))
+ .append(Component.text(" ... ", NamedTextColor.GRAY))
+ .append(Component.text("($message)", NamedTextColor.YELLOW))
+ } else {
+ Component
+ .text(" ")
+ .append(Component.text(languageManager.getMessage("channel.info.members"), NamedTextColor.GRAY))
+ .append(Component.text(": ", NamedTextColor.GRAY))
+ .append(Component.text(memberNames.joinToString(", "), NamedTextColor.WHITE))
+ }
+
+ sender.sendMessage(membersText)
+
+ return CommandResult.Success
+ }
+
+ override fun buildCommand(): LiteralArgumentBuilder<CommandSourceStack> =
+ throw UnsupportedOperationException(
+ "ChannelInfoCommand should use build() method instead of buildCommand()",
+ )
+}
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt
index 2a73199..c7a86ad 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelJoinCommand.kt
@@ -5,7 +5,9 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder
import dev.m1sk9.lunaticChat.engine.command.CommandResult
import dev.m1sk9.lunaticChat.engine.exception.ChannelAlreadyActiveException
import dev.m1sk9.lunaticChat.engine.exception.ChannelMemberAlreadyException
+import dev.m1sk9.lunaticChat.engine.exception.ChannelMemberLimitExceededException
import dev.m1sk9.lunaticChat.engine.exception.ChannelNotFoundException
+import dev.m1sk9.lunaticChat.engine.exception.PlayerChannelLimitExceededException
import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode
import dev.m1sk9.lunaticChat.paper.LunaticChat
import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager
@@ -113,6 +115,26 @@ class ChannelJoinCommand(
),
)
}
+ is ChannelMemberLimitExceededException -> {
+ CommandResult.Failure(
+ MessageFormatter.formatError(
+ languageManager.getMessage(
+ "channel.join.channelMemberLimitExceeded",
+ mapOf("limit" to error.limit.toString()),
+ ),
+ ),
+ )
+ }
+ is PlayerChannelLimitExceededException -> {
+ CommandResult.Failure(
+ MessageFormatter.formatError(
+ languageManager.getMessage(
+ "channel.join.playerChannelLimitExceeded",
+ mapOf("limit" to error.limit.toString()),
+ ),
+ ),
+ )
+ }
else -> {
CommandResult.Failure(
MessageFormatter.formatError(
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommand.kt
index aaf05af..1004baf 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommand.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelListCommand.kt
@@ -1,5 +1,6 @@
package dev.m1sk9.lunaticChat.paper.command.impl.lc.channel
+import com.mojang.brigadier.arguments.IntegerArgumentType
import com.mojang.brigadier.builder.LiteralArgumentBuilder
import dev.m1sk9.lunaticChat.engine.command.CommandResult
import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode
@@ -17,7 +18,7 @@ import net.kyori.adventure.text.Component
import net.kyori.adventure.text.event.ClickEvent
import net.kyori.adventure.text.event.HoverEvent
import net.kyori.adventure.text.format.NamedTextColor
-import net.kyori.adventure.text.format.TextDecoration
+import kotlin.math.ceil
@PlayerOnly
class ChannelListCommand(
@@ -25,6 +26,10 @@ class ChannelListCommand(
private val channelManager: ChannelManager,
private val languageManager: LanguageManager,
) : LunaticCommand(plugin) {
+ companion object {
+ private const val CHANNELS_PER_PAGE = 10
+ }
+
fun buildWithPermissionCheck(): LiteralArgumentBuilder<CommandSourceStack> {
val builder = build()
return applyMethodPermission("build", builder)
@@ -32,15 +37,31 @@ class ChannelListCommand(
@Permission(LunaticChatPermissionNode.ChannelList::class)
fun build(): LiteralArgumentBuilder<CommandSourceStack> =
- Commands.literal("list").executes { ctx ->
- val context = wrapContext(ctx)
- checkPlayerOnly(context)?.let { return@executes handleResult(context, it) }
+ Commands
+ .literal("list")
+ .executes { ctx ->
+ val context = wrapContext(ctx)
+ checkPlayerOnly(context)?.let { return@executes handleResult(context, it) }
+
+ val result = execute(context, 1)
+ handleResult(context, result)
+ }.then(
+ Commands
+ .argument("page", IntegerArgumentType.integer(1))
+ .executes { ctx ->
+ val context = wrapContext(ctx)
+ checkPlayerOnly(context)?.let { return@executes handleResult(context, it) }
- val result = execute(context)
- handleResult(context, result)
- }
+ val page = IntegerArgumentType.getInteger(ctx, "page")
+ val result = execute(context, page)
+ handleResult(context, result)
+ },
+ )
- private fun execute(ctx: CommandContext): CommandResult {
+ private fun execute(
+ ctx: CommandContext,
+ page: Int,
+ ): CommandResult {
val sender = ctx.requirePlayer()
val result = channelManager.getPublicChannels()
@@ -53,6 +74,12 @@ class ChannelListCommand(
),
)
} else {
+ val totalPages = ceil(channels.size.toDouble() / CHANNELS_PER_PAGE).toInt()
+ val currentPage = page.coerceIn(1, totalPages)
+ val startIndex = (currentPage - 1) * CHANNELS_PER_PAGE
+ val endIndex = (startIndex + CHANNELS_PER_PAGE).coerceAtMost(channels.size)
+ val pageChannels = channels.subList(startIndex, endIndex)
+
sender.sendMessage(
MessageFormatter.format(
languageManager.getMessage(
@@ -62,7 +89,7 @@ class ChannelListCommand(
),
)
- channels.forEach { channel ->
+ pageChannels.forEach { channel ->
val memberCountResult = channelManager.getChannelMembers(channel.id)
val memberCount =
memberCountResult.getOrNull()?.size
@@ -97,8 +124,7 @@ class ChannelListCommand(
.text(" • ", NamedTextColor.GRAY)
.append(
Component
- .text(channel.name, NamedTextColor.AQUA)
- .decorate(TextDecoration.BOLD),
+ .text(channel.name, NamedTextColor.AQUA),
).append(Component.text(" (", NamedTextColor.GRAY))
.append(Component.text(channel.id, NamedTextColor.YELLOW))
.append(Component.text(")", NamedTextColor.GRAY))
@@ -107,6 +133,55 @@ class ChannelListCommand(
sender.sendMessage(channelInfo)
}
+
+ // Display pagination footer
+ if (totalPages > 1) {
+ val paginationComponent =
+ Component
+ .text()
+ .append(Component.text("Page ", NamedTextColor.GRAY))
+ .append(Component.text("$currentPage", NamedTextColor.YELLOW))
+ .append(Component.text("/", NamedTextColor.GRAY))
+ .append(Component.text("$totalPages", NamedTextColor.YELLOW))
+
+ if (currentPage > 1) {
+ paginationComponent
+ .append(Component.text(" ", NamedTextColor.GRAY))
+ .append(
+ Component
+ .text("[◀]", NamedTextColor.GREEN)
+ .clickEvent(ClickEvent.runCommand("/lc channel list ${currentPage - 1}"))
+ .hoverEvent(
+ HoverEvent.showText(
+ Component.text(
+ "Go to page ${currentPage - 1}",
+ NamedTextColor.WHITE,
+ ),
+ ),
+ ),
+ )
+ }
+
+ if (currentPage < totalPages) {
+ paginationComponent
+ .append(Component.text(" ", NamedTextColor.GRAY))
+ .append(
+ Component
+ .text("[▶]", NamedTextColor.GREEN)
+ .clickEvent(ClickEvent.runCommand("/lc channel list ${currentPage + 1}"))
+ .hoverEvent(
+ HoverEvent.showText(
+ Component.text(
+ "Go to page ${currentPage + 1}",
+ NamedTextColor.WHITE,
+ ),
+ ),
+ ),
+ )
+ }
+
+ sender.sendMessage(paginationComponent.build())
+ }
}
CommandResult.Success
},
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt
index fa03965..571aa83 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/lc/channel/ChannelStatusCommand.kt
@@ -19,6 +19,7 @@ import net.kyori.adventure.text.event.ClickEvent
import net.kyori.adventure.text.event.HoverEvent
import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.text.format.TextDecoration
+import org.bukkit.Bukkit
@PlayerOnly
class ChannelStatusCommand(
@@ -27,6 +28,10 @@ class ChannelStatusCommand(
private val membershipManager: ChannelMembershipManager,
private val languageManager: LanguageManager,
) : LunaticCommand(plugin) {
+ companion object {
+ private const val MAX_MEMBERS_DISPLAY = 10
+ }
+
fun buildWithPermissionCheck(): LiteralArgumentBuilder<CommandSourceStack> {
val builder = build()
return applyMethodPermission("build", builder)
@@ -85,6 +90,44 @@ class ChannelStatusCommand(
.append(Component.text(")", NamedTextColor.GRAY))
sender.sendMessage(activeText)
+
+ // Display members of active channel
+ val activeMembers =
+ channelManager.getChannelMembers(activeChannelId).getOrElse {
+ emptyList()
+ }
+
+ if (activeMembers.isNotEmpty()) {
+ val memberNames =
+ activeMembers.mapNotNull { member ->
+ Bukkit.getOfflinePlayer(member.playerId).name
+ }
+
+ val membersText =
+ if (memberNames.size > MAX_MEMBERS_DISPLAY) {
+ val displayNames = memberNames.take(MAX_MEMBERS_DISPLAY)
+ val message =
+ languageManager.getMessage(
+ "channel.info.membersOmitted",
+ mapOf("count" to memberNames.size.toString()),
+ )
+ Component
+ .text(" ")
+ .append(Component.text(languageManager.getMessage("channel.info.members"), NamedTextColor.GRAY))
+ .append(Component.text(": ", NamedTextColor.GRAY))
+ .append(Component.text(displayNames.joinToString(", "), NamedTextColor.WHITE))
+ .append(Component.text(" ... ", NamedTextColor.GRAY))
+ .append(Component.text("($message)", NamedTextColor.YELLOW))
+ } else {
+ Component
+ .text(" ")
+ .append(Component.text(languageManager.getMessage("channel.info.members"), NamedTextColor.GRAY))
+ .append(Component.text(": ", NamedTextColor.GRAY))
+ .append(Component.text(memberNames.joinToString(", "), NamedTextColor.WHITE))
+ }
+
+ sender.sendMessage(membersText)
+ }
} else {
sender.sendMessage(
Component
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 9aa563a..c53d154 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
@@ -50,6 +50,9 @@ object ConfigManager {
channelChat =
ChannelChatFeatureConfig(
enabled = configFile.getBoolean("features.channelChat.enabled", false),
+ maxChannelsPerServer = configFile.getInt("features.channelChat.maxChannelsPerServer", 0),
+ maxMembersPerChannel = configFile.getInt("features.channelChat.maxMembersPerChannel", 0),
+ maxMembershipPerPlayer = configFile.getInt("features.channelChat.maxMembershipPerPlayer", 0),
),
),
messageFormat =
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/ChannelChatFeatureConfig.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/ChannelChatFeatureConfig.kt
index 828bdb3..5b93ac5 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/ChannelChatFeatureConfig.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/config/key/ChannelChatFeatureConfig.kt
@@ -2,4 +2,7 @@ package dev.m1sk9.lunaticChat.paper.config.key
data class ChannelChatFeatureConfig(
val enabled: Boolean,
+ val maxChannelsPerServer: Int = 0,
+ val maxMembersPerChannel: Int = 0,
+ val maxMembershipPerPlayer: Int = 0,
)
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt
index fe4239d..86ae129 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt
@@ -63,11 +63,13 @@ class PlayerPresenceListener(
val context = manager.getPlayerChannelContext(player.uniqueId)
context?.let {
val notification =
- languageManager.getMessage(
- "channel.notification.login",
- mapOf("channelName" to it.channel.name),
+ MessageFormatter.format(
+ languageManager.getMessage(
+ "channel.notification.login",
+ mapOf("channelName" to it.channel.name),
+ ),
)
- player.sendMessage(Component.text(notification))
+ player.sendMessage(notification)
}
}
}
diff --git a/platform-paper/src/main/resources/config.yml b/platform-paper/src/main/resources/config.yml
index 839e0e3..8efa1fa 100644
--- a/platform-paper/src/main/resources/config.yml
+++ b/platform-paper/src/main/resources/config.yml
@@ -51,6 +51,12 @@ features:
channelChat:
# If enabled, channel-based chat functionality will be activated.
enabled: false
+ # Maximum number of channels that can be created per server. Set to 0 for unlimited.
+ maxChannelsPerServer: 0
+ # Maximum number of members allowed in a single channel. Set to 0 for unlimited.
+ maxMembersPerChannel: 0
+ # Maximum number of channels a single player can join. Set to 0 for unlimited.
+ maxMembershipPerPlayer: 0
# ----------------------------------------------
# --------- Message Format Settings --------
diff --git a/platform-paper/src/main/resources/languages/en.yml b/platform-paper/src/main/resources/languages/en.yml
index 925d7e7..5887b25 100644
--- a/platform-paper/src/main/resources/languages/en.yml
+++ b/platform-paper/src/main/resources/languages/en.yml
@@ -50,11 +50,13 @@ channel:
leave: "/lc channel leave - Leave the active channel"
switch: "/lc channel switch <channelId> - Switch to a channel you're already a member of"
status: "/lc channel status - Show your channel status and memberships"
+ info: "/lc channel info [channelId] - Show detailed information about a channel"
delete: "/lc channel delete <channelId> - Delete a channel"
create:
success: "Created channel '{name}' (ID: {id})"
alreadyExists: "Channel ID '{id}' is already in use"
invalidId: "Invalid channel ID '{id}'. Must be 3-30 characters using only letters, numbers, underscores, and hyphens"
+ limitExceeded: "Cannot create channel: server has reached the maximum limit of {limit} channels"
list:
header: "=== Public Channels ({count}) ==="
empty: "No public channels available"
@@ -71,6 +73,8 @@ channel:
alreadyMember: "You are already a member of channel '{channelName}'"
notFound: "Channel '{channelId}' not found"
privateChannel: "This is a private channel and requires an invitation"
+ channelMemberLimitExceeded: "Cannot join channel: channel has reached the maximum member limit of {limit}"
+ playerChannelLimitExceeded: "Cannot join channel: you have reached the maximum channel membership limit of {limit}"
error: "Failed to join channel"
leave:
success: "Left channel '{channelName}'"
@@ -90,6 +94,16 @@ channel:
noChannels: "You are not a member of any channels"
clickToSwitch: "Click to switch"
error: "Failed to retrieve channel status"
+ info:
+ header: "=== Channel Information ==="
+ name: "Name"
+ id: "ID"
+ owner: "Owner"
+ members: "Members"
+ membersOmitted: "{count} members (showing first 10)"
+ notFound: "Channel '{channelId}' not found"
+ noActiveChannel: "You don't have an active channel. Please specify a channel ID"
+ error: "Failed to retrieve channel information"
notification:
login: "You are in channel '{channelName}'"
diff --git a/platform-paper/src/main/resources/languages/ja.yml b/platform-paper/src/main/resources/languages/ja.yml
index 17b0bf6..768466a 100644
--- a/platform-paper/src/main/resources/languages/ja.yml
+++ b/platform-paper/src/main/resources/languages/ja.yml
@@ -50,11 +50,13 @@ channel:
leave: "/lc channel leave - アクティブチャンネルから退出します"
switch: "/lc channel switch <チャンネルID> - 既に参加しているチャンネルに切り替えます"
status: "/lc channel status - チャンネルのステータスとメンバーシップを表示します"
+ info: "/lc channel info [チャンネルID] - チャンネルの詳細情報を表示します"
delete: "/lc channel delete <チャンネルID> - チャンネルを削除します"
create:
success: "チャンネル '{name}' (ID: {id}) を作成しました"
alreadyExists: "チャンネルID '{id}' は既に使用されています"
invalidId: "無効なチャンネルID '{id}' です。3-30文字の英数字、アンダースコア、ハイフンのみ使用できます"
+ limitExceeded: "チャンネルを作成できません: サーバーはチャンネル数の上限 ({limit}件) に達しています"
list:
header: "=== 公開チャンネル ({count}件) ==="
empty: "公開チャンネルはありません"
@@ -71,6 +73,8 @@ channel:
alreadyMember: "既にチャンネル '{channelName}' のメンバーです"
notFound: "チャンネル '{channelId}' が見つかりません"
privateChannel: "プライベートチャンネルには招待が必要です"
+ channelMemberLimitExceeded: "チャンネルに参加できません: チャンネルのメンバー数が上限 ({limit}人) に達しています"
+ playerChannelLimitExceeded: "チャンネルに参加できません: 参加できるチャンネル数の上限 ({limit}件) に達しています"
error: "チャンネルへの参加に失敗しました"
leave:
success: "チャンネル '{channelName}' から退出しました"
@@ -90,6 +94,16 @@ channel:
noChannels: "どのチャンネルにも参加していません"
clickToSwitch: "クリックして切り替え"
error: "チャンネルステータスの取得に失敗しました"
+ info:
+ header: "=== チャンネル情報 ==="
+ name: "名前"
+ id: "ID"
+ owner: "オーナー"
+ members: "メンバー"
+ membersOmitted: "{count}人のメンバー (最初の10人を表示)"
+ notFound: "チャンネル '{channelId}' が見つかりません"
+ noActiveChannel: "アクティブなチャンネルがありません。チャンネルIDを指定してください"
+ error: "チャンネル情報の取得に失敗しました"
notification:
login: "チャンネル '{channelName}' に入室中です"
diff --git a/platform-paper/src/main/resources/paper-plugin.yml b/platform-paper/src/main/resources/paper-plugin.yml
index b3a9d8f..0ae5cd3 100644
--- a/platform-paper/src/main/resources/paper-plugin.yml
+++ b/platform-paper/src/main/resources/paper-plugin.yml
@@ -42,5 +42,7 @@ permissions:
lunaticchat.spy:
default: op
- lunaticchat.noticeUpdate:
+ lunaticchat.noticeupdate:
+ default: op
+ lunaticchat.channelbypass:
default: op