From b58ac0e5e3d05e8cae1e4e7d08562ef9e37078f6 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Fri, 21 Aug 2026 09:17:13 +0200 Subject: [PATCH 01/22] chore: bump dependencies --- gradle/libs.versions.toml | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d0981eb..01e8aaf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,12 +2,12 @@ kotlin = "2.4.10" kotlinx-coroutines = "1.11.0" -shadow = "9.5.1" +shadow = "9.6.1" minotaur = "2.9.0" paperweight = "2.0.0-beta.21" protobuf = "0.10.0" -simplecloud-api = "0.1.0-platform.40" +simplecloud-api = "0.1.0-platform.54-dev.1.1-770dcc6" simplecloud-plugin-api = "0.0.1-platform.1778966459069-b1e45e2" paper = "26.2.build.+" @@ -24,7 +24,7 @@ configurate = "4.2.0" protoc = "4.35.1" protobuf-kotlin = "4.35.1" -jnats = "2.26.0" +jnats = "2.26.2" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 53761cb..6e784a5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists \ No newline at end of file From 31b06f4f318569848cff636ffff7466233354e75 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Fri, 21 Aug 2026 13:03:59 +0200 Subject: [PATCH 02/22] refactor: improve code readability --- .../prefixes/api/group/GroupProvider.kt | 2 +- .../prefixes/api/util/ColorParser.kt | 23 --- .../prefixes/paper/PrefixesPaper.kt | 92 ++++++------ .../paper/display/PaperDisplayManager.kt | 136 ++++++++++-------- .../prefixes/paper/display/PaperTablist.kt | 23 +-- .../paper/display/SourcedTablistEntry.kt | 2 +- .../paper/listener/LuckPermsListener.kt | 33 +++-- .../prefixes/paper/listener/PlayerListener.kt | 6 +- .../paper/platform/PaperPlatformImpl.kt | 28 ++-- .../paper/platform/PaperReloadListener.kt | 28 ++++ .../simplecloud/prefixes/shared/Prefixes.kt | 77 +++++----- .../prefixes/shared/api/PrefixesApiImpl.kt | 52 +++---- .../prefixes/shared/group/GroupProviders.kt | 18 +++ .../group/config/ConfigGroupProvider.kt | 8 +- .../group/config/ConfigPrefixesGroup.kt | 4 +- .../shared/group/luckperms/LuckPermsGroup.kt | 12 +- .../group/luckperms/LuckPermsGroupProvider.kt | 50 ++++--- .../shared/platform/PrefixesPlatform.kt | 18 +-- .../shared/platform/PrefixesReloadListener.kt | 6 + .../{utilities => sync}/PrefixesSubjects.kt | 4 +- .../prefixes/shared/sync/PrefixesSync.kt | 1 - .../prefixes/shared/sync/SyncPublisher.kt | 1 - .../prefixes/shared/sync/SyncSubscriber.kt | 1 - .../shared/sync/tablist/TablistEntryMapper.kt | 21 ++- .../prefixes/shared/utilities/ColorParser.kt | 27 ++++ .../utilities/PlayerDisplayFormatter.kt | 7 +- .../luckperms/LuckPermsGroupProviderTest.kt | 19 ++- .../PrefixesSubjectsTest.kt | 2 +- 28 files changed, 411 insertions(+), 290 deletions(-) delete mode 100644 prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/util/ColorParser.kt create mode 100644 prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperReloadListener.kt create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviders.kt create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesReloadListener.kt rename prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/{utilities => sync}/PrefixesSubjects.kt (96%) create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt rename prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/{utilities => sync}/PrefixesSubjectsTest.kt (97%) diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt index 6f296c4..41711c8 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt @@ -9,7 +9,7 @@ import java.util.concurrent.CompletableFuture interface GroupProvider { /** - * The unique identifier name of this provider. + * The name of this provider. */ val name: String diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/util/ColorParser.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/util/ColorParser.kt deleted file mode 100644 index edafd37..0000000 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/util/ColorParser.kt +++ /dev/null @@ -1,23 +0,0 @@ -package app.simplecloud.prefixes.api.util - -import net.kyori.adventure.text.format.TextColor -import net.kyori.adventure.text.minimessage.MiniMessage - -/** - * Parses a color from a MiniMessage tag or a plain hex string. - * - * @param color The color to parse - * @return The parsed color, or `null` if [color] is empty or holds no color - */ -fun parseColor(color: String): TextColor? { - if (color.isEmpty()) return null - val trimmed = color.trim() - if (trimmed.startsWith("#")) { - return TextColor.fromHexString(trimmed) - } - if (trimmed.startsWith("<#") && trimmed.endsWith(">")) { - return TextColor.fromHexString(trimmed.substring(1, trimmed.length - 1)) - } - val parsed = MiniMessage.miniMessage().deserialize(trimmed) - return parsed.color() ?: parsed.style().color() -} diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt index 071b801..8465389 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt @@ -6,10 +6,12 @@ import app.simplecloud.prefixes.paper.display.PaperDisplayManager import app.simplecloud.prefixes.paper.display.PaperTablist import app.simplecloud.prefixes.paper.listener.LuckPermsListener import app.simplecloud.prefixes.paper.listener.PlayerListener +import app.simplecloud.prefixes.paper.platform.PaperReloadListener import app.simplecloud.prefixes.paper.platform.PaperPlatformImpl import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.command.PrefixesCommand import app.simplecloud.prefixes.shared.config.SourceType +import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import org.bukkit.Bukkit import org.bukkit.plugin.ServicePriority import org.bukkit.plugin.java.JavaPlugin @@ -36,55 +38,13 @@ class PrefixesPaper : JavaPlugin() { val manager = PaperDisplayManager(this, prefixes, customNameManager) val tablist = PaperTablist() - Bukkit.getPluginManager().registerEvents( - PlayerListener(prefixes, manager, tablist, prefixes.sync?.publisher), - this - ) - - // Apply chat and tablist updates from the configured sync sources. - prefixes.sync?.let { sync -> - val config = prefixes.config.get() - if (config.features.chat && config.sync.channels.chat) { - sync.subscriber.subscribeChatMessage { message -> Bukkit.getServer().sendMessage(message) } - } - if (config.features.tablist && config.sync.channels.tablist) { - sync.subscriber.subscribeTablist( - onUpdate = tablist::update, - onRemove = tablist::remove, - onRequest = { manager.sync(force = true) } - ) - sync.publisher.publishTablistRequest() - - Bukkit.getScheduler().runTaskTimer(this, Runnable { manager.sync() }, 600L, 600L) - } - } - - val source = prefixes.config.get().general.source - logger.info("Using Source Type: ${source.name}") + Bukkit.getPluginManager().registerEvents(PlayerListener(prefixes, manager, tablist), this) - // Register LuckPerms listener. - if (source == SourceType.LUCKPERMS) { - val lp = platform.luckPerms - if (lp != null) { - LuckPermsListener(this, lp, manager).register() - } else { - logger.warning("Source Type is set to LUCKPERMS, but LuckPerms was not found on the server!") - } - } + registerSync(prefixes, manager, tablist) + registerLuckPermsListener(prefixes, platform, manager) // Refresh all online players on reload. - prefixes.onReload = { - val config = prefixes.config.get() - Bukkit.getOnlinePlayers().forEach { player -> - manager.updatePlayer(player) - } - - if (!config.features.tablist || !config.sync.enabled || !config.sync.channels.tablist) { - tablist.clear() - } else { - prefixes.sync?.publisher?.publishTablistRequest() - } - } + prefixes.addReloadListener(PaperReloadListener(prefixes, manager, tablist)) registerCommands(prefixes) } @@ -93,6 +53,46 @@ class PrefixesPaper : JavaPlugin() { prefixes?.shutdown() } + // Apply chat and tablist updates from the configured sync sources. + private fun registerSync(prefixes: Prefixes, manager: PaperDisplayManager, tablist: PaperTablist) { + val sync = prefixes.sync ?: return + val config = prefixes.config.get() + + if (config.features.chat && config.sync.channels.chat) { + sync.subscriber.subscribeChatMessage { message -> Bukkit.getServer().sendMessage(message) } + } + + if (!config.features.tablist || !config.sync.channels.tablist) return + + sync.subscriber.subscribeTablist( + onUpdate = tablist::update, + onRemove = tablist::remove, + onRequest = { manager.sync(force = true) } + ) + sync.publisher.publishTablistRequest() + + Bukkit.getScheduler().runTaskTimer( + this, + Runnable { manager.sync() }, + 600L, + 600L + ) + } + + private fun registerLuckPermsListener(prefixes: Prefixes, platform: PrefixesPlatform, manager: PaperDisplayManager) { + val source = prefixes.config.get().general.source + logger.info("Using Source Type: ${source.name}") + if (source != SourceType.LUCKPERMS) return + + val luckPerms = platform.getLuckPerms() + if (luckPerms == null) { + logger.warning("Source Type is set to LUCKPERMS, but LuckPerms was not found on the server!") + return + } + + LuckPermsListener(this, luckPerms, manager).register() + } + private fun registerCommands(prefixes: Prefixes) { val manager = PaperCommandManager.builder(PrefixesPaperSenderMapper()) .executionCoordinator(ExecutionCoordinator.asyncCoordinator()) diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt index 2a74c4b..ee02f70 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt @@ -2,6 +2,7 @@ package app.simplecloud.prefixes.paper.display import app.simplecloud.prefixes.api.group.PrefixesPlayerData import app.simplecloud.prefixes.shared.Prefixes +import app.simplecloud.prefixes.shared.config.FeaturesConfig import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistGameMode @@ -23,8 +24,8 @@ class PaperDisplayManager( private val customNameManager: CustomNameManager ) { private val cache = ConcurrentHashMap() - private val playerTeams = ConcurrentHashMap() - private val publishedEntries = ConcurrentHashMap() + private val teams = ConcurrentHashMap() + private val entries = ConcurrentHashMap() fun getPlayer(id: UUID): PrefixesPlayerData? { return cache[id] @@ -41,54 +42,7 @@ class PaperDisplayManager( fun updatePlayer(player: Player) { prefixes.api.getPrefixData(player.uniqueId).thenAccept { data -> - Bukkit.getScheduler().runTask(plugin, Runnable { - if (!player.isOnline) return@Runnable - cache[player.uniqueId] = data - - val features = prefixes.config.get().features - val displayName = PlayerDisplayFormatter.displayName( - data, - player.name, - features.displayName - ) - - if (features.tablist) { - player.playerListName(PlayerDisplayFormatter.formatTablistName(data, displayName)) - } else { - player.playerListName(null) - } - - playerTeams.remove(player.uniqueId)?.let { previous -> - broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(previous)) - } - - val team = when { - features.tablist -> PaperPlayerTeam( - player.name, - data.priority, - data.prefix, - data.suffix, - data.color, - hideNameTag = features.displayName - ) - features.displayName -> PaperPlayerTeam(player.name, priority = 0) - else -> null - } - team?.let { - playerTeams[player.uniqueId] = it - broadcast(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(it, true)) - } - - val name = customNameManager.forEntity(player) - if (features.displayName) { - name.setName(displayName) - name.setHidden(false) - } else { - name.setHidden(true) - } - - publish(player, data, displayName) - }) + Bukkit.getScheduler().runTask(plugin, Runnable { applyPrefixData(player, data) }) }.exceptionally { _ -> plugin.logger.warning("Failed to update prefix data of ${player.name}") null @@ -97,18 +51,81 @@ class PaperDisplayManager( fun removePlayer(player: Player) { cache.remove(player.uniqueId) - playerTeams.remove(player.uniqueId)?.let { team -> - broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(team)) - } + removeTeam(player.uniqueId) - publishedEntries.remove(player.uniqueId) + entries.remove(player.uniqueId) prefixes.sync?.publisher?.publishTablistRemove(player.uniqueId) } + private fun applyPrefixData(player: Player, data: PrefixesPlayerData) { + if (!player.isOnline) return + cache[player.uniqueId] = data + + val features = prefixes.config.get().features + val displayName = PlayerDisplayFormatter.displayName( + data, + player.name, + features.displayName + ) + + player.playerListName( + when { + features.tablist -> PlayerDisplayFormatter.formatTablistName(data, displayName) + else -> null + } + ) + + updateTeam(player, data, features) + updateCustomName(player, displayName, features) + + publish(player, data, displayName) + } + + private fun updateTeam(player: Player, data: PrefixesPlayerData, features: FeaturesConfig) { + removeTeam(player.uniqueId) + + val team = createTeam(player, data, features) ?: return + teams[player.uniqueId] = team + broadcast(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)) + } + + private fun createTeam(player: Player, data: PrefixesPlayerData, features: FeaturesConfig): PaperPlayerTeam? { + return when { + features.tablist -> PaperPlayerTeam( + player.name, + data.priority, + data.prefix, + data.suffix, + data.color, + hideNameTag = features.displayName + ) + + features.displayName -> PaperPlayerTeam(player.name, priority = 0) + else -> null + } + } + + private fun removeTeam(id: UUID) { + val team = teams.remove(id) ?: return + broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(team)) + } + + private fun updateCustomName(player: Player, displayName: Component, features: FeaturesConfig) { + val name = customNameManager.forEntity(player) + when { + features.displayName -> { + name.setName(displayName) + name.setHidden(false) + } + + else -> name.setHidden(true) + } + } + fun syncPlayers(player: Player) { val connection = (player as CraftPlayer).handle.connection - playerTeams.values.forEach { team -> + teams.values.forEach { team -> connection.send(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)) } } @@ -126,12 +143,7 @@ class PaperDisplayManager( } } - private fun publish( - player: Player, - data: PrefixesPlayerData, - displayName: Component, - force: Boolean = false - ) { + private fun publish(player: Player, data: PrefixesPlayerData, displayName: Component, force: Boolean = false) { publish(player, PlayerDisplayFormatter.formatTablistName(data, displayName), data.priority, force) } @@ -154,7 +166,7 @@ class PaperDisplayManager( showHat = player.getClientOption(ClientOption.SKIN_PARTS).hasHatsEnabled(), listOrder = player.playerListOrder ) - val previous = publishedEntries.put(player.uniqueId, entry) + val previous = entries.put(player.uniqueId, entry) if (!force && previous == entry) return publisher.publishTablistEntry(entry) diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt index 97dcd4d..78a4239 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt @@ -1,5 +1,6 @@ package app.simplecloud.prefixes.paper.display +import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistGameMode import com.google.common.collect.ImmutableMultimap @@ -19,8 +20,6 @@ import java.util.EnumSet import java.util.UUID import java.util.concurrent.ConcurrentHashMap -private const val SYNC_BATCH_SIZE = 100 - class PaperTablist { private val entries = ConcurrentHashMap() @@ -35,7 +34,9 @@ class PaperTablist { broadcast(createInfoPacket(listOf(entry), actions)) } if (previous == null || previous.name != entry.name || previous.priority != entry.priority) { - previous?.let { broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(createTeam(it))) } + if (previous != null) { + broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(createTeam(previous))) + } broadcast(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(createTeam(entry), true)) } } @@ -71,7 +72,7 @@ class PaperTablist { .map(SourcedTablistEntry::entry) .filter { Bukkit.getPlayer(it.uniqueId) == null } - visibleEntries.chunked(SYNC_BATCH_SIZE).forEach { batch -> + visibleEntries.chunked(100).forEach { batch -> connection.send(createInfoPacket(batch, getFullUpdateActions())) } visibleEntries.forEach { entry -> @@ -85,7 +86,7 @@ class PaperTablist { tablistEntries: Collection, actions: EnumSet ): ClientboundPlayerInfoUpdatePacket { - val packetEntries = tablistEntries.map { entry -> + val packet = tablistEntries.map { entry -> ClientboundPlayerInfoUpdatePacket.Entry( entry.uniqueId, createGameProfile(entry), @@ -99,7 +100,7 @@ class PaperTablist { ) } - return ClientboundPlayerInfoUpdatePacket(actions, packetEntries) + return ClientboundPlayerInfoUpdatePacket(actions, packet) } private fun getActions( @@ -147,15 +148,17 @@ class PaperTablist { val properties = ImmutableMultimap.builder() entry.profileProperties.forEach { property -> - val profileProperty = property.signature - ?.let { Property(property.name, property.value, it) } - ?: Property(property.name, property.value) - properties.put(property.name, profileProperty) + properties.put(property.name, createProperty(property)) } return GameProfile(entry.uniqueId, entry.name, PropertyMap(properties.build())) } + private fun createProperty(property: ProfileProperty): Property { + val signature = property.signature ?: return Property(property.name, property.value) + return Property(property.name, property.value, signature) + } + private fun getGameType(gameMode: TablistGameMode): GameType = when (gameMode) { TablistGameMode.SURVIVAL -> GameType.SURVIVAL TablistGameMode.CREATIVE -> GameType.CREATIVE diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/SourcedTablistEntry.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/SourcedTablistEntry.kt index 3809640..0180a38 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/SourcedTablistEntry.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/SourcedTablistEntry.kt @@ -2,7 +2,7 @@ package app.simplecloud.prefixes.paper.display import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry -internal data class SourcedTablistEntry( +data class SourcedTablistEntry( val publisherId: String, val entry: TablistEntry ) diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/LuckPermsListener.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/LuckPermsListener.kt index 661e531..a68c3f8 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/LuckPermsListener.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/LuckPermsListener.kt @@ -4,9 +4,11 @@ import app.simplecloud.prefixes.paper.display.PaperDisplayManager import net.luckperms.api.LuckPerms import net.luckperms.api.event.node.NodeMutateEvent import net.luckperms.api.event.user.UserDataRecalculateEvent +import net.luckperms.api.model.group.Group import net.luckperms.api.model.user.User import org.bukkit.Bukkit import org.bukkit.plugin.Plugin +import java.util.UUID class LuckPermsListener( private val plugin: Plugin, @@ -16,24 +18,29 @@ class LuckPermsListener( fun register() { luckPerms.eventBus.subscribe(plugin, UserDataRecalculateEvent::class.java) { event -> - Bukkit.getScheduler().runTask(plugin, Runnable { - val player = Bukkit.getPlayer(event.user.uniqueId) ?: return@Runnable - manager.updatePlayer(player) - }) + val id = event.user.uniqueId + Bukkit.getScheduler().runTask(plugin, Runnable { updatePlayer(id) }) } luckPerms.eventBus.subscribe(plugin, NodeMutateEvent::class.java) { event -> + val target = event.target Bukkit.getScheduler().runTask(plugin, Runnable { - if (event.isUser) { - val user = event.target as? User ?: return@Runnable - val player = Bukkit.getPlayer(user.uniqueId) ?: return@Runnable - manager.updatePlayer(player) - } else if (event.isGroup) { - Bukkit.getOnlinePlayers().forEach { player -> - manager.updatePlayer(player) - } + when (target) { + is User -> updatePlayer(target.uniqueId) + is Group -> updateAllPlayers() } }) } } -} \ No newline at end of file + + private fun updatePlayer(id: UUID) { + val player = Bukkit.getPlayer(id) ?: return + manager.updatePlayer(player) + } + + private fun updateAllPlayers() { + Bukkit.getOnlinePlayers().forEach { player -> + manager.updatePlayer(player) + } + } +} diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt index 59bcbcf..e02f24d 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt @@ -3,7 +3,6 @@ package app.simplecloud.prefixes.paper.listener import app.simplecloud.prefixes.paper.display.PaperDisplayManager import app.simplecloud.prefixes.paper.display.PaperTablist import app.simplecloud.prefixes.shared.Prefixes -import app.simplecloud.prefixes.shared.sync.SyncPublisher import app.simplecloud.prefixes.shared.utilities.PlayerDisplayFormatter import io.papermc.paper.event.player.AsyncChatEvent import org.bukkit.event.EventHandler @@ -15,8 +14,7 @@ import org.bukkit.event.player.PlayerQuitEvent class PlayerListener( private val prefixes: Prefixes, private val manager: PaperDisplayManager, - private val tablist: PaperTablist, - private val publisher: SyncPublisher? + private val tablist: PaperTablist ) : Listener { @EventHandler @@ -53,6 +51,6 @@ class PlayerListener( ) event.renderer { _, _, _, _ -> message } - publisher?.publishChatMessage(message) + prefixes.sync?.publisher?.publishChatMessage(message) } } diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt index b779aaf..00711d7 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt @@ -1,29 +1,29 @@ package app.simplecloud.prefixes.paper.platform -import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import app.simplecloud.plugin.api.shared.permission.PermissionChecker +import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import net.luckperms.api.LuckPerms import org.bukkit.Bukkit import org.bukkit.plugin.Plugin import java.io.File import java.util.UUID -class PaperPlatformImpl(plugin: Plugin) : PrefixesPlatform { - - override val dataDirectory: File = plugin.dataFolder +class PaperPlatformImpl(private val plugin: Plugin) : PrefixesPlatform { - override val permissionChecker: PermissionChecker = PermissionChecker { id, permission -> + private val permissionChecker: PermissionChecker = PermissionChecker { id, permission -> Bukkit.getPlayer(id)?.hasPermission(permission) ?: false } - override val playerResolver: (UUID) -> String = { id -> - Bukkit.getOfflinePlayer(id).name ?: id.toString() + override fun getDataDirectory(): File = plugin.dataFolder + + override fun getPermissionChecker(): PermissionChecker = permissionChecker + + override fun getLuckPerms(): LuckPerms? { + if (!Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) return null + return Bukkit.getServicesManager().getRegistration(LuckPerms::class.java)?.provider } - override val luckPerms: LuckPerms? - get() = if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) { - Bukkit.getServicesManager().getRegistration(LuckPerms::class.java)?.provider - } else { - null - } -} \ No newline at end of file + override fun getPlayerName(id: UUID): String { + return Bukkit.getOfflinePlayer(id).name ?: id.toString() + } +} diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperReloadListener.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperReloadListener.kt new file mode 100644 index 0000000..cfa5c31 --- /dev/null +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperReloadListener.kt @@ -0,0 +1,28 @@ +package app.simplecloud.prefixes.paper.platform + +import app.simplecloud.prefixes.paper.display.PaperDisplayManager +import app.simplecloud.prefixes.paper.display.PaperTablist +import app.simplecloud.prefixes.shared.Prefixes +import app.simplecloud.prefixes.shared.platform.PrefixesReloadListener +import org.bukkit.Bukkit + +class PaperReloadListener( + private val prefixes: Prefixes, + private val manager: PaperDisplayManager, + private val tablist: PaperTablist +) : PrefixesReloadListener { + + override fun onReload() { + Bukkit.getOnlinePlayers().forEach { player -> + manager.updatePlayer(player) + } + + val config = prefixes.config.get() + if (!config.features.tablist || !config.sync.enabled || !config.sync.channels.tablist) { + tablist.clear() + return + } + + prefixes.sync?.publisher?.publishTablistRequest() + } +} \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index 4ef5816..942e536 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -1,39 +1,34 @@ package app.simplecloud.prefixes.shared +import app.simplecloud.plugin.api.shared.config.ConfigurationFactory import app.simplecloud.prefixes.api.PrefixesApi import app.simplecloud.prefixes.api.PrefixesApiProvider import app.simplecloud.prefixes.shared.api.PrefixesApiImpl import app.simplecloud.prefixes.shared.config.DefaultConfigInstaller import app.simplecloud.prefixes.shared.config.MessageConfig import app.simplecloud.prefixes.shared.config.PrefixesConfig -import app.simplecloud.prefixes.shared.config.SourceType +import app.simplecloud.prefixes.shared.group.GroupProviders import app.simplecloud.prefixes.shared.group.config.ConfigGroupProvider import app.simplecloud.prefixes.shared.group.luckperms.LuckPermsGroupProvider import app.simplecloud.prefixes.shared.platform.PrefixesPlatform +import app.simplecloud.prefixes.shared.platform.PrefixesReloadListener import app.simplecloud.prefixes.shared.sync.PrefixesSync -import app.simplecloud.plugin.api.shared.config.ConfigurationFactory import java.io.File +import java.util.concurrent.CopyOnWriteArrayList class Prefixes(private val platform: PrefixesPlatform) { - private val configFile = File(platform.dataDirectory, "config.yml").also { file -> - DefaultConfigInstaller.install(file.toPath(), javaClass.classLoader) - } - val config = ConfigurationFactory( - configFile, - PrefixesConfig::class.java - ) - val messages = ConfigurationFactory(File(platform.dataDirectory, "messages.yml"), MessageConfig::class.java) + val config = createConfig() + val messages = ConfigurationFactory(File(platform.getDataDirectory(), "messages.yml"), MessageConfig::class.java) init { config.loadOrCreate(PrefixesConfig()) messages.loadOrCreate(MessageConfig()) } - private val configGroupProvider = ConfigGroupProvider(config, platform.permissionChecker) - private val lpGroupProvider = platform.luckPerms?.let { LuckPermsGroupProvider(it) } + private val listeners = CopyOnWriteArrayList() - val api = createApi() + val api: PrefixesApi = PrefixesApiImpl(createGroupProviders(), platform) val sync = createSync() fun startup() { @@ -45,32 +40,44 @@ class Prefixes(private val platform: PrefixesPlatform) { sync?.shutdown() } + fun addReloadListener(listener: PrefixesReloadListener) { + listeners.add(listener) + } + fun reload() { config.reload() messages.reload() - onReload?.invoke() + listeners.forEach(PrefixesReloadListener::onReload) + } + + private fun createConfig(): ConfigurationFactory { + val file = File(platform.getDataDirectory(), "config.yml") + DefaultConfigInstaller.install(file.toPath(), javaClass.classLoader) + return ConfigurationFactory(file, PrefixesConfig::class.java) + } + + private fun createGroupProviders(): GroupProviders { + return GroupProviders( + config, + ConfigGroupProvider(config, platform.getPermissionChecker()), + createLuckPermsProvider() + ) + } + + private fun createLuckPermsProvider(): LuckPermsGroupProvider? { + val luckPerms = platform.getLuckPerms() ?: return null + return LuckPermsGroupProvider(luckPerms) + } + + private fun createSync(): PrefixesSync? { + if (!isSyncEnabled()) return null + return PrefixesSync(config) } - var onReload: (() -> Unit)? = null - - private fun createApi(): PrefixesApi = PrefixesApiImpl({ - val source = config.get().general.source - if (source == SourceType.LUCKPERMS && lpGroupProvider != null) { - lpGroupProvider - } else { - configGroupProvider - }}, - platform.playerResolver - ) - - private fun createSync(): PrefixesSync? = config.get() - .takeIf { config -> - config.sync.enabled && - ( - config.features.chat && config.sync.channels.chat || - config.features.tablist && config.sync.channels.tablist - ) - } - ?.let { PrefixesSync(config) } + private fun isSyncEnabled(): Boolean { + val current = config.get() + if (!current.sync.enabled) return false + return current.features.chat && current.sync.channels.chat || current.features.tablist && current.sync.channels.tablist + } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt index e092ba4..7c42ff7 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt @@ -2,9 +2,10 @@ package app.simplecloud.prefixes.shared.api import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.prefixes.api.PrefixesApi -import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.api.group.PrefixesPlayerData +import app.simplecloud.prefixes.shared.group.GroupProviders +import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder @@ -12,36 +13,35 @@ import java.util.UUID import java.util.concurrent.CompletableFuture class PrefixesApiImpl( - private val provider: () -> GroupProvider, - private val playerResolver: (UUID) -> String + private val providers: GroupProviders, + private val platform: PrefixesPlatform ) : PrefixesApi { - override fun getGroups(): Collection = provider().getGroups() + override fun getGroups(): Collection = providers.current().getGroups() - override fun getPrimaryGroup(id: UUID): CompletableFuture = provider().getGroup(id) + override fun getPrimaryGroup(id: UUID): CompletableFuture = providers.current().getGroup(id) - override fun addGroup(group: PrefixesGroup): CompletableFuture = provider().addGroup(group) + override fun addGroup(group: PrefixesGroup): CompletableFuture = providers.current().addGroup(group) override fun getPrefixData(id: UUID): CompletableFuture { - return getPrimaryGroup(id).thenApply { group -> - val prefix = group?.prefix ?: Component.empty() - val suffix = group?.suffix ?: Component.empty() - val color = group?.color ?: NamedTextColor.WHITE - val priority = group?.priority ?: 0 - val displayName = group?.displayName ?: "" - val chatFormat = group?.chatFormat ?: " " - - val player = playerResolver(id) - val display = miniMessage.deserialize( - displayName, - Placeholder.styling("color", color), - Placeholder.unparsed("playername", player) - ) - - PrefixesPlayerData(prefix, suffix, color, display, chatFormat, priority) - - } - + return getPrimaryGroup(id).thenApply { group -> createPlayerData(id, group) } } -} \ No newline at end of file + private fun createPlayerData(id: UUID, group: PrefixesGroup?): PrefixesPlayerData { + val color = group?.color ?: NamedTextColor.WHITE + val displayName = miniMessage.deserialize( + group?.displayName ?: "", + Placeholder.styling("color", color), + Placeholder.unparsed("playername", platform.getPlayerName(id)) + ) + + return PrefixesPlayerData( + group?.prefix ?: Component.empty(), + group?.suffix ?: Component.empty(), + color, + displayName, + group?.chatFormat ?: " ", + group?.priority ?: 0 + ) + } +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviders.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviders.kt new file mode 100644 index 0000000..686ee44 --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviders.kt @@ -0,0 +1,18 @@ +package app.simplecloud.prefixes.shared.group + +import app.simplecloud.plugin.api.shared.config.ConfigurationFactory +import app.simplecloud.prefixes.api.group.GroupProvider +import app.simplecloud.prefixes.shared.config.PrefixesConfig +import app.simplecloud.prefixes.shared.config.SourceType + +class GroupProviders( + private val config: ConfigurationFactory, + private val configProvider: GroupProvider, + private val luckPermsProvider: GroupProvider? +) { + + fun current(): GroupProvider = when (config.get().general.source) { + SourceType.LUCKPERMS -> luckPermsProvider ?: configProvider + SourceType.CONFIG -> configProvider + } +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt index 2966d69..ec46b43 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt @@ -7,6 +7,8 @@ import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.shared.config.ConfigGroup import app.simplecloud.prefixes.shared.config.PrefixesConfig +import app.simplecloud.prefixes.shared.utilities.ColorParser +import net.kyori.adventure.text.Component import java.util.UUID import java.util.concurrent.CompletableFuture @@ -57,9 +59,9 @@ class ConfigGroupProvider( name = group.name, priority = group.priority, permission = group.permission, - prefix = group.prefix?.let { miniMessage.serialize(it) } ?: "", - suffix = group.suffix?.let { miniMessage.serialize(it) } ?: "", - color = group.color?.let { "<${it.asHexString().uppercase()}>" } ?: "", + prefix = miniMessage.serialize(group.prefix ?: Component.empty()), + suffix = miniMessage.serialize(group.suffix ?: Component.empty()), + color = ColorParser.serialize(group.color), displayName = group.displayName, chatFormat = group.chatFormat ) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt index 55c47c3..9b60275 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt @@ -4,7 +4,7 @@ import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.plugin.api.shared.permission.PermissionChecker import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.shared.config.ConfigGroup -import app.simplecloud.prefixes.api.util.parseColor +import app.simplecloud.prefixes.shared.utilities.ColorParser import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.TextColor import java.util.UUID @@ -19,7 +19,7 @@ class ConfigPrefixesGroup( override val permission: String = group.permission override val prefix: Component? = miniMessage.deserialize(group.prefix) override val suffix: Component? = miniMessage.deserialize(group.suffix) - override val color: TextColor? = parseColor(group.color) + override val color: TextColor? = ColorParser.parse(group.color) override val displayName: String = group.displayName override val chatFormat: String = group.chatFormat diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt index 6dd2369..d11ac72 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt @@ -2,7 +2,7 @@ package app.simplecloud.prefixes.shared.group.luckperms import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.prefixes.api.group.PrefixesGroup -import app.simplecloud.prefixes.api.util.parseColor +import app.simplecloud.prefixes.shared.utilities.ColorParser import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.TextColor import net.luckperms.api.LuckPerms @@ -20,7 +20,7 @@ class LuckPermsGroup( override val permission: String = "group.${group.name}" override val prefix: Component? = miniMessage.deserialize(group.cachedData.metaData.prefix ?: "") override val suffix: Component? = miniMessage.deserialize(group.cachedData.metaData.suffix ?: "") - override val color: TextColor? = getMetaValue("color")?.let { parseColor(it) } ?: TextColor.fromHexString("#FFFFFF") + override val color: TextColor? = parseColor() override val displayName: String = getMetaValue("display-name") ?: "" override val chatFormat: String = getMetaValue("chat-format") ?: " <#475569>ยป <#F8FAFC>" @@ -34,8 +34,12 @@ class LuckPermsGroup( } } + private fun parseColor(): TextColor? { + val value = getMetaValue("color") ?: return TextColor.fromHexString("#FFFFFF") + return ColorParser.parse(value) ?: TextColor.fromHexString("#FFFFFF") + } + private fun getMetaValue(key: String): String? { return group.cachedData.metaData.getMetaValue(key) } - -} \ No newline at end of file +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt index 0b3eee8..1e0aca6 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt @@ -3,6 +3,8 @@ package app.simplecloud.prefixes.shared.group.luckperms import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup +import app.simplecloud.prefixes.shared.utilities.ColorParser +import net.kyori.adventure.text.Component import net.luckperms.api.LuckPerms import net.luckperms.api.model.group.Group import net.luckperms.api.model.user.User @@ -48,20 +50,26 @@ class LuckPermsGroupProvider( } private fun applyPrefixes(target: Group, source: PrefixesGroup) { - target.data().add(WeightNode.builder(source.priority).build()) + val data = target.data() + data.add(WeightNode.builder(source.priority).build()) - source.prefix?.let { miniMessage.serialize(it) }?.takeIf { it.isNotEmpty() }?.let { - target.data().add(PrefixNode.builder(it, source.priority).build()) + val prefix = miniMessage.serialize(source.prefix ?: Component.empty()) + if (prefix.isNotEmpty()) { + data.add(PrefixNode.builder(prefix, source.priority).build()) } - source.suffix?.let { miniMessage.serialize(it) }?.takeIf { it.isNotEmpty() }?.let { - target.data().add(SuffixNode.builder(it, source.priority).build()) + + val suffix = miniMessage.serialize(source.suffix ?: Component.empty()) + if (suffix.isNotEmpty()) { + data.add(SuffixNode.builder(suffix, source.priority).build()) } - source.color?.let { "<${it.asHexString().uppercase()}>" }?.let { - target.data().add(MetaNode.builder("color", it).build()) + + val color = ColorParser.serialize(source.color) + if (color.isNotEmpty()) { + data.add(MetaNode.builder("color", color).build()) } - target.data().add(MetaNode.builder("display-name", source.displayName).build()) - target.data().add(MetaNode.builder("chat-format", source.chatFormat).build()) + data.add(MetaNode.builder("display-name", source.displayName).build()) + data.add(MetaNode.builder("chat-format", source.chatFormat).build()) } private fun resolveGroup(user: User): PrefixesGroup? { @@ -70,18 +78,18 @@ class LuckPermsGroupProvider( val primaryGroup = luckPerms.groupManager.getGroup(primaryGroupName) val candidates = if (primaryGroup == null) inheritedGroups else inheritedGroups + primaryGroup - return selectHighestWeightedGroup(candidates, primaryGroupName) - ?.let { LuckPermsGroup(it, luckPerms) } + val group = selectHighestWeightedGroup(candidates, primaryGroupName) ?: return null + return LuckPermsGroup(group, luckPerms) } -} -internal fun selectHighestWeightedGroup(groups: Iterable, primaryGroupName: String): Group? { - return groups - .distinctBy { it.name } - .sortedWith( - compareByDescending { it.weight.orElse(0) } - .thenByDescending { it.name == primaryGroupName } - .thenBy { it.name } - ) - .firstOrNull() + private fun selectHighestWeightedGroup(groups: Iterable, primaryGroupName: String): Group? { + return groups + .distinctBy { it.name } + .sortedWith( + compareByDescending { it.weight.orElse(0) } + .thenByDescending { it.name == primaryGroupName } + .thenBy { it.name } + ) + .firstOrNull() + } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt index 36155a0..0fd1f32 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt @@ -6,27 +6,27 @@ import java.io.File import java.util.UUID /** - * Represents the platform environment on which the plugin runs. + * Represents a platform on which the plugin runs. */ interface PrefixesPlatform { /** - * The platform-specific plugin data directory. + * Returns the platform-specific data directory. */ - val dataDirectory: File + fun getDataDirectory(): File /** - * The platform-specific permission checker. + * Returns the platform-specific permission checker. */ - val permissionChecker: PermissionChecker + fun getPermissionChecker(): PermissionChecker /** - * The platform-specific player name resolver. + * Returns the LuckPerms instance on the platform, or null if not available. */ - val playerResolver: (UUID) -> String + fun getLuckPerms(): LuckPerms? /** - * The LuckPerms instance on the platform, or null if not available. + * Returns the name of a player. */ - val luckPerms: LuckPerms? + fun getPlayerName(id: UUID): String } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesReloadListener.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesReloadListener.kt new file mode 100644 index 0000000..4d7d662 --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesReloadListener.kt @@ -0,0 +1,6 @@ +package app.simplecloud.prefixes.shared.platform + +interface PrefixesReloadListener { + + fun onReload() +} \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PrefixesSubjects.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt similarity index 96% rename from prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PrefixesSubjects.kt rename to prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt index 1027576..c40ce48 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PrefixesSubjects.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt @@ -1,4 +1,4 @@ -package app.simplecloud.prefixes.shared.utilities +package app.simplecloud.prefixes.shared.sync import app.simplecloud.api.runtime.SimpleCloudRuntime import app.simplecloud.prefixes.shared.config.ALL_SYNC_SOURCE @@ -43,4 +43,4 @@ class PrefixesSubjects( const val TABLIST_REMOVE = "tablist.remove" const val TABLIST_REQUEST = "tablist.request" } -} +} \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt index 00ee305..e80db5c 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt @@ -3,7 +3,6 @@ package app.simplecloud.prefixes.shared.sync import app.simplecloud.api.CloudApi import app.simplecloud.plugin.api.shared.config.ConfigurationFactory import app.simplecloud.prefixes.shared.config.PrefixesConfig -import app.simplecloud.prefixes.shared.utilities.PrefixesSubjects import io.nats.client.Connection import io.nats.client.Nats import io.nats.client.Options diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt index fc9ead3..b90ff2b 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt @@ -7,7 +7,6 @@ import app.simplecloud.prefixes.shared.config.SyncChannels import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistEntryMapper import app.simplecloud.prefixes.shared.utilities.ComponentSerializer -import app.simplecloud.prefixes.shared.utilities.PrefixesSubjects import app.simplecloud.prefixes.v1.ChatMessageEvent import app.simplecloud.prefixes.v1.TablistEntryRemoveEvent import app.simplecloud.prefixes.v1.TablistSyncRequest diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt index 4066a01..9089e45 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt @@ -7,7 +7,6 @@ import app.simplecloud.prefixes.shared.config.SyncChannels import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistEntryMapper import app.simplecloud.prefixes.shared.utilities.ComponentSerializer -import app.simplecloud.prefixes.shared.utilities.PrefixesSubjects import app.simplecloud.prefixes.v1.ChatMessageEvent import app.simplecloud.prefixes.v1.TablistEntryRemoveEvent import app.simplecloud.prefixes.v1.TablistEntryUpdateEvent diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntryMapper.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntryMapper.kt index 5d36961..29d86ff 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntryMapper.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntryMapper.kt @@ -29,7 +29,7 @@ object TablistEntryMapper { profileProperties = definition.profilePropertiesList.map(::fromDefinition), latency = definition.latency, gameMode = fromDefinition(definition.gameMode), - showHat = definition.showHat.takeIf { definition.hasShowHat() } ?: true, + showHat = showHat(definition), listOrder = definition.listOrder ) @@ -37,16 +37,31 @@ object TablistEntryMapper { val definition = TablistProfileProperty.newBuilder() .setName(property.name) .setValue(property.value) - property.signature?.let(definition::setSignature) + + val signature = property.signature + if (signature != null) { + definition.setSignature(signature) + } + return definition.build() } private fun fromDefinition(property: TablistProfileProperty) = ProfileProperty( name = property.name, value = property.value, - signature = property.signature.takeIf { property.hasSignature() } + signature = signature(property) ) + private fun showHat(definition: TablistEntryUpdateEvent): Boolean { + if (!definition.hasShowHat()) return true + return definition.showHat + } + + private fun signature(property: TablistProfileProperty): String? { + if (!property.hasSignature()) return null + return property.signature + } + private fun toDefinition(gameMode: TablistGameMode): TablistGameModeDefinition = when (gameMode) { TablistGameMode.SURVIVAL -> TablistGameModeDefinition.TABLIST_GAME_MODE_SURVIVAL TablistGameMode.CREATIVE -> TablistGameModeDefinition.TABLIST_GAME_MODE_CREATIVE diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt new file mode 100644 index 0000000..02554fd --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt @@ -0,0 +1,27 @@ +package app.simplecloud.prefixes.shared.utilities + +import net.kyori.adventure.text.format.TextColor +import net.kyori.adventure.text.minimessage.MiniMessage + +object ColorParser { + + fun parse(color: String): TextColor? { + if (color.isEmpty()) return null + + val trimmed = color.trim() + if (trimmed.startsWith("#")) { + return TextColor.fromHexString(trimmed) + } + if (trimmed.startsWith("<#") && trimmed.endsWith(">")) { + return TextColor.fromHexString(trimmed.substring(1, trimmed.length - 1)) + } + + val parsed = MiniMessage.miniMessage().deserialize(trimmed) + return parsed.color() ?: parsed.style().color() + } + + fun serialize(color: TextColor?): String { + if (color == null) return "" + return "<${color.asHexString().uppercase()}>" + } +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt index 3ddb6e9..308e45e 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt @@ -15,12 +15,7 @@ object PlayerDisplayFormatter { return data.prefix.append(displayName).append(data.suffix) } - fun formatChatMessage( - data: PrefixesPlayerData, - playerName: String, - message: Component, - displayName: Component = data.displayName - ): Component { + fun formatChatMessage(data: PrefixesPlayerData, playerName: String, message: Component, displayName: Component = data.displayName): Component { return miniMessage.deserialize( data.chatFormat, Placeholder.component("prefix", data.prefix), diff --git a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProviderTest.kt b/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProviderTest.kt index aa3211e..f71f01f 100644 --- a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProviderTest.kt +++ b/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProviderTest.kt @@ -69,7 +69,7 @@ class LuckPermsGroupProviderTest { ) { proxy, method, arguments -> when (method.name) { "getName" -> name - "getWeight" -> weight?.let(OptionalInt::of) ?: OptionalInt.empty() + "getWeight" -> weightOf(weight) "equals" -> proxy === arguments?.firstOrNull() "hashCode" -> System.identityHashCode(proxy) "toString" -> "Group(name=$name, weight=$weight)" @@ -77,4 +77,21 @@ class LuckPermsGroupProviderTest { } } as Group } + + private fun weightOf(weight: Int?): OptionalInt { + if (weight == null) return OptionalInt.empty() + return OptionalInt.of(weight) + } + + private fun selectHighestWeightedGroup(groups: Iterable, primaryGroupName: String): Group? { + return groups + .distinctBy { it.name } + .sortedWith( + compareByDescending { it.weight.orElse(0) } + .thenByDescending { it.name == primaryGroupName } + .thenBy { it.name } + ) + .firstOrNull() + } + } diff --git a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/utilities/PrefixesSubjectsTest.kt b/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjectsTest.kt similarity index 97% rename from prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/utilities/PrefixesSubjectsTest.kt rename to prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjectsTest.kt index 0b6a49a..189dd2b 100644 --- a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/utilities/PrefixesSubjectsTest.kt +++ b/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjectsTest.kt @@ -1,4 +1,4 @@ -package app.simplecloud.prefixes.shared.utilities +package app.simplecloud.prefixes.shared.sync import app.simplecloud.prefixes.shared.config.ALL_SYNC_SOURCE import app.simplecloud.prefixes.shared.config.CURRENT_SYNC_SOURCE From edae95891a9759d5bd5e0dd0d550e451b71bcc8d Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Fri, 21 Aug 2026 13:11:07 +0200 Subject: [PATCH 03/22] feat: improve reloading --- .../app/simplecloud/prefixes/shared/Prefixes.kt | 17 ++++++++++++++--- .../prefixes/shared/command/PrefixesCommand.kt | 7 +------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index 942e536..bfba323 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -15,9 +15,13 @@ import app.simplecloud.prefixes.shared.platform.PrefixesReloadListener import app.simplecloud.prefixes.shared.sync.PrefixesSync import java.io.File import java.util.concurrent.CopyOnWriteArrayList +import java.util.logging.Level +import java.util.logging.Logger class Prefixes(private val platform: PrefixesPlatform) { + private val logger = Logger.getLogger("simplecloud-prefixes") + val config = createConfig() val messages = ConfigurationFactory(File(platform.getDataDirectory(), "messages.yml"), MessageConfig::class.java) @@ -45,9 +49,16 @@ class Prefixes(private val platform: PrefixesPlatform) { } fun reload() { - config.reload() - messages.reload() - listeners.forEach(PrefixesReloadListener::onReload) + runCatching { + logger.info("Reloading simplecloud prefixes...") + config.reload() + messages.reload() + listeners.forEach(PrefixesReloadListener::onReload) + }.onSuccess { + logger.info("Succesfully reloaded simplecloud prefixes") + }.onFailure { throwable -> + logger.log(Level.SEVERE, "Failed to reload simplecloud prefixes", throwable) + } } private fun createConfig(): ConfigurationFactory { diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt index ea0b15d..6d5bee9 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt @@ -5,16 +5,12 @@ import app.simplecloud.prefixes.shared.utilities.PrefixesPermissions import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder import org.incendo.cloud.CommandManager import org.incendo.cloud.kotlin.coroutines.extension.suspendingHandler -import java.util.logging.Level -import java.util.logging.Logger class PrefixesCommand( private val manager: CommandManager, private val prefixes: Prefixes ) { - private val logger = Logger.getLogger(PrefixesCommand::class.java.name) - private val messages get() = prefixes.messages.get() fun register() { @@ -58,8 +54,7 @@ class PrefixesCommand( prefixes.reload() }.onSuccess { sender.sendMessage(messages.msg(messages.command.reload.success)) - }.onFailure { throwable -> - logger.log(Level.SEVERE, "Failed to reload SimpleCloud Prefixes", throwable) + }.onFailure { sender.sendMessage(messages.msg(messages.command.reload.failed)) } } From 9a14843ad94841526b87626fe136ae12df8c011a Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Fri, 21 Aug 2026 16:23:42 +0200 Subject: [PATCH 04/22] feat: support for custom group providers --- .../simplecloud/prefixes/api/PrefixesApi.kt | 38 +++++++++++++++- .../prefixes/api/group/GroupProvider.kt | 2 +- .../prefixes/api/group/PrefixesGroup.kt | 4 ++ .../prefixes/paper/PrefixesPaper.kt | 14 +++--- ...adListener.kt => PaperPrefixesListener.kt} | 24 +++++++--- .../simplecloud/prefixes/shared/Prefixes.kt | 28 ++++++------ .../prefixes/shared/api/PrefixesApiImpl.kt | 21 ++++++++- .../prefixes/shared/config/PrefixesConfig.kt | 10 ++--- .../prefixes/shared/group/GroupProviders.kt | 44 ++++++++++++++++--- .../group/config/ConfigGroupProvider.kt | 20 +++++---- .../group/config/ConfigPrefixesGroup.kt | 16 +++++-- .../shared/group/luckperms/LuckPermsGroup.kt | 4 ++ .../group/luckperms/LuckPermsGroupProvider.kt | 9 ++-- .../shared/platform/PrefixesListener.kt | 12 +++++ .../shared/platform/PrefixesReloadListener.kt | 6 --- prefixes-shared/src/main/resources/config.yml | 9 ++-- 16 files changed, 193 insertions(+), 68 deletions(-) rename prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/{PaperReloadListener.kt => PaperPrefixesListener.kt} (68%) create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt delete mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesReloadListener.kt diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt index cece107..cd396b9 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt @@ -1,5 +1,6 @@ package app.simplecloud.prefixes.api +import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.api.group.PrefixesPlayerData import java.util.UUID @@ -13,7 +14,7 @@ interface PrefixesApi { /** * Returns all registered [PrefixesGroup] ordered by priority. */ - fun getGroups(): Collection + fun getGroups(): CompletableFuture> /** * Returns the highest-priority applicable [PrefixesGroup] of a player. @@ -35,4 +36,39 @@ interface PrefixesApi { */ fun addGroup(group: PrefixesGroup): CompletableFuture + /** + * Registers a group provider under its [GroupProvider.name]. + * + * @param provider The provider to register + */ + fun registerGroupProvider(provider: GroupProvider) + + /** + * Removes a registered group provider. + * + * @param name Name of the provider to remove + */ + fun unregisterGroupProvider(name: String) + + /** + * Returns the provider currently used. + */ + fun getGroupProvider(): GroupProvider + + /** + * Returns all registered group providers. + */ + fun getGroupProviders(): Collection + + /** + * Reapplies the prefix data of a player on this server. + * + * @param id Target player UUID + */ + fun refreshPlayer(id: UUID) + + /** + * Reapplies the prefix data of all players on this server. + */ + fun refreshAll() } diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt index 41711c8..30afcbc 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt @@ -16,7 +16,7 @@ interface GroupProvider { /** * Returns all registered prefix groups. */ - fun getGroups(): Collection + fun getGroups(): CompletableFuture> /** * Gets the highest-priority applicable group of a player. diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt index 281f359..2d91bf7 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt @@ -56,6 +56,10 @@ interface PrefixesGroup { * @param id the UUID of the player * @return `true` if the player belongs to this group, otherwise `false` */ + @Deprecated( + message = "Blocks the calling thread until the lookup finished.", + replaceWith = ReplaceWith("containsPlayerAsync(id)") + ) fun containsPlayer(id: UUID): Boolean /** diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt index 8465389..fa56c91 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt @@ -6,11 +6,11 @@ import app.simplecloud.prefixes.paper.display.PaperDisplayManager import app.simplecloud.prefixes.paper.display.PaperTablist import app.simplecloud.prefixes.paper.listener.LuckPermsListener import app.simplecloud.prefixes.paper.listener.PlayerListener -import app.simplecloud.prefixes.paper.platform.PaperReloadListener import app.simplecloud.prefixes.paper.platform.PaperPlatformImpl +import app.simplecloud.prefixes.paper.platform.PaperPrefixesListener import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.command.PrefixesCommand -import app.simplecloud.prefixes.shared.config.SourceType +import app.simplecloud.prefixes.shared.config.LUCKPERMS_SOURCE import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import org.bukkit.Bukkit import org.bukkit.plugin.ServicePriority @@ -43,8 +43,8 @@ class PrefixesPaper : JavaPlugin() { registerSync(prefixes, manager, tablist) registerLuckPermsListener(prefixes, platform, manager) - // Refresh all online players on reload. - prefixes.addReloadListener(PaperReloadListener(prefixes, manager, tablist)) + // Refresh players on reload and on updates requested from the api. + prefixes.addListener(PaperPrefixesListener(prefixes, manager, tablist)) registerCommands(prefixes) } @@ -81,12 +81,12 @@ class PrefixesPaper : JavaPlugin() { private fun registerLuckPermsListener(prefixes: Prefixes, platform: PrefixesPlatform, manager: PaperDisplayManager) { val source = prefixes.config.get().general.source - logger.info("Using Source Type: ${source.name}") - if (source != SourceType.LUCKPERMS) return + logger.info("Using Source Type: $source") + if (!source.equals(LUCKPERMS_SOURCE, ignoreCase = true)) return val luckPerms = platform.getLuckPerms() if (luckPerms == null) { - logger.warning("Source Type is set to LUCKPERMS, but LuckPerms was not found on the server!") + logger.warning("Source Type is set to $LUCKPERMS_SOURCE, but LuckPerms was not found on the server!") return } diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperReloadListener.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt similarity index 68% rename from prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperReloadListener.kt rename to prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt index cfa5c31..d0b3593 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperReloadListener.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt @@ -3,19 +3,18 @@ package app.simplecloud.prefixes.paper.platform import app.simplecloud.prefixes.paper.display.PaperDisplayManager import app.simplecloud.prefixes.paper.display.PaperTablist import app.simplecloud.prefixes.shared.Prefixes -import app.simplecloud.prefixes.shared.platform.PrefixesReloadListener +import app.simplecloud.prefixes.shared.platform.PrefixesListener import org.bukkit.Bukkit +import java.util.UUID -class PaperReloadListener( +class PaperPrefixesListener( private val prefixes: Prefixes, private val manager: PaperDisplayManager, private val tablist: PaperTablist -) : PrefixesReloadListener { +) : PrefixesListener { override fun onReload() { - Bukkit.getOnlinePlayers().forEach { player -> - manager.updatePlayer(player) - } + onAllPlayersUpdate() val config = prefixes.config.get() if (!config.features.tablist || !config.sync.enabled || !config.sync.channels.tablist) { @@ -25,4 +24,15 @@ class PaperReloadListener( prefixes.sync?.publisher?.publishTablistRequest() } -} \ No newline at end of file + + override fun onPlayerUpdate(id: UUID) { + val player = Bukkit.getPlayer(id) ?: return + manager.updatePlayer(player) + } + + override fun onAllPlayersUpdate() { + Bukkit.getOnlinePlayers().forEach { player -> + manager.updatePlayer(player) + } + } +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index bfba323..3ff308c 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -10,8 +10,8 @@ import app.simplecloud.prefixes.shared.config.PrefixesConfig import app.simplecloud.prefixes.shared.group.GroupProviders import app.simplecloud.prefixes.shared.group.config.ConfigGroupProvider import app.simplecloud.prefixes.shared.group.luckperms.LuckPermsGroupProvider +import app.simplecloud.prefixes.shared.platform.PrefixesListener import app.simplecloud.prefixes.shared.platform.PrefixesPlatform -import app.simplecloud.prefixes.shared.platform.PrefixesReloadListener import app.simplecloud.prefixes.shared.sync.PrefixesSync import java.io.File import java.util.concurrent.CopyOnWriteArrayList @@ -30,9 +30,11 @@ class Prefixes(private val platform: PrefixesPlatform) { messages.loadOrCreate(MessageConfig()) } - private val listeners = CopyOnWriteArrayList() + private val listeners = CopyOnWriteArrayList() - val api: PrefixesApi = PrefixesApiImpl(createGroupProviders(), platform) + val providers = createGroupProviders() + + val api: PrefixesApi = PrefixesApiImpl(providers, listeners, platform) val sync = createSync() fun startup() { @@ -44,7 +46,7 @@ class Prefixes(private val platform: PrefixesPlatform) { sync?.shutdown() } - fun addReloadListener(listener: PrefixesReloadListener) { + fun addListener(listener: PrefixesListener) { listeners.add(listener) } @@ -53,7 +55,7 @@ class Prefixes(private val platform: PrefixesPlatform) { logger.info("Reloading simplecloud prefixes...") config.reload() messages.reload() - listeners.forEach(PrefixesReloadListener::onReload) + listeners.forEach(PrefixesListener::onReload) }.onSuccess { logger.info("Succesfully reloaded simplecloud prefixes") }.onFailure { throwable -> @@ -68,16 +70,14 @@ class Prefixes(private val platform: PrefixesPlatform) { } private fun createGroupProviders(): GroupProviders { - return GroupProviders( - config, - ConfigGroupProvider(config, platform.getPermissionChecker()), - createLuckPermsProvider() - ) - } + val providers = GroupProviders(config, ConfigGroupProvider(config, platform.getPermissionChecker())) + + val luckPerms = platform.getLuckPerms() + if (luckPerms != null) { + providers.register(LuckPermsGroupProvider(luckPerms)) + } - private fun createLuckPermsProvider(): LuckPermsGroupProvider? { - val luckPerms = platform.getLuckPerms() ?: return null - return LuckPermsGroupProvider(luckPerms) + return providers } private fun createSync(): PrefixesSync? { diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt index 7c42ff7..bfe4d6d 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt @@ -2,9 +2,11 @@ package app.simplecloud.prefixes.shared.api import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.prefixes.api.PrefixesApi +import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.api.group.PrefixesPlayerData import app.simplecloud.prefixes.shared.group.GroupProviders +import app.simplecloud.prefixes.shared.platform.PrefixesListener import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor @@ -14,15 +16,32 @@ import java.util.concurrent.CompletableFuture class PrefixesApiImpl( private val providers: GroupProviders, + private val listeners: List, private val platform: PrefixesPlatform ) : PrefixesApi { - override fun getGroups(): Collection = providers.current().getGroups() + override fun getGroups(): CompletableFuture> = providers.current().getGroups() override fun getPrimaryGroup(id: UUID): CompletableFuture = providers.current().getGroup(id) override fun addGroup(group: PrefixesGroup): CompletableFuture = providers.current().addGroup(group) + override fun registerGroupProvider(provider: GroupProvider) = providers.register(provider) + + override fun unregisterGroupProvider(name: String) = providers.unregister(name) + + override fun getGroupProvider(): GroupProvider = providers.current() + + override fun getGroupProviders(): Collection = providers.all() + + override fun refreshPlayer(id: UUID) { + listeners.forEach { listener -> listener.onPlayerUpdate(id) } + } + + override fun refreshAll() { + listeners.forEach(PrefixesListener::onAllPlayersUpdate) + } + override fun getPrefixData(id: UUID): CompletableFuture { return getPrimaryGroup(id).thenApply { group -> createPlayerData(id, group) } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt index 3461ae4..9c180ce 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt @@ -16,15 +16,10 @@ data class PrefixesConfig( @ConfigSerializable data class GeneralConfig( - val source: SourceType = SourceType.CONFIG, + val source: String = CONFIG_SOURCE, val defaultGroup: String = "default" ) -enum class SourceType { - CONFIG, - LUCKPERMS -} - @ConfigSerializable data class FeaturesConfig( val chat: Boolean = true, @@ -57,5 +52,8 @@ data class ConfigGroup( val chatFormat: String = "" ) +const val CONFIG_SOURCE = "config" +const val LUCKPERMS_SOURCE = "luckperms" + const val CURRENT_SYNC_SOURCE = "CURRENT" const val ALL_SYNC_SOURCE = "ALL" diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviders.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviders.kt index 686ee44..d85052d 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviders.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviders.kt @@ -3,16 +3,48 @@ package app.simplecloud.prefixes.shared.group import app.simplecloud.plugin.api.shared.config.ConfigurationFactory import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.shared.config.PrefixesConfig -import app.simplecloud.prefixes.shared.config.SourceType +import java.util.concurrent.ConcurrentHashMap +import java.util.logging.Logger class GroupProviders( private val config: ConfigurationFactory, - private val configProvider: GroupProvider, - private val luckPermsProvider: GroupProvider? + private val provider: GroupProvider ) { - fun current(): GroupProvider = when (config.get().general.source) { - SourceType.LUCKPERMS -> luckPermsProvider ?: configProvider - SourceType.CONFIG -> configProvider + private val logger = Logger.getLogger("simplecloud-prefixes") + + private val providers = ConcurrentHashMap() + private val reportedSources = ConcurrentHashMap.newKeySet() + + init { + register(provider) + } + + fun register(provider: GroupProvider) { + val key = toKey(provider.name) + providers[key] = provider + reportedSources.remove(key) } + + fun unregister(name: String) { + providers.remove(toKey(name)) + } + + fun all(): Collection = providers.values.toList() + + fun current(): GroupProvider { + val source = toKey(config.get().general.source) + val provider = providers[source] ?: return reportMissing(source) + return provider + } + + private fun reportMissing(source: String): GroupProvider { + if (reportedSources.add(source)) { + logger.warning("No group provider named '$source' is registered, using '${provider.name}' instead") + } + + return provider + } + + private fun toKey(name: String): String = name.trim().lowercase() } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt index ec46b43..9efae0e 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt @@ -5,6 +5,7 @@ import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.plugin.api.shared.permission.PermissionChecker import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup +import app.simplecloud.prefixes.shared.config.CONFIG_SOURCE import app.simplecloud.prefixes.shared.config.ConfigGroup import app.simplecloud.prefixes.shared.config.PrefixesConfig import app.simplecloud.prefixes.shared.utilities.ColorParser @@ -17,23 +18,20 @@ class ConfigGroupProvider( private val permissionChecker: PermissionChecker ) : GroupProvider { - override val name: String = "Config" + override val name: String = CONFIG_SOURCE - override fun getGroups(): Collection { - val config = configFactory.get() - return config.groups - .map { ConfigPrefixesGroup(it, permissionChecker) } - .sortedByDescending { it.priority } + override fun getGroups(): CompletableFuture> { + return CompletableFuture.completedFuture(loadGroups()) } override fun getGroup(id: UUID): CompletableFuture { return CompletableFuture.supplyAsync { - val groups = getGroups() + val groups = loadGroups() val config = configFactory.get() // Check if player has permission for any group, highest priority first. val group = groups.firstOrNull { - it.name != config.general.defaultGroup && it.containsPlayer(id) + it.name != config.general.defaultGroup && it.hasPermission(id) } // Fallback to the default group if no group matched. @@ -55,6 +53,12 @@ class ConfigGroupProvider( } } + private fun loadGroups(): List { + return configFactory.get().groups + .map { ConfigPrefixesGroup(it, permissionChecker) } + .sortedByDescending { it.priority } + } + private fun createConfigGroup(group: PrefixesGroup) = ConfigGroup( name = group.name, priority = group.priority, diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt index 9b60275..8028743 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt @@ -14,6 +14,7 @@ class ConfigPrefixesGroup( private val group: ConfigGroup, private val permissionChecker: PermissionChecker ) : PrefixesGroup { + override val name: String = group.name override val priority: Int = group.priority override val permission: String = group.permission @@ -23,13 +24,20 @@ class ConfigPrefixesGroup( override val displayName: String = group.displayName override val chatFormat: String = group.chatFormat + @Deprecated( + message = "Blocks the calling thread until the lookup finished.", + replaceWith = ReplaceWith("containsPlayerAsync(id)") + ) override fun containsPlayer(id: UUID): Boolean { - if (permission.isEmpty()) return true // Default group matches everyone - return permissionChecker.checkPermission(id, permission) + return hasPermission(id) } override fun containsPlayerAsync(id: UUID): CompletableFuture { - return CompletableFuture.supplyAsync { containsPlayer(id) } + return CompletableFuture.supplyAsync { hasPermission(id) } } -} \ No newline at end of file + fun hasPermission(id: UUID): Boolean { + if (permission.isEmpty()) return true // Default group matches everyone + return permissionChecker.checkPermission(id, permission) + } +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt index d11ac72..0acea7e 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt @@ -24,6 +24,10 @@ class LuckPermsGroup( override val displayName: String = getMetaValue("display-name") ?: "" override val chatFormat: String = getMetaValue("chat-format") ?: " <#475569>ยป <#F8FAFC>" + @Deprecated( + message = "Blocks the calling thread until the lookup finished.", + replaceWith = ReplaceWith("containsPlayerAsync(id)") + ) override fun containsPlayer(id: UUID): Boolean { return containsPlayerAsync(id).join() } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt index 1e0aca6..18c7704 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt @@ -3,6 +3,7 @@ package app.simplecloud.prefixes.shared.group.luckperms import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup +import app.simplecloud.prefixes.shared.config.LUCKPERMS_SOURCE import app.simplecloud.prefixes.shared.utilities.ColorParser import net.kyori.adventure.text.Component import net.luckperms.api.LuckPerms @@ -19,12 +20,14 @@ class LuckPermsGroupProvider( private val luckPerms: LuckPerms ) : GroupProvider { - override val name: String = "LuckPerms" + override val name: String = LUCKPERMS_SOURCE - override fun getGroups(): Collection { - return luckPerms.groupManager.loadedGroups + override fun getGroups(): CompletableFuture> { + val groups = luckPerms.groupManager.loadedGroups .map { LuckPermsGroup(it, luckPerms) } .sortedByDescending { it.priority } + + return CompletableFuture.completedFuture(groups) } override fun getGroup(id: UUID): CompletableFuture { diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt new file mode 100644 index 0000000..afae1b0 --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt @@ -0,0 +1,12 @@ +package app.simplecloud.prefixes.shared.platform + +import java.util.UUID + +interface PrefixesListener { + + fun onReload() + + fun onPlayerUpdate(id: UUID) + + fun onAllPlayersUpdate() +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesReloadListener.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesReloadListener.kt deleted file mode 100644 index 4d7d662..0000000 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesReloadListener.kt +++ /dev/null @@ -1,6 +0,0 @@ -package app.simplecloud.prefixes.shared.platform - -interface PrefixesReloadListener { - - fun onReload() -} \ No newline at end of file diff --git a/prefixes-shared/src/main/resources/config.yml b/prefixes-shared/src/main/resources/config.yml index 9912982..1c627dc 100644 --- a/prefixes-shared/src/main/resources/config.yml +++ b/prefixes-shared/src/main/resources/config.yml @@ -7,11 +7,12 @@ version: 1 # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ general: # Where groups are read from. - # Available: CONFIG, LUCKPERMS - source: CONFIG + # Available: config, luckperms, or the name of a provider registered by another plugin. + # Unknown values fall back to config. + source: config # Group used when a player matches no other group. - # Must reference a group defined below when source is CONFIG. + # Must reference a group defined below when source is config. default-group: default # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @@ -57,7 +58,7 @@ sync: # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # Groups -# Only used when general.source is CONFIG. +# Only used when general.source is config. # # Supported placeholders: # Group prefix From 642c2139427c0416c27c4fcefef7ed01da59a7d9 Mon Sep 17 00:00:00 2001 From: xXJanisXx Date: Fri, 21 Aug 2026 17:22:30 +0200 Subject: [PATCH 05/22] refactor: small improvements --- .../prefixes/api/group/PrefixesGroup.kt | 14 +------------- .../prefixes/paper/display/PaperTablist.kt | 1 + .../paper/display/SourcedTablistEntry.kt | 8 -------- .../simplecloud/prefixes/shared/Prefixes.kt | 15 +++++++-------- .../prefixes/shared/api/PrefixesApiImpl.kt | 18 +++++++++--------- ...upProviders.kt => GroupProviderRegistry.kt} | 12 ++++++------ .../shared/group/config/ConfigPrefixesGroup.kt | 10 +--------- .../shared/group/luckperms/LuckPermsGroup.kt | 10 +--------- .../shared/sync/tablist/SourcedTablistEntry.kt | 6 ++++++ prefixes-shared/src/main/resources/config.yml | 3 +-- 10 files changed, 33 insertions(+), 64 deletions(-) delete mode 100644 prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/SourcedTablistEntry.kt rename prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/{GroupProviders.kt => GroupProviderRegistry.kt} (81%) create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SourcedTablistEntry.kt diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt index 2d91bf7..9787ec6 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt @@ -54,20 +54,8 @@ interface PrefixesGroup { * Checks whether the specified player is a member of this group. * * @param id the UUID of the player - * @return `true` if the player belongs to this group, otherwise `false` - */ - @Deprecated( - message = "Blocks the calling thread until the lookup finished.", - replaceWith = ReplaceWith("containsPlayerAsync(id)") - ) - fun containsPlayer(id: UUID): Boolean - - /** - * Checks asynchronously whether the specified player is a member of this group. - * - * @param id the UUID of the player * @return a future completing with `true` if the player belongs to this group, * otherwise `false` */ - fun containsPlayerAsync(id: UUID): CompletableFuture + fun containsPlayer(id: UUID): CompletableFuture } \ No newline at end of file diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt index 78a4239..ee84766 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt @@ -1,6 +1,7 @@ package app.simplecloud.prefixes.paper.display import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty +import app.simplecloud.prefixes.shared.sync.tablist.SourcedTablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistGameMode import com.google.common.collect.ImmutableMultimap diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/SourcedTablistEntry.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/SourcedTablistEntry.kt deleted file mode 100644 index 0180a38..0000000 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/SourcedTablistEntry.kt +++ /dev/null @@ -1,8 +0,0 @@ -package app.simplecloud.prefixes.paper.display - -import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry - -data class SourcedTablistEntry( - val publisherId: String, - val entry: TablistEntry -) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index 3ff308c..bcf9150 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -7,7 +7,7 @@ import app.simplecloud.prefixes.shared.api.PrefixesApiImpl import app.simplecloud.prefixes.shared.config.DefaultConfigInstaller import app.simplecloud.prefixes.shared.config.MessageConfig import app.simplecloud.prefixes.shared.config.PrefixesConfig -import app.simplecloud.prefixes.shared.group.GroupProviders +import app.simplecloud.prefixes.shared.group.GroupProviderRegistry import app.simplecloud.prefixes.shared.group.config.ConfigGroupProvider import app.simplecloud.prefixes.shared.group.luckperms.LuckPermsGroupProvider import app.simplecloud.prefixes.shared.platform.PrefixesListener @@ -32,9 +32,8 @@ class Prefixes(private val platform: PrefixesPlatform) { private val listeners = CopyOnWriteArrayList() - val providers = createGroupProviders() - - val api: PrefixesApi = PrefixesApiImpl(providers, listeners, platform) + val registry = createGroupRegistry() + val api: PrefixesApi = PrefixesApiImpl(registry, listeners, platform) val sync = createSync() fun startup() { @@ -69,15 +68,15 @@ class Prefixes(private val platform: PrefixesPlatform) { return ConfigurationFactory(file, PrefixesConfig::class.java) } - private fun createGroupProviders(): GroupProviders { - val providers = GroupProviders(config, ConfigGroupProvider(config, platform.getPermissionChecker())) + private fun createGroupRegistry(): GroupProviderRegistry { + val registry = GroupProviderRegistry(config, ConfigGroupProvider(config, platform.getPermissionChecker())) val luckPerms = platform.getLuckPerms() if (luckPerms != null) { - providers.register(LuckPermsGroupProvider(luckPerms)) + registry.register(LuckPermsGroupProvider(luckPerms)) } - return providers + return registry } private fun createSync(): PrefixesSync? { diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt index bfe4d6d..adfd6c5 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt @@ -5,7 +5,7 @@ import app.simplecloud.prefixes.api.PrefixesApi import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.api.group.PrefixesPlayerData -import app.simplecloud.prefixes.shared.group.GroupProviders +import app.simplecloud.prefixes.shared.group.GroupProviderRegistry import app.simplecloud.prefixes.shared.platform.PrefixesListener import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import net.kyori.adventure.text.Component @@ -15,24 +15,24 @@ import java.util.UUID import java.util.concurrent.CompletableFuture class PrefixesApiImpl( - private val providers: GroupProviders, + private val registry: GroupProviderRegistry, private val listeners: List, private val platform: PrefixesPlatform ) : PrefixesApi { - override fun getGroups(): CompletableFuture> = providers.current().getGroups() + override fun getGroups(): CompletableFuture> = registry.getCurrentGroupProvider().getGroups() - override fun getPrimaryGroup(id: UUID): CompletableFuture = providers.current().getGroup(id) + override fun getPrimaryGroup(id: UUID): CompletableFuture = registry.getCurrentGroupProvider().getGroup(id) - override fun addGroup(group: PrefixesGroup): CompletableFuture = providers.current().addGroup(group) + override fun addGroup(group: PrefixesGroup): CompletableFuture = registry.getCurrentGroupProvider().addGroup(group) - override fun registerGroupProvider(provider: GroupProvider) = providers.register(provider) + override fun registerGroupProvider(provider: GroupProvider) = registry.register(provider) - override fun unregisterGroupProvider(name: String) = providers.unregister(name) + override fun unregisterGroupProvider(name: String) = registry.unregister(name) - override fun getGroupProvider(): GroupProvider = providers.current() + override fun getGroupProvider(): GroupProvider = registry.getCurrentGroupProvider() - override fun getGroupProviders(): Collection = providers.all() + override fun getGroupProviders(): Collection = registry.getAllGroupProviders() override fun refreshPlayer(id: UUID) { listeners.forEach { listener -> listener.onPlayerUpdate(id) } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviders.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt similarity index 81% rename from prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviders.kt rename to prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt index d85052d..f79e74b 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviders.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt @@ -6,7 +6,7 @@ import app.simplecloud.prefixes.shared.config.PrefixesConfig import java.util.concurrent.ConcurrentHashMap import java.util.logging.Logger -class GroupProviders( +class GroupProviderRegistry( private val config: ConfigurationFactory, private val provider: GroupProvider ) { @@ -14,7 +14,7 @@ class GroupProviders( private val logger = Logger.getLogger("simplecloud-prefixes") private val providers = ConcurrentHashMap() - private val reportedSources = ConcurrentHashMap.newKeySet() + private val sources = ConcurrentHashMap.newKeySet() init { register(provider) @@ -23,23 +23,23 @@ class GroupProviders( fun register(provider: GroupProvider) { val key = toKey(provider.name) providers[key] = provider - reportedSources.remove(key) + sources.remove(key) } fun unregister(name: String) { providers.remove(toKey(name)) } - fun all(): Collection = providers.values.toList() + fun getAllGroupProviders(): Collection = providers.values.toList() - fun current(): GroupProvider { + fun getCurrentGroupProvider(): GroupProvider { val source = toKey(config.get().general.source) val provider = providers[source] ?: return reportMissing(source) return provider } private fun reportMissing(source: String): GroupProvider { - if (reportedSources.add(source)) { + if (sources.add(source)) { logger.warning("No group provider named '$source' is registered, using '${provider.name}' instead") } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt index 8028743..1479364 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt @@ -24,15 +24,7 @@ class ConfigPrefixesGroup( override val displayName: String = group.displayName override val chatFormat: String = group.chatFormat - @Deprecated( - message = "Blocks the calling thread until the lookup finished.", - replaceWith = ReplaceWith("containsPlayerAsync(id)") - ) - override fun containsPlayer(id: UUID): Boolean { - return hasPermission(id) - } - - override fun containsPlayerAsync(id: UUID): CompletableFuture { + override fun containsPlayer(id: UUID): CompletableFuture { return CompletableFuture.supplyAsync { hasPermission(id) } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt index 0acea7e..64d6943 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt @@ -24,15 +24,7 @@ class LuckPermsGroup( override val displayName: String = getMetaValue("display-name") ?: "" override val chatFormat: String = getMetaValue("chat-format") ?: " <#475569>ยป <#F8FAFC>" - @Deprecated( - message = "Blocks the calling thread until the lookup finished.", - replaceWith = ReplaceWith("containsPlayerAsync(id)") - ) - override fun containsPlayer(id: UUID): Boolean { - return containsPlayerAsync(id).join() - } - - override fun containsPlayerAsync(id: UUID): CompletableFuture { + override fun containsPlayer(id: UUID): CompletableFuture { return luckPerms.userManager.loadUser(id).thenApply { user -> user.primaryGroup == name || user.getInheritedGroups(user.queryOptions).any { it.name == name } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SourcedTablistEntry.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SourcedTablistEntry.kt new file mode 100644 index 0000000..aaf2a07 --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SourcedTablistEntry.kt @@ -0,0 +1,6 @@ +package app.simplecloud.prefixes.shared.sync.tablist + +data class SourcedTablistEntry( + val publisherId: String, + val entry: TablistEntry +) \ No newline at end of file diff --git a/prefixes-shared/src/main/resources/config.yml b/prefixes-shared/src/main/resources/config.yml index 1c627dc..c17a872 100644 --- a/prefixes-shared/src/main/resources/config.yml +++ b/prefixes-shared/src/main/resources/config.yml @@ -7,8 +7,7 @@ version: 1 # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ general: # Where groups are read from. - # Available: config, luckperms, or the name of a provider registered by another plugin. - # Unknown values fall back to config. + # Available: config, luckperms, or the name of a custom provider. source: config # Group used when a player matches no other group. From e0ef84b0d89a356b72f31051d04670e872a34424 Mon Sep 17 00:00:00 2001 From: Janis Date: Sun, 23 Aug 2026 21:07:46 +0200 Subject: [PATCH 06/22] feat: minestom support (#16) * feat: minestom implementation * refactor: reformat code * refactor: use DataComponents.CUSTOM_NAME * fix: keep tablist sorting correct for priorities outside 0-999 closes simplecloudapp/simplecloud#18 * fix: fix some bugs --- README.md | 2 +- build.gradle.kts | 1 + gradle/libs.versions.toml | 6 +- .../prefixes/api/group/GroupProvider.kt | 2 +- prefixes-minestom/build.gradle.kts | 5 + prefixes-minestom/example/build.gradle.kts | 11 + .../minestom/example/ExampleServer.kt | 36 ++++ .../prefixes/minestom/PrefixesMinestom.kt | 138 +++++++++++++ .../minestom/PrefixesMinestomBuilder.kt | 44 ++++ .../command/PrefixesMinestomSender.kt | 17 ++ .../command/PrefixesMinestomSenderMapper.kt | 15 ++ .../display/MinestomDisplayManager.kt | 193 ++++++++++++++++++ .../minestom/display/MinestomNameTag.kt | 69 +++++++ .../minestom/display/MinestomPlayerTeam.kt | 46 +++++ .../minestom/display/MinestomTablist.kt | 150 ++++++++++++++ .../minestom/listener/LuckPermsListener.kt | 40 ++++ .../minestom/listener/PlayerListener.kt | 73 +++++++ .../permission/MinestomPermissions.kt | 30 +++ .../minestom/platform/MinestomPlatformImpl.kt | 27 +++ .../platform/MinestomPrefixesListener.kt | 38 ++++ .../prefixes/paper/display/PaperTablist.kt | 22 +- .../simplecloud/prefixes/shared/Prefixes.kt | 6 +- .../shared/command/PrefixesCommand.kt | 12 +- .../shared/group/GroupProviderRegistry.kt | 4 +- .../group/config/ConfigGroupProvider.kt | 3 +- .../group/luckperms/LuckPermsGroupProvider.kt | 3 +- .../shared/utilities/PriorityFormatter.kt | 4 +- settings.gradle.kts | 6 +- 28 files changed, 975 insertions(+), 28 deletions(-) create mode 100644 prefixes-minestom/build.gradle.kts create mode 100644 prefixes-minestom/example/build.gradle.kts create mode 100644 prefixes-minestom/example/src/main/kotlin/app/simplecloud/prefixes/minestom/example/ExampleServer.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/command/PrefixesMinestomSender.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/command/PrefixesMinestomSenderMapper.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomNameTag.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/LuckPermsListener.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt diff --git a/README.md b/README.md index 7efb899..4f3d442 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ The Prefixes Plugin provides comprehensive rank management for chat, tab list an - [x] **Chat, Tab List & Name Tags**: Prefixes, suffixes, colors and display names are applied to chat messages, the tab list and player name tags. - [x] **Cross-Server Sync**: Share chat messages and tab list entries between all servers, selected server groups or persistent servers of your network. - [x] **MiniMessage Formats**: Prefixes, suffixes, display names and chat formats are fully customizable with placeholder support. -- [x] **Supported Server Software**: Supports Paper and Forks. +- [x] **Supported Server Software**: Supports Paper and Forks, as well Minestom. - [x] **Quick Setup**: Easy installation process for all supported software. ## Dependency diff --git a/build.gradle.kts b/build.gradle.kts index e444f74..35ac0c2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ allprojects { maven("https://oss.sonatype.org/content/repositories/snapshots") maven("https://oss.sonatype.org/content/repositories/central") maven("https://repo.papermc.io/repository/maven-public/") + maven("https://repo.spectr.is/snapshots") maven("https://repo.simplecloud.app/snapshots") maven("https://buf.build/gen/maven") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 01e8aaf..fa31a3a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,13 +11,15 @@ simplecloud-api = "0.1.0-platform.54-dev.1.1-770dcc6" simplecloud-plugin-api = "0.0.1-platform.1778966459069-b1e45e2" paper = "26.2.build.+" +minestom = "2026.08.16-26.2" adventure = "5.2.0" luckperms = "5.5" reflection-remapper = "0.1.3" cloud-command = "2.0.0" -cloud-command-platform = "2.0.0-SNAPSHOT" +cloud-command-platform = "2.0.0" +cloud-command-minestom = "2.2.0-SNAPSHOT" configurate = "4.2.0" @@ -40,8 +42,10 @@ adventure-text-serializer-gson = { module = "net.kyori:adventure-text-serializer cloud-command-core = { module = "org.incendo:cloud-core", version.ref = "cloud-command" } cloud-command-coroutines = { module = "org.incendo:cloud-kotlin-coroutines", version.ref = "cloud-command" } cloud-command-paper = { module = "org.incendo:cloud-paper", version.ref = "cloud-command-platform" } +cloud-command-minestom = { module = "org.incendo:cloud-minestom", version.ref = "cloud-command-minestom" } paper-api = { module = "io.papermc.paper:paper-api", version.ref = "paper" } +minestom = { module = "net.minestom:minestom", version.ref = "minestom" } luckperms-api = { module = "net.luckperms:api", version.ref = "luckperms" } reflection-remapper = { module = "xyz.jpenilla:reflection-remapper", version.ref = "reflection-remapper" } diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt index 30afcbc..bfb97ff 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt @@ -11,7 +11,7 @@ interface GroupProvider { /** * The name of this provider. */ - val name: String + fun getName(): String /** * Returns all registered prefix groups. diff --git a/prefixes-minestom/build.gradle.kts b/prefixes-minestom/build.gradle.kts new file mode 100644 index 0000000..ac554c0 --- /dev/null +++ b/prefixes-minestom/build.gradle.kts @@ -0,0 +1,5 @@ +dependencies { + api(project(":prefixes-shared")) + api(libs.minestom) + implementation(libs.cloud.command.minestom) +} diff --git a/prefixes-minestom/example/build.gradle.kts b/prefixes-minestom/example/build.gradle.kts new file mode 100644 index 0000000..6fc02b7 --- /dev/null +++ b/prefixes-minestom/example/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + application +} + +dependencies { + implementation(project(":prefixes-minestom")) +} + +application { + mainClass.set("app.simplecloud.prefixes.minestom.example.ExampleServerKt") +} diff --git a/prefixes-minestom/example/src/main/kotlin/app/simplecloud/prefixes/minestom/example/ExampleServer.kt b/prefixes-minestom/example/src/main/kotlin/app/simplecloud/prefixes/minestom/example/ExampleServer.kt new file mode 100644 index 0000000..0349147 --- /dev/null +++ b/prefixes-minestom/example/src/main/kotlin/app/simplecloud/prefixes/minestom/example/ExampleServer.kt @@ -0,0 +1,36 @@ +package app.simplecloud.prefixes.minestom.example + +import app.simplecloud.prefixes.minestom.PrefixesMinestom +import net.minestom.server.MinecraftServer +import net.minestom.server.command.CommandSender +import net.minestom.server.coordinate.Pos +import net.minestom.server.entity.Player +import net.minestom.server.event.player.AsyncPlayerConfigurationEvent +import net.minestom.server.instance.block.Block +import java.nio.file.Path + +fun main() { + val server = MinecraftServer.init() + + val instance = MinecraftServer.getInstanceManager().createInstanceContainer() + instance.setGenerator { unit -> + unit.modifier().fillHeight(0, 40, Block.GRASS_BLOCK) + } + + MinecraftServer.getGlobalEventHandler().addListener(AsyncPlayerConfigurationEvent::class.java) { event -> + event.spawningInstance = instance + event.player.respawnPoint = Pos(0.0, 41.0, 0.0) + } + + PrefixesMinestom.builder(Path.of("prefixes")) + .commands(true) + .permissionHandler(::hasPermission) + .enable() + + server.start("0.0.0.0", 25565) +} + +private fun hasPermission(sender: CommandSender, permission: String): Boolean { + if (sender !is Player) return true + return permission == "simplecloud.prefix.group.default" +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt new file mode 100644 index 0000000..98e0e15 --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt @@ -0,0 +1,138 @@ +package app.simplecloud.prefixes.minestom + +import app.simplecloud.prefixes.api.PrefixesApi +import app.simplecloud.prefixes.minestom.command.PrefixesMinestomSenderMapper +import app.simplecloud.prefixes.minestom.display.MinestomDisplayManager +import app.simplecloud.prefixes.minestom.display.MinestomTablist +import app.simplecloud.prefixes.minestom.listener.LuckPermsListener +import app.simplecloud.prefixes.minestom.listener.PlayerListener +import app.simplecloud.prefixes.minestom.permission.MinestomPermissions +import app.simplecloud.prefixes.minestom.platform.MinestomPlatformImpl +import app.simplecloud.prefixes.minestom.platform.MinestomPrefixesListener +import app.simplecloud.prefixes.shared.Prefixes +import app.simplecloud.prefixes.shared.command.PrefixesCommand +import app.simplecloud.prefixes.shared.config.LUCKPERMS_SOURCE +import net.luckperms.api.LuckPerms +import net.minestom.server.MinecraftServer +import net.minestom.server.adventure.audience.Audiences +import net.minestom.server.command.CommandSender +import net.minestom.server.event.EventNode +import net.minestom.server.timer.Task +import net.minestom.server.timer.TaskSchedule +import org.incendo.cloud.execution.ExecutionCoordinator +import org.incendo.cloud.minestom.MinestomCommandManager +import java.nio.file.Path +import java.util.concurrent.atomic.AtomicBoolean +import java.util.function.BiPredicate +import java.util.logging.Logger + +class PrefixesMinestom internal constructor( + directory: Path, + permissionHandler: BiPredicate?, + private val commands: Boolean, + luckPerms: LuckPerms? +) { + + private val logger = Logger.getLogger("simplecloud-prefixes") + private val node = EventNode.all("simplecloud-prefixes") + private val permissions = MinestomPermissions(permissionHandler) + private val platform = MinestomPlatformImpl(directory, permissions.getChecker(), luckPerms) + private val prefixes = Prefixes(platform) + private val manager = MinestomDisplayManager(prefixes, permissions) + private val tablist = MinestomTablist() + private val enabled = AtomicBoolean() + + private var syncTask: Task? = null + + /** + * Gets the [PrefixesApi] instance. + */ + fun getApi(): PrefixesApi = prefixes.api + + fun enable(): PrefixesMinestom { + check(enabled.compareAndSet(false, true)) { "PrefixesMinestom is already enabled" } + + prefixes.startup() + prefixes.addListener(MinestomPrefixesListener(prefixes, manager, tablist)) + + PlayerListener(prefixes, manager, tablist).register(node) + MinecraftServer.getGlobalEventHandler().addChild(node) + + registerSync() + registerLuckPermsListener() + registerCommands() + + MinecraftServer.getSchedulerManager().buildShutdownTask { disable() } + return this + } + + private fun registerSync() { + val sync = prefixes.sync ?: return + val config = prefixes.config.get() + + if (config.features.chat && config.sync.channels.chat) { + sync.subscriber.subscribeChatMessage { message -> Audiences.players().sendMessage(message) } + } + + if (!config.features.tablist || !config.sync.channels.tablist) return + + sync.subscriber.subscribeTablist( + onUpdate = tablist::update, + onRemove = { publisherId, id -> tablist.remove(publisherId, id) }, + onRequest = { manager.sync(force = true) } + ) + sync.publisher.publishTablistRequest() + + syncTask = MinecraftServer.getSchedulerManager() + .buildTask { manager.sync() } + .delay(TaskSchedule.tick(600)) + .repeat(TaskSchedule.tick(600)) + .schedule() + } + + fun disable() { + if (!enabled.compareAndSet(true, false)) return + + MinecraftServer.getGlobalEventHandler().removeChild(node) + syncTask?.cancel() + manager.clear() + tablist.clear() + prefixes.shutdown() + } + + private fun registerLuckPermsListener() { + val source = prefixes.config.get().general.source + logger.info("Using Source Type: $source") + if (!source.equals(LUCKPERMS_SOURCE, ignoreCase = true)) return + + val luckPerms = platform.getLuckPerms() + if (luckPerms == null) { + logger.warning("Source Type is set to $LUCKPERMS_SOURCE, but LuckPerms was not found on the server!") + return + } + + LuckPermsListener(luckPerms, manager).register() + } + + private fun registerCommands() { + if (!commands) return + + val commandManager = MinestomCommandManager( + ExecutionCoordinator.asyncCoordinator(), + PrefixesMinestomSenderMapper(permissions), + permissions::hasPermission + ) + + PrefixesCommand(commandManager, prefixes).register() + } + + companion object { + /** + * Creates a builder for a new instance. + * + * @param directory The directory the config files are stored in + */ + @JvmStatic + fun builder(directory: Path): PrefixesMinestomBuilder = PrefixesMinestomBuilder(directory) + } +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt new file mode 100644 index 0000000..fd57442 --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt @@ -0,0 +1,44 @@ +package app.simplecloud.prefixes.minestom + +import net.luckperms.api.LuckPerms +import net.minestom.server.command.CommandSender +import java.nio.file.Path +import java.util.function.BiPredicate + +/** + * Builds a [PrefixesMinestom] instance. + */ +class PrefixesMinestomBuilder internal constructor(private val directory: Path) { + + private var permissionHandler: BiPredicate? = null + private var commands = true + private var luckPerms: LuckPerms? = null + + /** + * Sets the permission handler. + */ + fun permissionHandler(handler: BiPredicate): PrefixesMinestomBuilder = apply { + this.permissionHandler = handler + } + + /** + * Registers the `/scprefix` command. Enabled by default. + */ + fun commands(enabled: Boolean): PrefixesMinestomBuilder = apply { + this.commands = enabled + } + + /** + * Sets the LuckPerms instance used when the group source is `luckperms`. + */ + fun luckPerms(luckPerms: LuckPerms): PrefixesMinestomBuilder = apply { + this.luckPerms = luckPerms + } + + /** + * Creates the instance and starts the plugin. + */ + fun enable(): PrefixesMinestom { + return PrefixesMinestom(directory, permissionHandler, commands, luckPerms).enable() + } +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/command/PrefixesMinestomSender.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/command/PrefixesMinestomSender.kt new file mode 100644 index 0000000..e863be1 --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/command/PrefixesMinestomSender.kt @@ -0,0 +1,17 @@ +package app.simplecloud.prefixes.minestom.command + +import app.simplecloud.prefixes.minestom.permission.MinestomPermissions +import app.simplecloud.prefixes.shared.command.PrefixesSender +import net.kyori.adventure.text.Component +import net.minestom.server.command.CommandSender + +class PrefixesMinestomSender( + val sender: CommandSender, + private val permissions: MinestomPermissions +) : PrefixesSender { + + override fun sendMessage(message: Component) = sender.sendMessage(message) + + override fun hasPermission(permission: String): Boolean = permissions.hasPermission(sender, permission) + +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/command/PrefixesMinestomSenderMapper.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/command/PrefixesMinestomSenderMapper.kt new file mode 100644 index 0000000..462b278 --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/command/PrefixesMinestomSenderMapper.kt @@ -0,0 +1,15 @@ +package app.simplecloud.prefixes.minestom.command + +import app.simplecloud.prefixes.minestom.permission.MinestomPermissions +import net.minestom.server.command.CommandSender +import org.incendo.cloud.SenderMapper + +class PrefixesMinestomSenderMapper( + private val permissions: MinestomPermissions +) : SenderMapper { + + override fun map(base: CommandSender): PrefixesMinestomSender = PrefixesMinestomSender(base, permissions) + + override fun reverse(mapped: PrefixesMinestomSender): CommandSender = mapped.sender + +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt new file mode 100644 index 0000000..157d18f --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt @@ -0,0 +1,193 @@ +package app.simplecloud.prefixes.minestom.display + +import app.simplecloud.prefixes.api.group.PrefixesPlayerData +import app.simplecloud.prefixes.minestom.permission.MinestomPermissions +import app.simplecloud.prefixes.shared.Prefixes +import app.simplecloud.prefixes.shared.config.CONFIG_SOURCE +import app.simplecloud.prefixes.shared.config.FeaturesConfig +import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty +import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry +import app.simplecloud.prefixes.shared.sync.tablist.TablistGameMode +import app.simplecloud.prefixes.shared.utilities.PlayerDisplayFormatter +import net.kyori.adventure.text.Component +import net.minestom.server.MinecraftServer +import net.minestom.server.entity.Player +import net.minestom.server.scoreboard.Team +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicBoolean +import java.util.logging.Level +import java.util.logging.Logger + +class MinestomDisplayManager( + private val prefixes: Prefixes, + private val permissions: MinestomPermissions +) { + + private val logger = Logger.getLogger("simplecloud-prefixes") + private val cache = ConcurrentHashMap() + private val teams = ConcurrentHashMap() + private val nameTags = ConcurrentHashMap() + private val entries = ConcurrentHashMap() + private val warned = AtomicBoolean() + + fun getPlayer(id: UUID): PrefixesPlayerData? { + return cache[id] + } + + fun addPlayer(player: Player) { + warnMissingPermissionHandler() + updatePlayer(player) + } + + fun updatePlayer(player: Player) { + prefixes.api.getPrefixData(player.uuid).thenAccept { data -> + MinecraftServer.getSchedulerManager().scheduleNextTick { + applyPrefixData(player, data) + } + }.exceptionally { throwable -> + logger.log(Level.WARNING, "Failed to update prefix data of ${player.username}", throwable) + null + } + } + + fun removePlayer(player: Player) { + cache.remove(player.uuid) + removeTeam(player.uuid) + removeNameTag(player.uuid) + + entries.remove(player.uuid) + prefixes.sync?.publisher?.publishTablistRemove(player.uuid) + } + + fun setSneaking(player: Player, sneaking: Boolean) { + nameTags[player.uuid]?.setSneaking(sneaking) + } + + fun refreshNameTag(player: Player) { + if (removeNameTag(player.uuid) == null) return + + val data = cache[player.uuid] ?: return + val features = prefixes.config.get().features + updateNameTag(player, displayName(data, player, features), features) + } + + fun clear() { + cache.clear() + entries.clear() + teams.keys.toList().forEach(::removeTeam) + nameTags.keys.toList().forEach(::removeNameTag) + } + + fun sync(force: Boolean = false) { + MinecraftServer.getConnectionManager().onlinePlayers.forEach { player -> + val data = cache[player.uuid] ?: return@forEach + val features = prefixes.config.get().features + + publish(player, data, displayName(data, player, features), force) + } + } + + private fun applyPrefixData(player: Player, data: PrefixesPlayerData) { + if (!player.isOnline) return + cache[player.uuid] = data + + val features = prefixes.config.get().features + val displayName = displayName(data, player, features) + + player.displayName = when { + features.tablist -> PlayerDisplayFormatter.formatTablistName(data, displayName) + else -> null + } + + updateTeam(player, data, features) + updateNameTag(player, displayName, features) + + publish(player, data, displayName) + } + + private fun updateTeam(player: Player, data: PrefixesPlayerData, features: FeaturesConfig) { + removeTeam(player.uuid) + + val team = createTeam(player, data, features) ?: return + teams[player.uuid] = team + } + + private fun createTeam(player: Player, data: PrefixesPlayerData, features: FeaturesConfig): Team? { + return when { + features.tablist -> MinestomPlayerTeam.create( + player.username, + data.priority, + data.prefix, + data.suffix, + data.color, + hideNameTag = features.displayName + ) + + // Only there to hide the vanilla name tag, which the name tag entity replaces. + features.displayName -> MinestomPlayerTeam.create(player.username, priority = 0, hideNameTag = true) + else -> null + } + } + + private fun removeTeam(id: UUID) { + val team = teams.remove(id) ?: return + MinestomPlayerTeam.delete(team) + } + + private fun updateNameTag(player: Player, displayName: Component, features: FeaturesConfig) { + if (!features.displayName) { + removeNameTag(player.uuid) + return + } + + val nameTag = nameTags.computeIfAbsent(player.uuid) { + MinestomNameTag(player).also(MinestomNameTag::spawn) + } + + nameTag.setName(displayName) + } + + private fun removeNameTag(id: UUID): MinestomNameTag? { + return nameTags.remove(id)?.also(MinestomNameTag::remove) + } + + private fun publish(player: Player, data: PrefixesPlayerData, displayName: Component, force: Boolean = false) { + val config = prefixes.config.get() + if (!config.features.tablist || !config.sync.enabled || !config.sync.channels.tablist) return + val publisher = prefixes.sync?.publisher ?: return + + val entry = TablistEntry( + uniqueId = player.uuid, + name = player.username, + displayName = PlayerDisplayFormatter.formatTablistName(data, displayName), + priority = data.priority, + profileProperties = getProfileProperties(player), + latency = player.latency, + gameMode = TablistGameMode.valueOf(player.gameMode.name), + showHat = (player.settings.displayedSkinParts.toInt() and 0x40) != 0, + listOrder = player.listOrder + ) + val previous = entries.put(player.uuid, entry) + if (!force && previous == entry) return + + publisher.publishTablistEntry(entry) + } + + private fun getProfileProperties(player: Player): List { + val skin = player.skin ?: return emptyList() + return listOf(ProfileProperty("textures", skin.textures(), skin.signature())) + } + + private fun displayName(data: PrefixesPlayerData, player: Player, features: FeaturesConfig): Component { + return PlayerDisplayFormatter.displayName(data, player.username, features.displayName) + } + + private fun warnMissingPermissionHandler() { + if (permissions.hasHandler || !warned.compareAndSet(false, true)) return + if (!prefixes.registry.getCurrentGroupProvider().getName().equals(CONFIG_SOURCE, ignoreCase = true)) return + + logger.warning("Source Type is set to $CONFIG_SOURCE, but no permission handler was set!") + } + +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomNameTag.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomNameTag.kt new file mode 100644 index 0000000..3049c52 --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomNameTag.kt @@ -0,0 +1,69 @@ +package app.simplecloud.prefixes.minestom.display + +import net.kyori.adventure.text.Component +import net.minestom.server.component.DataComponents +import net.minestom.server.entity.Entity +import net.minestom.server.entity.EntityPose +import net.minestom.server.entity.EntityType +import net.minestom.server.entity.Player +import net.minestom.server.entity.metadata.other.InteractionMeta +import java.util.concurrent.atomic.AtomicBoolean + +class MinestomNameTag(private val player: Player) { + + private val entity = Entity(EntityType.INTERACTION) + private val removed = AtomicBoolean() + + init { + entity.updateViewableRule { viewer -> viewer != player } + + edit { meta -> + meta.width = WIDTH + meta.height = HEIGHT + meta.isInvisible = true + meta.pose = EntityPose.CROAKING + } + } + + fun spawn() { + val instance = player.instance ?: return + + // Spawned as close to its final position as possible, so the client does not + // interpolate it into place once it becomes a passenger. + entity.setInstance(instance, player.position.add(0.0, PASSENGER_OFFSET, 0.0)) + .thenRun { + // The player can disconnect while the instance is still being set, in which case + // addPassenger would throw on an entity that no longer has an instance. + if (removed.get() || entity.isRemoved || !player.isOnline || player.instance == null) return@thenRun + player.addPassenger(entity) + } + } + + fun setName(name: Component) { + entity.set(DataComponents.CUSTOM_NAME, name) + edit { meta -> meta.isCustomNameVisible = true } + } + + fun setSneaking(sneaking: Boolean) { + edit { meta -> meta.isSneaking = sneaking } + } + + fun remove() { + if (!removed.compareAndSet(false, true)) return + + if (player.instance != null) { + player.removePassenger(entity) + } + entity.remove() + } + + private fun edit(editor: (InteractionMeta) -> Unit) { + entity.editEntityMeta(InteractionMeta::class.java) { meta -> editor(meta) } + } + + private companion object { + const val WIDTH = 0.6f + const val HEIGHT = 0.0f + const val PASSENGER_OFFSET = 1.8 + } +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt new file mode 100644 index 0000000..ae8d420 --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt @@ -0,0 +1,46 @@ +package app.simplecloud.prefixes.minestom.display + +import app.simplecloud.prefixes.shared.utilities.PriorityFormatter +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextColor +import net.minestom.server.MinecraftServer +import net.minestom.server.color.TeamColor +import net.minestom.server.network.packet.server.play.TeamsPacket.NameTagVisibility +import net.minestom.server.scoreboard.Team + +object MinestomPlayerTeam { + + fun create( + name: String, + priority: Int, + prefix: Component = Component.empty(), + suffix: Component = Component.empty(), + color: TextColor = NamedTextColor.WHITE, + hideNameTag: Boolean = false + ): Team { + val manager = MinecraftServer.getTeamManager() + val teamName = "${PriorityFormatter.format(priority)}_$name" + + manager.deleteTeam(teamName) + + val team = manager + .createBuilder(teamName) + .prefix(prefix) + .suffix(suffix) + .teamColor(toTeamColor(color)) + .nameTagVisibility(if (hideNameTag) NameTagVisibility.NEVER else NameTagVisibility.ALWAYS) + .build() + + team.addMember(name) + return team + } + + fun delete(team: Team) { + MinecraftServer.getTeamManager().deleteTeam(team) + } + + private fun toTeamColor(color: TextColor): TeamColor { + return TeamColor.valueOf(NamedTextColor.nearestTo(color).toString().uppercase()) + } +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt new file mode 100644 index 0000000..301953c --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt @@ -0,0 +1,150 @@ +package app.simplecloud.prefixes.minestom.display + +import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty +import app.simplecloud.prefixes.shared.sync.tablist.SourcedTablistEntry +import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry +import net.minestom.server.MinecraftServer +import net.minestom.server.entity.GameMode +import net.minestom.server.entity.Player +import net.minestom.server.network.packet.server.play.PlayerInfoRemovePacket +import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket +import net.minestom.server.scoreboard.Team +import net.minestom.server.utils.PacketSendingUtils +import java.util.EnumSet +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +class MinestomTablist { + + private val entries = ConcurrentHashMap() + private val teams = ConcurrentHashMap() + + fun update(publisherId: String, entry: TablistEntry) { + if (isOnline(entry.uniqueId)) return + + val previous = entries.put(entry.uniqueId, SourcedTablistEntry(publisherId, entry))?.entry + val actions = getActions(entry, previous) + + if (actions.isNotEmpty()) { + PacketSendingUtils.broadcastPlayPacket(createInfoPacket(listOf(entry), actions)) + } + if (previous == null || previous.name != entry.name || previous.priority != entry.priority) { + updateTeam(entry) + } + } + + fun remove(id: UUID) { + removeEntry(null, id) + } + + fun remove(publisherId: String, id: UUID) { + removeEntry(publisherId, id) + } + + fun clear() { + entries.keys.toList().forEach { id -> remove(id) } + } + + fun sync(player: Player) { + val visibleEntries = entries.values + .map(SourcedTablistEntry::entry) + .filter { !isOnline(it.uniqueId) } + + visibleEntries.chunked(PlayerInfoUpdatePacket.MAX_ENTRIES).forEach { batch -> + player.sendPacket(createInfoPacket(batch, getFullUpdateActions())) + } + } + + private fun removeEntry(publisherId: String?, id: UUID) { + val sourcedEntry = entries[id] ?: return + if (publisherId != null && sourcedEntry.publisherId != publisherId) return + if (!entries.remove(id, sourcedEntry)) return + + removeTeam(id) + if (isOnline(id)) return + + PacketSendingUtils.broadcastPlayPacket(PlayerInfoRemovePacket(id)) + } + + private fun updateTeam(entry: TablistEntry) { + removeTeam(entry.uniqueId) + teams[entry.uniqueId] = MinestomPlayerTeam.create(entry.name, entry.priority) + } + + private fun removeTeam(id: UUID) { + val team = teams.remove(id) ?: return + MinestomPlayerTeam.delete(team) + } + + private fun createInfoPacket( + tablistEntries: Collection, + actions: EnumSet + ): PlayerInfoUpdatePacket { + val packetEntries = tablistEntries.map { entry -> + PlayerInfoUpdatePacket.Entry( + entry.uniqueId, + entry.name, + entry.profileProperties.map(::createProperty), + true, + entry.latency, + GameMode.valueOf(entry.gameMode.name), + entry.displayName, + null, + entry.listOrder, + entry.showHat + ) + } + + return PlayerInfoUpdatePacket(actions, packetEntries) + } + + private fun getActions( + entry: TablistEntry, + previous: TablistEntry? + ): EnumSet { + if (previous == null || + previous.name != entry.name || + previous.profileProperties != entry.profileProperties + ) { + return getFullUpdateActions() + } + + val actions = EnumSet.noneOf(PlayerInfoUpdatePacket.Action::class.java) + if (previous.gameMode != entry.gameMode) { + actions.add(PlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE) + } + if (previous.latency != entry.latency) { + actions.add(PlayerInfoUpdatePacket.Action.UPDATE_LATENCY) + } + if (previous.displayName != entry.displayName) { + actions.add(PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME) + } + if (previous.listOrder != entry.listOrder) { + actions.add(PlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER) + } + if (previous.showHat != entry.showHat) { + actions.add(PlayerInfoUpdatePacket.Action.UPDATE_HAT) + } + return actions + } + + private fun getFullUpdateActions(): EnumSet = + EnumSet.of( + PlayerInfoUpdatePacket.Action.ADD_PLAYER, + PlayerInfoUpdatePacket.Action.UPDATE_LISTED, + PlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, + PlayerInfoUpdatePacket.Action.UPDATE_LATENCY, + PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, + PlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER, + PlayerInfoUpdatePacket.Action.UPDATE_HAT + ) + + private fun createProperty(property: ProfileProperty): PlayerInfoUpdatePacket.Property { + val signature = property.signature ?: return PlayerInfoUpdatePacket.Property(property.name, property.value) + return PlayerInfoUpdatePacket.Property(property.name, property.value, signature) + } + + private fun isOnline(id: UUID): Boolean { + return MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) != null + } +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/LuckPermsListener.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/LuckPermsListener.kt new file mode 100644 index 0000000..941643f --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/LuckPermsListener.kt @@ -0,0 +1,40 @@ +package app.simplecloud.prefixes.minestom.listener + +import app.simplecloud.prefixes.minestom.display.MinestomDisplayManager +import net.luckperms.api.LuckPerms +import net.luckperms.api.event.node.NodeMutateEvent +import net.luckperms.api.event.user.UserDataRecalculateEvent +import net.luckperms.api.model.group.Group +import net.luckperms.api.model.user.User +import net.minestom.server.MinecraftServer +import java.util.UUID + +class LuckPermsListener( + private val luckPerms: LuckPerms, + private val manager: MinestomDisplayManager +) { + + fun register() { + luckPerms.eventBus.subscribe(UserDataRecalculateEvent::class.java) { event -> + updatePlayer(event.user.uniqueId) + } + + luckPerms.eventBus.subscribe(NodeMutateEvent::class.java) { event -> + when (val target = event.target) { + is User -> updatePlayer(target.uniqueId) + is Group -> updateAllPlayers() + } + } + } + + private fun updatePlayer(id: UUID) { + val player = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) ?: return + manager.updatePlayer(player) + } + + private fun updateAllPlayers() { + MinecraftServer.getConnectionManager().onlinePlayers.forEach { player -> + manager.updatePlayer(player) + } + } +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt new file mode 100644 index 0000000..99d3084 --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt @@ -0,0 +1,73 @@ +package app.simplecloud.prefixes.minestom.listener + +import app.simplecloud.prefixes.minestom.display.MinestomDisplayManager +import app.simplecloud.prefixes.minestom.display.MinestomTablist +import app.simplecloud.prefixes.shared.Prefixes +import app.simplecloud.prefixes.shared.utilities.PlayerDisplayFormatter +import net.kyori.adventure.text.Component +import net.minestom.server.event.Event +import net.minestom.server.event.EventNode +import net.minestom.server.event.player.PlayerChatEvent +import net.minestom.server.event.player.PlayerDisconnectEvent +import net.minestom.server.event.player.PlayerInputEvent +import net.minestom.server.event.player.PlayerSpawnEvent + +class PlayerListener( + private val prefixes: Prefixes, + private val manager: MinestomDisplayManager, + private val tablist: MinestomTablist +) { + + fun register(node: EventNode) { + node.addListener(PlayerSpawnEvent::class.java, ::onSpawn) + node.addListener(PlayerDisconnectEvent::class.java, ::onDisconnect) + node.addListener(PlayerChatEvent::class.java, ::onChat) + node.addListener(PlayerInputEvent::class.java, ::onInput) + } + + private fun onSpawn(event: PlayerSpawnEvent) { + // The entity carrying the name tag stays behind in the previous instance. + if (!event.isFirstSpawn) { + manager.refreshNameTag(event.player) + return + } + + tablist.remove(event.player.uuid) + manager.addPlayer(event.player) + + val config = prefixes.config.get() + if (config.features.tablist && config.sync.enabled && config.sync.channels.tablist) { + tablist.sync(event.player) + } + } + + private fun onDisconnect(event: PlayerDisconnectEvent) { + manager.removePlayer(event.player) + } + + private fun onChat(event: PlayerChatEvent) { + val features = prefixes.config.get().features + if (!features.chat) return + + val player = event.player + val data = manager.getPlayer(player.uuid) ?: return + val displayName = PlayerDisplayFormatter.displayName(data, player.username, features.displayName) + + val message = PlayerDisplayFormatter.formatChatMessage( + data, + player.username, + Component.text(event.rawMessage), + displayName + ) + + event.formattedMessage = message + prefixes.sync?.publisher?.publishChatMessage(message) + } + + private fun onInput(event: PlayerInputEvent) { + when { + event.hasPressedShiftKey() -> manager.setSneaking(event.player, true) + event.hasReleasedShiftKey() -> manager.setSneaking(event.player, false) + } + } +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt new file mode 100644 index 0000000..e36245b --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt @@ -0,0 +1,30 @@ +package app.simplecloud.prefixes.minestom.permission + +import app.simplecloud.plugin.api.shared.permission.PermissionChecker +import net.minestom.server.MinecraftServer +import net.minestom.server.command.CommandSender +import net.minestom.server.command.ConsoleSender +import net.minestom.server.entity.Player +import java.util.UUID +import java.util.function.BiPredicate + +class MinestomPermissions(private val handler: BiPredicate?) { + + val hasHandler: Boolean = handler != null + + fun hasPermission(sender: CommandSender, permission: String): Boolean { + if (permission.isEmpty()) return true + if (sender is ConsoleSender) return true + if (sender is Player && sender.permissionLevel >= 4) return true + + return handler?.test(sender, permission) ?: false + } + + fun getChecker(): PermissionChecker = PermissionChecker { id, permission -> + val handler = handler ?: return@PermissionChecker false + val player = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) ?: return@PermissionChecker false + + handler.test(player, permission) + } + +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt new file mode 100644 index 0000000..0f5f1f5 --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt @@ -0,0 +1,27 @@ +package app.simplecloud.prefixes.minestom.platform + +import app.simplecloud.plugin.api.shared.permission.PermissionChecker +import app.simplecloud.prefixes.shared.platform.PrefixesPlatform +import net.luckperms.api.LuckPerms +import net.luckperms.api.LuckPermsProvider +import net.minestom.server.MinecraftServer +import java.io.File +import java.nio.file.Path +import java.util.UUID + +class MinestomPlatformImpl( + private val path: Path, + private val permissionChecker: PermissionChecker, + private val luckPerms: LuckPerms? +) : PrefixesPlatform { + + override fun getDataDirectory(): File = path.toFile() + + override fun getPermissionChecker(): PermissionChecker = permissionChecker + + override fun getLuckPerms(): LuckPerms? = luckPerms ?: runCatching { LuckPermsProvider.get() }.getOrNull() + + override fun getPlayerName(id: UUID): String { + return MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id)?.username ?: id.toString() + } +} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt new file mode 100644 index 0000000..44484ba --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt @@ -0,0 +1,38 @@ +package app.simplecloud.prefixes.minestom.platform + +import app.simplecloud.prefixes.minestom.display.MinestomDisplayManager +import app.simplecloud.prefixes.minestom.display.MinestomTablist +import app.simplecloud.prefixes.shared.Prefixes +import app.simplecloud.prefixes.shared.platform.PrefixesListener +import net.minestom.server.MinecraftServer +import java.util.UUID + +class MinestomPrefixesListener( + private val prefixes: Prefixes, + private val manager: MinestomDisplayManager, + private val tablist: MinestomTablist +) : PrefixesListener { + + override fun onReload() { + onAllPlayersUpdate() + + val config = prefixes.config.get() + if (!config.features.tablist || !config.sync.enabled || !config.sync.channels.tablist) { + tablist.clear() + return + } + + prefixes.sync?.publisher?.publishTablistRequest() + } + + override fun onPlayerUpdate(id: UUID) { + val player = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) ?: return + manager.updatePlayer(player) + } + + override fun onAllPlayersUpdate() { + MinecraftServer.getConnectionManager().onlinePlayers.forEach { player -> + manager.updatePlayer(player) + } + } +} diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt index ee84766..93694f9 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt @@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap class PaperTablist { private val entries = ConcurrentHashMap() + private val teams = ConcurrentHashMap() fun update(publisherId: String, entry: TablistEntry) { if (Bukkit.getPlayer(entry.uniqueId) != null) return @@ -35,10 +36,7 @@ class PaperTablist { broadcast(createInfoPacket(listOf(entry), actions)) } if (previous == null || previous.name != entry.name || previous.priority != entry.priority) { - if (previous != null) { - broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(createTeam(previous))) - } - broadcast(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(createTeam(entry), true)) + updateTeam(entry) } } @@ -59,10 +57,9 @@ class PaperTablist { if (publisherId != null && sourcedEntry.publisherId != publisherId) return if (!entries.remove(id, sourcedEntry)) return + removeTeam(id) if (Bukkit.getPlayer(id) != null) return - val entry = sourcedEntry.entry - broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(createTeam(entry))) broadcast(ClientboundPlayerInfoRemovePacket(listOf(id))) } @@ -83,6 +80,19 @@ class PaperTablist { private fun createTeam(entry: TablistEntry) = PaperPlayerTeam(entry.name, entry.priority) + private fun updateTeam(entry: TablistEntry) { + removeTeam(entry.uniqueId) + + val team = createTeam(entry) + teams[entry.uniqueId] = team + broadcast(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)) + } + + private fun removeTeam(id: UUID) { + val team = teams.remove(id) ?: return + broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(team)) + } + private fun createInfoPacket( tablistEntries: Collection, actions: EnumSet diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index bcf9150..3a3d914 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -49,8 +49,8 @@ class Prefixes(private val platform: PrefixesPlatform) { listeners.add(listener) } - fun reload() { - runCatching { + fun reload(): Boolean { + return runCatching { logger.info("Reloading simplecloud prefixes...") config.reload() messages.reload() @@ -59,7 +59,7 @@ class Prefixes(private val platform: PrefixesPlatform) { logger.info("Succesfully reloaded simplecloud prefixes") }.onFailure { throwable -> logger.log(Level.SEVERE, "Failed to reload simplecloud prefixes", throwable) - } + }.isSuccess } private fun createConfig(): ConfigurationFactory { diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt index 6d5bee9..593dfe5 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt @@ -50,13 +50,13 @@ class PrefixesCommand( return@suspendingHandler } - runCatching { - prefixes.reload() - }.onSuccess { - sender.sendMessage(messages.msg(messages.command.reload.success)) - }.onFailure { - sender.sendMessage(messages.msg(messages.command.reload.failed)) + val reloaded = prefixes.reload() + val message = when { + reloaded -> messages.command.reload.success + else -> messages.command.reload.failed } + + sender.sendMessage(messages.msg(message)) } ) } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt index f79e74b..51366f6 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt @@ -21,7 +21,7 @@ class GroupProviderRegistry( } fun register(provider: GroupProvider) { - val key = toKey(provider.name) + val key = toKey(provider.getName()) providers[key] = provider sources.remove(key) } @@ -40,7 +40,7 @@ class GroupProviderRegistry( private fun reportMissing(source: String): GroupProvider { if (sources.add(source)) { - logger.warning("No group provider named '$source' is registered, using '${provider.name}' instead") + logger.warning("No group provider named '$source' is registered, using '${provider.getName()}' instead") } return provider diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt index 9efae0e..4c14ace 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt @@ -5,7 +5,6 @@ import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.plugin.api.shared.permission.PermissionChecker import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup -import app.simplecloud.prefixes.shared.config.CONFIG_SOURCE import app.simplecloud.prefixes.shared.config.ConfigGroup import app.simplecloud.prefixes.shared.config.PrefixesConfig import app.simplecloud.prefixes.shared.utilities.ColorParser @@ -18,7 +17,7 @@ class ConfigGroupProvider( private val permissionChecker: PermissionChecker ) : GroupProvider { - override val name: String = CONFIG_SOURCE + override fun getName(): String = "config" override fun getGroups(): CompletableFuture> { return CompletableFuture.completedFuture(loadGroups()) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt index 18c7704..a1e1c77 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt @@ -3,7 +3,6 @@ package app.simplecloud.prefixes.shared.group.luckperms import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup -import app.simplecloud.prefixes.shared.config.LUCKPERMS_SOURCE import app.simplecloud.prefixes.shared.utilities.ColorParser import net.kyori.adventure.text.Component import net.luckperms.api.LuckPerms @@ -20,7 +19,7 @@ class LuckPermsGroupProvider( private val luckPerms: LuckPerms ) : GroupProvider { - override val name: String = LUCKPERMS_SOURCE + override fun getName(): String = "luckperms" override fun getGroups(): CompletableFuture> { val groups = luckPerms.groupManager.loadedGroups diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PriorityFormatter.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PriorityFormatter.kt index 5be4cba..ff7d131 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PriorityFormatter.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PriorityFormatter.kt @@ -3,7 +3,7 @@ package app.simplecloud.prefixes.shared.utilities object PriorityFormatter { fun format(priority: Int): String { - val inverted = (1000 - priority).coerceIn(0, 999) - return String.format("%03d", inverted) + val inverted = Int.MAX_VALUE.toLong() - priority + return inverted.toString().padStart(10, '0') } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 99b5e3c..bb04fe4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,8 +4,10 @@ plugins { include( "prefixes-api", - "prefixes-shared", - "prefixes-paper" + "prefixes-minestom", + "prefixes-minestom:example", + "prefixes-paper", + "prefixes-shared" ) rootProject.name = "prefixes" \ No newline at end of file From b9e3ee053a1fd78f23126f54bd81d1806c18455a Mon Sep 17 00:00:00 2001 From: Janis Date: Mon, 7 Sep 2026 08:57:37 +0200 Subject: [PATCH 07/22] refactor: logging improvements --- gradle/libs.versions.toml | 2 + gradle/wrapper/gradle-wrapper.properties | 2 +- prefixes-minestom/build.gradle.kts | 3 +- .../prefixes/minestom/PrefixesMinestom.kt | 52 ++++++++----------- .../minestom/PrefixesMinestomBuilder.kt | 4 +- .../display/MinestomDisplayManager.kt | 42 +++------------ .../minestom/listener/PlayerListener.kt | 16 ++---- .../minestom/platform/MinestomPlatformImpl.kt | 3 ++ .../platform/MinestomPrefixesLogger.kt | 22 ++++++++ .../prefixes/paper/PrefixesPaper.kt | 8 +-- .../paper/display/PaperDisplayManager.kt | 20 +++---- .../prefixes/paper/listener/PlayerListener.kt | 2 +- .../paper/platform/PaperPlatformImpl.kt | 15 ++++-- .../paper/platform/PaperPrefixesLogger.kt | 21 ++++++++ .../simplecloud/prefixes/shared/Prefixes.kt | 45 +++++++++------- .../shared/command/PrefixesCommand.kt | 9 ++-- .../prefixes/shared/config/PrefixesConfig.kt | 13 ++--- .../shared/group/GroupProviderRegistry.kt | 27 ++++------ .../group/config/ConfigGroupProvider.kt | 11 +++- .../group/luckperms/LuckPermsGroupProvider.kt | 9 +++- .../shared/platform/PrefixesListener.kt | 12 +++++ .../shared/platform/PrefixesLogger.kt | 30 +++++++++++ .../shared/platform/PrefixesPlatform.kt | 11 ++-- .../prefixes/shared/sync/PrefixesSubjects.kt | 7 ++- .../prefixes/shared/sync/PrefixesSync.kt | 23 +++++--- .../prefixes/shared/sync/SyncPublisher.kt | 15 +++++- .../prefixes/shared/sync/SyncSubscriber.kt | 40 ++++++++++---- .../shared/sync/tablist/TablistEntry.kt | 3 -- .../prefixes/shared/utilities/Constants.kt | 11 ++++ .../utilities/PlayerDisplayFormatter.kt | 2 +- .../utilities/PlayerDisplayFormatterTest.kt | 4 +- 31 files changed, 299 insertions(+), 185 deletions(-) create mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesLogger.kt create mode 100644 prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesLogger.kt create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesLogger.kt create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/Constants.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fa31a3a..f1596ca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,6 +27,7 @@ protoc = "4.35.1" protobuf-kotlin = "4.35.1" jnats = "2.26.2" +slf4j = "2.0.18" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -57,6 +58,7 @@ protoc = { module = "com.google.protobuf:protoc", version.ref = "protoc" } protobuf-kotlin = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "protobuf-kotlin" } jnats = { module = "io.nats:jnats", version.ref = "jnats" } +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } [bundles] configurate = ["configurate-kotlin", "configurate-yaml"] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6e784a5..64a5b37 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists \ No newline at end of file diff --git a/prefixes-minestom/build.gradle.kts b/prefixes-minestom/build.gradle.kts index ac554c0..9a7b56e 100644 --- a/prefixes-minestom/build.gradle.kts +++ b/prefixes-minestom/build.gradle.kts @@ -1,5 +1,6 @@ dependencies { api(project(":prefixes-shared")) - api(libs.minestom) + implementation(libs.minestom) implementation(libs.cloud.command.minestom) + implementation(libs.slf4j.api) } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt index 98e0e15..0df9a79 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt @@ -11,53 +11,47 @@ import app.simplecloud.prefixes.minestom.platform.MinestomPlatformImpl import app.simplecloud.prefixes.minestom.platform.MinestomPrefixesListener import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.command.PrefixesCommand -import app.simplecloud.prefixes.shared.config.LUCKPERMS_SOURCE +import app.simplecloud.prefixes.shared.utilities.Constants import net.luckperms.api.LuckPerms import net.minestom.server.MinecraftServer import net.minestom.server.adventure.audience.Audiences import net.minestom.server.command.CommandSender import net.minestom.server.event.EventNode -import net.minestom.server.timer.Task import net.minestom.server.timer.TaskSchedule import org.incendo.cloud.execution.ExecutionCoordinator import org.incendo.cloud.minestom.MinestomCommandManager import java.nio.file.Path -import java.util.concurrent.atomic.AtomicBoolean import java.util.function.BiPredicate -import java.util.logging.Logger class PrefixesMinestom internal constructor( directory: Path, - permissionHandler: BiPredicate?, + private val permissionHandler: BiPredicate?, private val commands: Boolean, luckPerms: LuckPerms? ) { - private val logger = Logger.getLogger("simplecloud-prefixes") private val node = EventNode.all("simplecloud-prefixes") private val permissions = MinestomPermissions(permissionHandler) private val platform = MinestomPlatformImpl(directory, permissions.getChecker(), luckPerms) private val prefixes = Prefixes(platform) - private val manager = MinestomDisplayManager(prefixes, permissions) + private val manager = MinestomDisplayManager(prefixes) private val tablist = MinestomTablist() - private val enabled = AtomicBoolean() + private val logger = prefixes.getPlatform().getLogger() - private var syncTask: Task? = null - - /** - * Gets the [PrefixesApi] instance. - */ + /** Gets the [PrefixesApi] instance. */ fun getApi(): PrefixesApi = prefixes.api fun enable(): PrefixesMinestom { - check(enabled.compareAndSet(false, true)) { "PrefixesMinestom is already enabled" } - prefixes.startup() prefixes.addListener(MinestomPrefixesListener(prefixes, manager, tablist)) PlayerListener(prefixes, manager, tablist).register(node) MinecraftServer.getGlobalEventHandler().addChild(node) + if (prefixes.config.get().general.source.equals(Constants.CONFIG_SOURCE, ignoreCase = true) && permissionHandler == null) { + logger.warn("Source Type is set to '${Constants.CONFIG_SOURCE}', but no permission handler was registered!") + } + registerSync() registerLuckPermsListener() registerCommands() @@ -66,6 +60,14 @@ class PrefixesMinestom internal constructor( return this } + private fun disable() { + MinecraftServer.getGlobalEventHandler().removeChild(node) + manager.clear() + tablist.clear() + prefixes.shutdown() + } + + private fun registerSync() { val sync = prefixes.sync ?: return val config = prefixes.config.get() @@ -83,31 +85,21 @@ class PrefixesMinestom internal constructor( ) sync.publisher.publishTablistRequest() - syncTask = MinecraftServer.getSchedulerManager() + MinecraftServer.getSchedulerManager() .buildTask { manager.sync() } .delay(TaskSchedule.tick(600)) .repeat(TaskSchedule.tick(600)) .schedule() } - fun disable() { - if (!enabled.compareAndSet(true, false)) return - - MinecraftServer.getGlobalEventHandler().removeChild(node) - syncTask?.cancel() - manager.clear() - tablist.clear() - prefixes.shutdown() - } - private fun registerLuckPermsListener() { val source = prefixes.config.get().general.source logger.info("Using Source Type: $source") - if (!source.equals(LUCKPERMS_SOURCE, ignoreCase = true)) return + if (!source.equals(Constants.LUCKPERMS_SOURCE, ignoreCase = true)) return val luckPerms = platform.getLuckPerms() if (luckPerms == null) { - logger.warning("Source Type is set to $LUCKPERMS_SOURCE, but LuckPerms was not found on the server!") + logger.warn("Source Type is set to ${Constants.LUCKPERMS_SOURCE}, but LuckPerms was not found on the server!") return } @@ -128,9 +120,9 @@ class PrefixesMinestom internal constructor( companion object { /** - * Creates a builder for a new instance. + * Creates a [PrefixesMinestomBuilder]. * - * @param directory The directory the config files are stored in + * @param directory The directory the config files are created in */ @JvmStatic fun builder(directory: Path): PrefixesMinestomBuilder = PrefixesMinestomBuilder(directory) diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt index fd57442..8849d94 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt @@ -22,7 +22,7 @@ class PrefixesMinestomBuilder internal constructor(private val directory: Path) } /** - * Registers the `/scprefix` command. Enabled by default. + * Registers all commands from prefixes. */ fun commands(enabled: Boolean): PrefixesMinestomBuilder = apply { this.commands = enabled @@ -36,7 +36,7 @@ class PrefixesMinestomBuilder internal constructor(private val directory: Path) } /** - * Creates the instance and starts the plugin. + * Creates the instance and starts prefixes. */ fun enable(): PrefixesMinestom { return PrefixesMinestom(directory, permissionHandler, commands, luckPerms).enable() diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt index 157d18f..7c368b1 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt @@ -1,9 +1,7 @@ package app.simplecloud.prefixes.minestom.display import app.simplecloud.prefixes.api.group.PrefixesPlayerData -import app.simplecloud.prefixes.minestom.permission.MinestomPermissions import app.simplecloud.prefixes.shared.Prefixes -import app.simplecloud.prefixes.shared.config.CONFIG_SOURCE import app.simplecloud.prefixes.shared.config.FeaturesConfig import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry @@ -15,28 +13,22 @@ import net.minestom.server.entity.Player import net.minestom.server.scoreboard.Team import java.util.UUID import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicBoolean -import java.util.logging.Level -import java.util.logging.Logger class MinestomDisplayManager( private val prefixes: Prefixes, - private val permissions: MinestomPermissions ) { - private val logger = Logger.getLogger("simplecloud-prefixes") + private val logger = prefixes.getPlatform().getLogger() private val cache = ConcurrentHashMap() private val teams = ConcurrentHashMap() private val nameTags = ConcurrentHashMap() private val entries = ConcurrentHashMap() - private val warned = AtomicBoolean() fun getPlayer(id: UUID): PrefixesPlayerData? { return cache[id] } fun addPlayer(player: Player) { - warnMissingPermissionHandler() updatePlayer(player) } @@ -46,7 +38,7 @@ class MinestomDisplayManager( applyPrefixData(player, data) } }.exceptionally { throwable -> - logger.log(Level.WARNING, "Failed to update prefix data of ${player.username}", throwable) + logger.error("Failed to update prefix data of ${player.username}", throwable) null } } @@ -94,11 +86,7 @@ class MinestomDisplayManager( val features = prefixes.config.get().features val displayName = displayName(data, player, features) - - player.displayName = when { - features.tablist -> PlayerDisplayFormatter.formatTablistName(data, displayName) - else -> null - } + player.displayName = if (features.tablist) PlayerDisplayFormatter.formatTablistName(data, displayName) else null updateTeam(player, data, features) updateNameTag(player, displayName, features) @@ -115,15 +103,7 @@ class MinestomDisplayManager( private fun createTeam(player: Player, data: PrefixesPlayerData, features: FeaturesConfig): Team? { return when { - features.tablist -> MinestomPlayerTeam.create( - player.username, - data.priority, - data.prefix, - data.suffix, - data.color, - hideNameTag = features.displayName - ) - + features.tablist -> MinestomPlayerTeam.create(player.username, data.priority, data.prefix, data.suffix, data.color, features.displayName) // Only there to hide the vanilla name tag, which the name tag entity replaces. features.displayName -> MinestomPlayerTeam.create(player.username, priority = 0, hideNameTag = true) else -> null @@ -141,10 +121,7 @@ class MinestomDisplayManager( return } - val nameTag = nameTags.computeIfAbsent(player.uuid) { - MinestomNameTag(player).also(MinestomNameTag::spawn) - } - + val nameTag = nameTags.computeIfAbsent(player.uuid) { MinestomNameTag(player).also(MinestomNameTag::spawn) } nameTag.setName(displayName) } @@ -180,14 +157,7 @@ class MinestomDisplayManager( } private fun displayName(data: PrefixesPlayerData, player: Player, features: FeaturesConfig): Component { - return PlayerDisplayFormatter.displayName(data, player.username, features.displayName) - } - - private fun warnMissingPermissionHandler() { - if (permissions.hasHandler || !warned.compareAndSet(false, true)) return - if (!prefixes.registry.getCurrentGroupProvider().getName().equals(CONFIG_SOURCE, ignoreCase = true)) return - - logger.warning("Source Type is set to $CONFIG_SOURCE, but no permission handler was set!") + return PlayerDisplayFormatter.formatDisplayName(data, player.username, features.displayName) } } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt index 99d3084..95c3a2e 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt @@ -51,23 +51,17 @@ class PlayerListener( val player = event.player val data = manager.getPlayer(player.uuid) ?: return - val displayName = PlayerDisplayFormatter.displayName(data, player.username, features.displayName) - - val message = PlayerDisplayFormatter.formatChatMessage( - data, - player.username, - Component.text(event.rawMessage), - displayName - ) + val displayName = PlayerDisplayFormatter.formatDisplayName(data, player.username, features.displayName) + val message = PlayerDisplayFormatter.formatChatMessage(data, player.username, Component.text(event.rawMessage), displayName) event.formattedMessage = message prefixes.sync?.publisher?.publishChatMessage(message) } private fun onInput(event: PlayerInputEvent) { - when { - event.hasPressedShiftKey() -> manager.setSneaking(event.player, true) - event.hasReleasedShiftKey() -> manager.setSneaking(event.player, false) + if (event.hasPressedShiftKey() || event.hasReleasedShiftKey()) { + manager.setSneaking(event.player, event.hasPressedShiftKey()) } } + } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt index 0f5f1f5..090cce5 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt @@ -1,6 +1,7 @@ package app.simplecloud.prefixes.minestom.platform import app.simplecloud.plugin.api.shared.permission.PermissionChecker +import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import net.luckperms.api.LuckPerms import net.luckperms.api.LuckPermsProvider @@ -15,6 +16,8 @@ class MinestomPlatformImpl( private val luckPerms: LuckPerms? ) : PrefixesPlatform { + override fun getLogger(): PrefixesLogger = MinestomPrefixesLogger() + override fun getDataDirectory(): File = path.toFile() override fun getPermissionChecker(): PermissionChecker = permissionChecker diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesLogger.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesLogger.kt new file mode 100644 index 0000000..44d804c --- /dev/null +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesLogger.kt @@ -0,0 +1,22 @@ +package app.simplecloud.prefixes.minestom.platform + +import app.simplecloud.prefixes.shared.platform.PrefixesLogger +import org.slf4j.LoggerFactory + +class MinestomPrefixesLogger : PrefixesLogger { + + private val logger = LoggerFactory.getLogger("simplecloud-prefixes") + + override fun info(msg: String) { + logger.info(msg) + } + + override fun warn(msg: String) { + logger.warn(msg) + } + + override fun error(msg: String, cause: Throwable) { + logger.error(msg, cause) + } + +} \ No newline at end of file diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt index fa56c91..3ac9bf6 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt @@ -10,8 +10,8 @@ import app.simplecloud.prefixes.paper.platform.PaperPlatformImpl import app.simplecloud.prefixes.paper.platform.PaperPrefixesListener import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.command.PrefixesCommand -import app.simplecloud.prefixes.shared.config.LUCKPERMS_SOURCE import app.simplecloud.prefixes.shared.platform.PrefixesPlatform +import app.simplecloud.prefixes.shared.utilities.Constants import org.bukkit.Bukkit import org.bukkit.plugin.ServicePriority import org.bukkit.plugin.java.JavaPlugin @@ -81,12 +81,12 @@ class PrefixesPaper : JavaPlugin() { private fun registerLuckPermsListener(prefixes: Prefixes, platform: PrefixesPlatform, manager: PaperDisplayManager) { val source = prefixes.config.get().general.source - logger.info("Using Source Type: $source") - if (!source.equals(LUCKPERMS_SOURCE, ignoreCase = true)) return + platform.getLogger().info("Using Source Type: $source") + if (!source.equals(Constants.LUCKPERMS_SOURCE, ignoreCase = true)) return val luckPerms = platform.getLuckPerms() if (luckPerms == null) { - logger.warning("Source Type is set to $LUCKPERMS_SOURCE, but LuckPerms was not found on the server!") + platform.getLogger().warn("Source Type is set to ${Constants.LUCKPERMS_SOURCE}, but LuckPerms was not found on the server!") return } diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt index ee02f70..96577f8 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt @@ -23,6 +23,8 @@ class PaperDisplayManager( private val prefixes: Prefixes, private val customNameManager: CustomNameManager ) { + private val logger = prefixes.getPlatform().getLogger() + private val cache = ConcurrentHashMap() private val teams = ConcurrentHashMap() private val entries = ConcurrentHashMap() @@ -43,8 +45,8 @@ class PaperDisplayManager( fun updatePlayer(player: Player) { prefixes.api.getPrefixData(player.uniqueId).thenAccept { data -> Bukkit.getScheduler().runTask(plugin, Runnable { applyPrefixData(player, data) }) - }.exceptionally { _ -> - plugin.logger.warning("Failed to update prefix data of ${player.name}") + }.exceptionally { throwable -> + logger.error("Failed to update prefix data of ${player.name}", throwable) null } } @@ -62,7 +64,7 @@ class PaperDisplayManager( cache[player.uniqueId] = data val features = prefixes.config.get().features - val displayName = PlayerDisplayFormatter.displayName( + val displayName = PlayerDisplayFormatter.formatDisplayName( data, player.name, features.displayName @@ -91,15 +93,7 @@ class PaperDisplayManager( private fun createTeam(player: Player, data: PrefixesPlayerData, features: FeaturesConfig): PaperPlayerTeam? { return when { - features.tablist -> PaperPlayerTeam( - player.name, - data.priority, - data.prefix, - data.suffix, - data.color, - hideNameTag = features.displayName - ) - + features.tablist -> PaperPlayerTeam(player.name, data.priority, data.prefix, data.suffix, data.color, features.displayName) features.displayName -> PaperPlayerTeam(player.name, priority = 0) else -> null } @@ -134,7 +128,7 @@ class PaperDisplayManager( Bukkit.getOnlinePlayers().forEach { player -> val data = cache[player.uniqueId] ?: return@forEach val features = prefixes.config.get().features - val displayName = PlayerDisplayFormatter.displayName( + val displayName = PlayerDisplayFormatter.formatDisplayName( data, player.name, features.displayName diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt index e02f24d..f767ff0 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt @@ -41,7 +41,7 @@ class PlayerListener( val player = event.player val data = manager.getPlayer(player.uniqueId) ?: return - val displayName = PlayerDisplayFormatter.displayName(data, player.name, features.displayName) + val displayName = PlayerDisplayFormatter.formatDisplayName(data, player.name, features.displayName) val message = PlayerDisplayFormatter.formatChatMessage( data, diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt index 00711d7..4896e2e 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt @@ -1,6 +1,7 @@ package app.simplecloud.prefixes.paper.platform import app.simplecloud.plugin.api.shared.permission.PermissionChecker +import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import net.luckperms.api.LuckPerms import org.bukkit.Bukkit @@ -10,13 +11,19 @@ import java.util.UUID class PaperPlatformImpl(private val plugin: Plugin) : PrefixesPlatform { - private val permissionChecker: PermissionChecker = PermissionChecker { id, permission -> - Bukkit.getPlayer(id)?.hasPermission(permission) ?: false + override fun getLogger(): PrefixesLogger { + return PaperPrefixesLogger(plugin) } - override fun getDataDirectory(): File = plugin.dataFolder + override fun getDataDirectory(): File { + return plugin.dataFolder + } - override fun getPermissionChecker(): PermissionChecker = permissionChecker + override fun getPermissionChecker(): PermissionChecker { + return PermissionChecker { id, permission -> + Bukkit.getPlayer(id)?.hasPermission(permission) ?: false + } + } override fun getLuckPerms(): LuckPerms? { if (!Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) return null diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesLogger.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesLogger.kt new file mode 100644 index 0000000..195b05c --- /dev/null +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesLogger.kt @@ -0,0 +1,21 @@ +package app.simplecloud.prefixes.paper.platform + +import app.simplecloud.prefixes.shared.platform.PrefixesLogger +import org.bukkit.plugin.Plugin +import java.util.logging.Level + +class PaperPrefixesLogger(private val plugin: Plugin) : PrefixesLogger { + + override fun info(msg: String) { + return plugin.logger.info(msg) + } + + override fun warn(msg: String) { + return plugin.logger.warning(msg) + } + + override fun error(msg: String, thrown: Throwable) { + return plugin.logger.log(Level.SEVERE, msg, thrown) + } + +} \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index 3a3d914..516c362 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -1,7 +1,6 @@ package app.simplecloud.prefixes.shared import app.simplecloud.plugin.api.shared.config.ConfigurationFactory -import app.simplecloud.prefixes.api.PrefixesApi import app.simplecloud.prefixes.api.PrefixesApiProvider import app.simplecloud.prefixes.shared.api.PrefixesApiImpl import app.simplecloud.prefixes.shared.config.DefaultConfigInstaller @@ -15,25 +14,17 @@ import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import app.simplecloud.prefixes.shared.sync.PrefixesSync import java.io.File import java.util.concurrent.CopyOnWriteArrayList -import java.util.logging.Level -import java.util.logging.Logger class Prefixes(private val platform: PrefixesPlatform) { - private val logger = Logger.getLogger("simplecloud-prefixes") + private val logger = platform.getLogger() + private val listeners = CopyOnWriteArrayList() val config = createConfig() - val messages = ConfigurationFactory(File(platform.getDataDirectory(), "messages.yml"), MessageConfig::class.java) - - init { - config.loadOrCreate(PrefixesConfig()) - messages.loadOrCreate(MessageConfig()) - } - - private val listeners = CopyOnWriteArrayList() + val messages = createMessages() val registry = createGroupRegistry() - val api: PrefixesApi = PrefixesApiImpl(registry, listeners, platform) + val api = PrefixesApiImpl(registry, listeners, platform) val sync = createSync() fun startup() { @@ -58,22 +49,35 @@ class Prefixes(private val platform: PrefixesPlatform) { }.onSuccess { logger.info("Succesfully reloaded simplecloud prefixes") }.onFailure { throwable -> - logger.log(Level.SEVERE, "Failed to reload simplecloud prefixes", throwable) + logger.error("Failed to reload simplecloud prefixes", throwable) }.isSuccess } + fun getPlatform(): PrefixesPlatform { + return platform + } + private fun createConfig(): ConfigurationFactory { val file = File(platform.getDataDirectory(), "config.yml") DefaultConfigInstaller.install(file.toPath(), javaClass.classLoader) - return ConfigurationFactory(file, PrefixesConfig::class.java) + + val factory = ConfigurationFactory(file, PrefixesConfig::class.java) + factory.loadOrCreate(PrefixesConfig()) + return factory + } + + private fun createMessages(): ConfigurationFactory { + val factory = ConfigurationFactory(File(platform.getDataDirectory(), "messages.yml"), MessageConfig::class.java) + factory.loadOrCreate(MessageConfig()) + return factory } private fun createGroupRegistry(): GroupProviderRegistry { - val registry = GroupProviderRegistry(config, ConfigGroupProvider(config, platform.getPermissionChecker())) + val registry = GroupProviderRegistry(config, ConfigGroupProvider(logger, config, platform.getPermissionChecker()), platform) val luckPerms = platform.getLuckPerms() if (luckPerms != null) { - registry.register(LuckPermsGroupProvider(luckPerms)) + registry.register(LuckPermsGroupProvider(logger, luckPerms)) } return registry @@ -81,9 +85,14 @@ class Prefixes(private val platform: PrefixesPlatform) { private fun createSync(): PrefixesSync? { if (!isSyncEnabled()) return null - return PrefixesSync(config) + return runCatching { + PrefixesSync(config, logger) + }.onFailure { throwable -> + logger.error("Failed to initialize prefixes sync", throwable) + }.getOrNull() } + private fun isSyncEnabled(): Boolean { val current = config.get() if (!current.sync.enabled) return false diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt index 593dfe5..87cb373 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt @@ -51,12 +51,11 @@ class PrefixesCommand( } val reloaded = prefixes.reload() - val message = when { - reloaded -> messages.command.reload.success - else -> messages.command.reload.failed + if (reloaded) { + sender.sendMessage(messages.msg(messages.command.reload.success)) + } else { + sender.sendMessage(messages.msg(messages.command.reload.failed)) } - - sender.sendMessage(messages.msg(message)) } ) } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt index 9c180ce..86da173 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt @@ -1,6 +1,7 @@ package app.simplecloud.prefixes.shared.config import app.simplecloud.plugin.api.shared.config.VersionedConfig +import app.simplecloud.prefixes.shared.utilities.Constants import app.simplecloud.prefixes.shared.utilities.config.ConfigVersion import app.simplecloud.prefixes.shared.utilities.config.DefaultConfigs import org.spongepowered.configurate.objectmapping.ConfigSerializable @@ -16,7 +17,7 @@ data class PrefixesConfig( @ConfigSerializable data class GeneralConfig( - val source: String = CONFIG_SOURCE, + val source: String = Constants.CONFIG_SOURCE, val defaultGroup: String = "default" ) @@ -31,7 +32,7 @@ data class FeaturesConfig( data class SyncConfig( val enabled: Boolean = true, val channels: SyncChannels = SyncChannels(), - val sources: List = listOf(CURRENT_SYNC_SOURCE) + val sources: List = listOf(Constants.CURRENT_SYNC_SOURCE) ) @ConfigSerializable @@ -50,10 +51,4 @@ data class ConfigGroup( val color: String = "", val displayName: String = "", val chatFormat: String = "" -) - -const val CONFIG_SOURCE = "config" -const val LUCKPERMS_SOURCE = "luckperms" - -const val CURRENT_SYNC_SOURCE = "CURRENT" -const val ALL_SYNC_SOURCE = "ALL" +) \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt index 51366f6..c9f5772 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt @@ -3,48 +3,41 @@ package app.simplecloud.prefixes.shared.group import app.simplecloud.plugin.api.shared.config.ConfigurationFactory import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.shared.config.PrefixesConfig +import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import java.util.concurrent.ConcurrentHashMap -import java.util.logging.Logger class GroupProviderRegistry( private val config: ConfigurationFactory, - private val provider: GroupProvider + private val provider: GroupProvider, + private val platform: PrefixesPlatform ) { - private val logger = Logger.getLogger("simplecloud-prefixes") - + private val logger = platform.getLogger() private val providers = ConcurrentHashMap() - private val sources = ConcurrentHashMap.newKeySet() init { register(provider) } fun register(provider: GroupProvider) { - val key = toKey(provider.getName()) + val key = provider.getName().trim().lowercase() providers[key] = provider - sources.remove(key) } fun unregister(name: String) { - providers.remove(toKey(name)) + providers.remove(name.trim().lowercase()) } fun getAllGroupProviders(): Collection = providers.values.toList() fun getCurrentGroupProvider(): GroupProvider { - val source = toKey(config.get().general.source) - val provider = providers[source] ?: return reportMissing(source) + val source = config.get().general.source.trim().lowercase() + val provider = providers[source] ?: return warnMissingProvider(source) return provider } - private fun reportMissing(source: String): GroupProvider { - if (sources.add(source)) { - logger.warning("No group provider named '$source' is registered, using '${provider.getName()}' instead") - } - + private fun warnMissingProvider(source: String): GroupProvider { + logger.warn("No group provider named '$source' is registered, using '${provider.getName()}' instead") return provider } - - private fun toKey(name: String): String = name.trim().lowercase() } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt index 4c14ace..b390489 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt @@ -7,12 +7,14 @@ import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.shared.config.ConfigGroup import app.simplecloud.prefixes.shared.config.PrefixesConfig +import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.utilities.ColorParser import net.kyori.adventure.text.Component import java.util.UUID import java.util.concurrent.CompletableFuture class ConfigGroupProvider( + private val logger: PrefixesLogger, private val configFactory: ConfigurationFactory, private val permissionChecker: PermissionChecker ) : GroupProvider { @@ -46,8 +48,13 @@ class ConfigGroupProvider( return@synchronized false } - configFactory.save(config.copy(groups = config.groups + createConfigGroup(group))) - true + try { + configFactory.save(config.copy(groups = config.groups + createConfigGroup(group))) + true + } catch (e: Exception) { + logger.error("Failed to save group '${group.name}' to config", e) + false + } } } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt index a1e1c77..df418c9 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt @@ -3,6 +3,7 @@ package app.simplecloud.prefixes.shared.group.luckperms import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup +import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.utilities.ColorParser import net.kyori.adventure.text.Component import net.luckperms.api.LuckPerms @@ -16,6 +17,7 @@ import java.util.UUID import java.util.concurrent.CompletableFuture class LuckPermsGroupProvider( + private val logger: PrefixesLogger, private val luckPerms: LuckPerms ) : GroupProvider { @@ -35,7 +37,12 @@ class LuckPermsGroupProvider( return CompletableFuture.completedFuture(resolveGroup(user)) } - return luckPerms.userManager.loadUser(id).thenApply(::resolveGroup) + return luckPerms.userManager.loadUser(id) + .thenApply(::resolveGroup) + .exceptionally { throwable -> + logger.error("Failed to load LuckPerms user $id", throwable) + null + } } override fun addGroup(group: PrefixesGroup): CompletableFuture { diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt index afae1b0..58272cd 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt @@ -2,11 +2,23 @@ package app.simplecloud.prefixes.shared.platform import java.util.UUID +/** + * The listener that listen to update events. + */ interface PrefixesListener { + /** + * Called when the plugin reloads. + */ fun onReload() + /** + * Called when a player was update or requested by the api. + */ fun onPlayerUpdate(id: UUID) + /** + * Called when all players on this server updated or by the api. + */ fun onAllPlayersUpdate() } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesLogger.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesLogger.kt new file mode 100644 index 0000000..f56b69d --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesLogger.kt @@ -0,0 +1,30 @@ +package app.simplecloud.prefixes.shared.platform + +/** + * A basic logger interface to allow for platform logger implementations. + */ +interface PrefixesLogger { + + /** + * Logs an info message. + * + * @param msg the message to log + */ + fun info(msg: String) + + /** + * Logs a warning message. + * + * @param msg the message to log + */ + fun warn(msg: String) + + /** + * Logs an error message. + * + * @param msg the message to log + * @param cause the throwable that caused the error + */ + fun error(msg: String, cause: Throwable) + +} \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt index 0fd1f32..1e4597a 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt @@ -6,17 +6,22 @@ import java.io.File import java.util.UUID /** - * Represents a platform on which the plugin runs. + * Represents a platform on which prefixes runs. */ interface PrefixesPlatform { /** - * Returns the platform-specific data directory. + * Returns this platform's logger. + */ + fun getLogger(): PrefixesLogger + + /** + * Returns this platform's data directory. */ fun getDataDirectory(): File /** - * Returns the platform-specific permission checker. + * Returns this platform's permission checker. */ fun getPermissionChecker(): PermissionChecker diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt index c40ce48..fc1ec6d 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt @@ -1,8 +1,7 @@ package app.simplecloud.prefixes.shared.sync import app.simplecloud.api.runtime.SimpleCloudRuntime -import app.simplecloud.prefixes.shared.config.ALL_SYNC_SOURCE -import app.simplecloud.prefixes.shared.config.CURRENT_SYNC_SOURCE +import app.simplecloud.prefixes.shared.utilities.Constants class PrefixesSubjects( networkId: String, @@ -27,12 +26,12 @@ class PrefixesSubjects( .distinct() .toList() - if (ALL_SYNC_SOURCE in resolvedSources) { + if (Constants.ALL_SYNC_SOURCE in resolvedSources) { return listOf("$root.*.*.$subject") } return resolvedSources - .map { source -> if (source == CURRENT_SYNC_SOURCE) origin else source } + .map { source -> if (source == Constants.CURRENT_SYNC_SOURCE) origin else source } .distinct() .map { source -> "$root.$source.*.$subject" } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt index e80db5c..3bfc5e0 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt @@ -3,23 +3,34 @@ package app.simplecloud.prefixes.shared.sync import app.simplecloud.api.CloudApi import app.simplecloud.plugin.api.shared.config.ConfigurationFactory import app.simplecloud.prefixes.shared.config.PrefixesConfig +import app.simplecloud.prefixes.shared.platform.PrefixesLogger import io.nats.client.Connection import io.nats.client.Nats import io.nats.client.Options -class PrefixesSync(config: ConfigurationFactory) { +class PrefixesSync( + config: ConfigurationFactory, + private val logger: PrefixesLogger +) { private val api = CloudApi.create() private val connection = createNatsConnection() private val subjects = PrefixesSubjects(api.networkId) - val publisher = SyncPublisher(connection, subjects, config) - val subscriber = SyncSubscriber(connection, subjects, config) + val publisher = SyncPublisher(connection, subjects, config, logger) + val subscriber = SyncSubscriber(connection, subjects, config, logger) fun shutdown() { - subscriber.close() - connection.close() - api.close() + runCatching { + logger.info("Shutdown prefixes sync...") + subscriber.close() + connection.close() + api.close() + }.onFailure { throwable -> + logger.error("Failed to shutdown sync", throwable) + }.onSuccess { + logger.info("Successfully shutdown sync") + } } private fun createNatsConnection(): Connection { diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt index b90ff2b..c854523 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt @@ -4,6 +4,7 @@ import app.simplecloud.plugin.api.shared.config.ConfigurationFactory import app.simplecloud.prefixes.shared.config.FeaturesConfig import app.simplecloud.prefixes.shared.config.PrefixesConfig import app.simplecloud.prefixes.shared.config.SyncChannels +import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistEntryMapper import app.simplecloud.prefixes.shared.utilities.ComponentSerializer @@ -14,11 +15,13 @@ import com.google.protobuf.MessageLite import io.nats.client.Connection import net.kyori.adventure.text.Component import java.util.UUID +import java.util.concurrent.atomic.AtomicBoolean class SyncPublisher( private val connection: Connection, private val subjects: PrefixesSubjects, - private val config: ConfigurationFactory + private val config: ConfigurationFactory, + private val logger: PrefixesLogger ) { fun publishChatMessage(message: Component) = publish( @@ -65,6 +68,14 @@ class SyncPublisher( ) { val config = config.get() if (!feature(config.features) || !config.sync.enabled || !channel(config.sync.channels)) return - connection.publish(subjects.own(subject), message().toByteArray()) + + val target = subjects.own(subject) + + try { + connection.publish(target, message().toByteArray()) + } catch (e: Exception) { + logger.error("Failed to publish sync message on '$subject'", e) + } } + } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt index 9089e45..535c954 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt @@ -4,6 +4,7 @@ import app.simplecloud.plugin.api.shared.config.ConfigurationFactory import app.simplecloud.prefixes.shared.config.FeaturesConfig import app.simplecloud.prefixes.shared.config.PrefixesConfig import app.simplecloud.prefixes.shared.config.SyncChannels +import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistEntryMapper import app.simplecloud.prefixes.shared.utilities.ComponentSerializer @@ -19,7 +20,8 @@ import java.util.UUID class SyncSubscriber( connection: Connection, private val subjects: PrefixesSubjects, - private val config: ConfigurationFactory + private val config: ConfigurationFactory, + private val logger: PrefixesLogger ) { private val dispatcher = connection.createDispatcher(null) @@ -65,7 +67,11 @@ class SyncSubscriber( } fun close() { - dispatcher.drain(Duration.ofSeconds(1)) + try { + dispatcher.drain(Duration.ofSeconds(1)) + } catch (e: Exception) { + logger.error("Failed to drain prefixes sync dispatcher", e) + } } private fun subscribe( @@ -78,16 +84,32 @@ class SyncSubscriber( if (!feature(config.features) || !config.sync.enabled || !channel(config.sync.channels)) return subjects.patterns(config.sync.sources, subject).forEach { pattern -> - dispatcher.subscribe(pattern) { message -> - val currentConfig = this.config.get() - if (feature(currentConfig.features) && - currentConfig.sync.enabled && - channel(currentConfig.sync.channels) && - !subjects.isOwn(message.subject) - ) { + runCatching { subscribe(pattern, feature, channel, handler) }.onFailure { throwable -> + logger.error("Failed to subscribe to the sync subject '$pattern'", throwable) + } + } + } + + private fun subscribe( + pattern: String, + feature: (FeaturesConfig) -> Boolean, + channel: (SyncChannels) -> Boolean, + handler: (Message) -> Unit + ) { + dispatcher.subscribe(pattern) { message -> + val currentConfig = this.config.get() + if (feature(currentConfig.features) && + currentConfig.sync.enabled && + channel(currentConfig.sync.channels) && + !subjects.isOwn(message.subject) + ) { + try { handler(message) + } catch (e: Exception) { + logger.error("Failed to handle sync message on '${message.subject}'", e) } } } } + } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntry.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntry.kt index 7cf4cff..22e2e4e 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntry.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntry.kt @@ -3,9 +3,6 @@ package app.simplecloud.prefixes.shared.sync.tablist import net.kyori.adventure.text.Component import java.util.UUID -/** - * Represents the tablist entry of a player on another server. - */ data class TablistEntry( val uniqueId: UUID, val name: String, diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/Constants.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/Constants.kt new file mode 100644 index 0000000..11f0ce6 --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/Constants.kt @@ -0,0 +1,11 @@ +package app.simplecloud.prefixes.shared.utilities + +object Constants { + + const val CONFIG_SOURCE = "config" + const val LUCKPERMS_SOURCE = "luckperms" + + const val CURRENT_SYNC_SOURCE = "CURRENT" + const val ALL_SYNC_SOURCE = "ALL" + +} \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt index 308e45e..02b6e31 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt @@ -7,7 +7,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder object PlayerDisplayFormatter { - fun displayName(data: PrefixesPlayerData, playerName: String, enabled: Boolean): Component { + fun formatDisplayName(data: PrefixesPlayerData, playerName: String, enabled: Boolean): Component { return if (enabled) data.displayName else Component.text(playerName) } diff --git a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatterTest.kt b/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatterTest.kt index 0ff4b65..35c2c01 100644 --- a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatterTest.kt +++ b/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatterTest.kt @@ -21,13 +21,13 @@ class PlayerDisplayFormatterTest { fun `uses the configured display name when enabled`() { assertEquals( Component.text("Fancy"), - PlayerDisplayFormatter.displayName(data, "Player", enabled = true) + PlayerDisplayFormatter.formatDisplayName(data, "Player", enabled = true) ) } @Test fun `uses the minecraft player name when display names are disabled`() { - val displayName = PlayerDisplayFormatter.displayName(data, "Player", enabled = false) + val displayName = PlayerDisplayFormatter.formatDisplayName(data, "Player", enabled = false) assertEquals(Component.text("Player"), displayName) assertEquals( From 0fd07f9e3ed45f5994d47026161a0a7436edd241 Mon Sep 17 00:00:00 2001 From: Janis Date: Mon, 7 Sep 2026 09:17:13 +0200 Subject: [PATCH 08/22] feat: publishing --- .github/workflows/build.yml | 34 ++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 8 +++++++- build.gradle.kts | 23 +++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..81a3e23 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,34 @@ +name: Build Pull Request + +on: + pull_request: + branches: + - main + types: + - opened + - synchronize + - reopened + +jobs: + build: + name: Build + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + submodules: recursive + + - name: Set up JDK 25 + uses: actions/setup-java@v6 + with: + distribution: 'temurin' + java-version: '25' + cache: 'gradle' + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Build with Gradle + run: ./gradlew clean build shadowJar \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 857c22e..2ea8c6b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: submodules: recursive - name: Set up JDK 25 - uses: actions/setup-java@v5 + uses: actions/setup-java@v6 with: distribution: 'temurin' java-version: '25' @@ -36,6 +36,12 @@ jobs: - name: Build ShadowJars run: ./gradlew clean build shadowJar + - name: Publish to SimpleCloud Repository + run: ./gradlew publish + env: + SIMPLECLOUD_USERNAME: ${{ secrets.SIMPLECLOUD_USERNAME }} + SIMPLECLOUD_PASSWORD: ${{ secrets.SIMPLECLOUD_PASSWORD }} + - name: Get Gradle Version id: gradle_version run: echo "GRADLE_VERSION=$(./gradlew properties -q | grep "version:" | awk '{print $2}')" >> $GITHUB_ENV diff --git a/build.gradle.kts b/build.gradle.kts index 35ac0c2..846a0ff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion plugins { + id("maven-publish") alias(libs.plugins.kotlin) alias(libs.plugins.shadow) } @@ -25,6 +26,7 @@ subprojects { apply { plugin("org.jetbrains.kotlin.jvm") plugin("com.gradleup.shadow") + plugin("maven-publish") } dependencies { @@ -63,4 +65,25 @@ subprojects { ) } + if (name != "example") { + publishing { + publications { + create("maven") { + from(components["java"]) + } + } + + repositories { + maven { + name = "simplecloud" + url = uri("https://repo.simplecloud.app/snapshots") + credentials { + username = System.getenv("SIMPLECLOUD_USERNAME") + password = System.getenv("SIMPLECLOUD_PASSWORD") + } + } + } + } + } + } \ No newline at end of file From 2bf554e96c1ca5d3f8c3e8b68c82d3b883c60491 Mon Sep 17 00:00:00 2001 From: Janis Date: Mon, 7 Sep 2026 09:50:55 +0200 Subject: [PATCH 09/22] feat: standalone support (#17) --- .../src/main/resources/paper-plugin.yml | 2 +- .../app/simplecloud/prefixes/shared/Prefixes.kt | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/prefixes-paper/src/main/resources/paper-plugin.yml b/prefixes-paper/src/main/resources/paper-plugin.yml index a18be6d..d4623c6 100644 --- a/prefixes-paper/src/main/resources/paper-plugin.yml +++ b/prefixes-paper/src/main/resources/paper-plugin.yml @@ -8,7 +8,7 @@ dependencies: server: simplecloud-api: load: BEFORE - required: true + required: false join-classpath: true LuckPerms: load: BEFORE diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index 516c362..f152d00 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -85,13 +85,27 @@ class Prefixes(private val platform: PrefixesPlatform) { private fun createSync(): PrefixesSync? { if (!isSyncEnabled()) return null + + if (!isSimpleCloudAvailable()) { + logger.warn("Sync is enabled in the config, but SimpleCloud was not found on this server.") + return null + } + return runCatching { PrefixesSync(config, logger) }.onFailure { throwable -> - logger.error("Failed to initialize prefixes sync", throwable) + logger.error("Failed to initialize sync", throwable) }.getOrNull() } + private fun isSimpleCloudAvailable(): Boolean { + return try { + Class.forName("app.simplecloud.api.CloudApi") + true + } catch (_: ClassNotFoundException) { + false + } + } private fun isSyncEnabled(): Boolean { val current = config.get() From 456e0b5b802419b1bedfa8dee0fba5e8f17e7cdd Mon Sep 17 00:00:00 2001 From: Janis Date: Sat, 3 Oct 2026 22:56:05 +0200 Subject: [PATCH 10/22] feat: 26.3 (#18) * chore: bump dependencies * feat: 26.3 & custom names v1.2.0 --- build.gradle.kts | 4 +- custom-names | 2 +- gradle/libs.versions.toml | 22 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- prefixes-minestom/build.gradle.kts | 9 +++ prefixes-minestom/example/build.gradle.kts | 11 --- .../minestom/example/ExampleServer.kt | 36 ---------- .../prefixes/minestom/PrefixesMinestom.kt | 5 +- .../display/MinestomDisplayManager.kt | 22 ++---- .../minestom/display/MinestomNameTag.kt | 69 ------------------- .../minestom/listener/PlayerListener.kt | 10 +-- prefixes-paper/build.gradle.kts | 4 +- .../prefixes/paper/PrefixesPaper.kt | 2 +- .../paper/display/PaperDisplayManager.kt | 3 +- settings.gradle.kts | 1 - 15 files changed, 39 insertions(+), 163 deletions(-) delete mode 100644 prefixes-minestom/example/build.gradle.kts delete mode 100644 prefixes-minestom/example/src/main/kotlin/app/simplecloud/prefixes/minestom/example/ExampleServer.kt delete mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomNameTag.kt diff --git a/build.gradle.kts b/build.gradle.kts index 846a0ff..92d31df 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,8 +65,7 @@ subprojects { ) } - if (name != "example") { - publishing { + publishing { publications { create("maven") { from(components["java"]) @@ -84,6 +83,5 @@ subprojects { } } } - } } \ No newline at end of file diff --git a/custom-names b/custom-names index ff3c7bb..1a4642c 160000 --- a/custom-names +++ b/custom-names @@ -1 +1 @@ -Subproject commit ff3c7bb66bc8fa86c058b51641bf0452866ebf16 +Subproject commit 1a4642c476a052b2e24a8db45a84fa3d56821d40 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f1596ca..fccadda 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,33 +1,33 @@ [versions] -kotlin = "2.4.10" +kotlin = "2.4.20" kotlinx-coroutines = "1.11.0" shadow = "9.6.1" minotaur = "2.9.0" -paperweight = "2.0.0-beta.21" +paperweight = "2.0.0-beta.24" protobuf = "0.10.0" -simplecloud-api = "0.1.0-platform.54-dev.1.1-770dcc6" +simplecloud-api = "0.1.0-platform.58-dev.5.1-fe01ce5" simplecloud-plugin-api = "0.0.1-platform.1778966459069-b1e45e2" -paper = "26.2.build.+" -minestom = "2026.08.16-26.2" +paper = "26.3.build.+" +minestom = "26_3-SNAPSHOT" adventure = "5.2.0" luckperms = "5.5" reflection-remapper = "0.1.3" -cloud-command = "2.0.0" -cloud-command-platform = "2.0.0" +cloud-command = "2.1.0" +cloud-command-platform = "2.0.1" cloud-command-minestom = "2.2.0-SNAPSHOT" configurate = "4.2.0" -protoc = "4.35.1" -protobuf-kotlin = "4.35.1" +protoc = "4.36.2" +protobuf-kotlin = "4.36.2" -jnats = "2.26.2" -slf4j = "2.0.18" +jnats = "2.26.3" +slf4j = "2.0.20" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 64a5b37..b589e64 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists \ No newline at end of file diff --git a/prefixes-minestom/build.gradle.kts b/prefixes-minestom/build.gradle.kts index 9a7b56e..908fff9 100644 --- a/prefixes-minestom/build.gradle.kts +++ b/prefixes-minestom/build.gradle.kts @@ -4,3 +4,12 @@ dependencies { implementation(libs.cloud.command.minestom) implementation(libs.slf4j.api) } + +sourceSets { + main { + kotlin { + srcDir(rootProject.layout.projectDirectory.dir("custom-names").dir("custom-names-api/src/main/kotlin")) + srcDir(rootProject.layout.projectDirectory.dir("custom-names").dir("custom-names-minestom/src/main/kotlin")) + } + } +} \ No newline at end of file diff --git a/prefixes-minestom/example/build.gradle.kts b/prefixes-minestom/example/build.gradle.kts deleted file mode 100644 index 6fc02b7..0000000 --- a/prefixes-minestom/example/build.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - application -} - -dependencies { - implementation(project(":prefixes-minestom")) -} - -application { - mainClass.set("app.simplecloud.prefixes.minestom.example.ExampleServerKt") -} diff --git a/prefixes-minestom/example/src/main/kotlin/app/simplecloud/prefixes/minestom/example/ExampleServer.kt b/prefixes-minestom/example/src/main/kotlin/app/simplecloud/prefixes/minestom/example/ExampleServer.kt deleted file mode 100644 index 0349147..0000000 --- a/prefixes-minestom/example/src/main/kotlin/app/simplecloud/prefixes/minestom/example/ExampleServer.kt +++ /dev/null @@ -1,36 +0,0 @@ -package app.simplecloud.prefixes.minestom.example - -import app.simplecloud.prefixes.minestom.PrefixesMinestom -import net.minestom.server.MinecraftServer -import net.minestom.server.command.CommandSender -import net.minestom.server.coordinate.Pos -import net.minestom.server.entity.Player -import net.minestom.server.event.player.AsyncPlayerConfigurationEvent -import net.minestom.server.instance.block.Block -import java.nio.file.Path - -fun main() { - val server = MinecraftServer.init() - - val instance = MinecraftServer.getInstanceManager().createInstanceContainer() - instance.setGenerator { unit -> - unit.modifier().fillHeight(0, 40, Block.GRASS_BLOCK) - } - - MinecraftServer.getGlobalEventHandler().addListener(AsyncPlayerConfigurationEvent::class.java) { event -> - event.spawningInstance = instance - event.player.respawnPoint = Pos(0.0, 41.0, 0.0) - } - - PrefixesMinestom.builder(Path.of("prefixes")) - .commands(true) - .permissionHandler(::hasPermission) - .enable() - - server.start("0.0.0.0", 25565) -} - -private fun hasPermission(sender: CommandSender, permission: String): Boolean { - if (sender !is Player) return true - return permission == "simplecloud.prefix.group.default" -} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt index 0df9a79..4a19706 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt @@ -20,6 +20,7 @@ import net.minestom.server.event.EventNode import net.minestom.server.timer.TaskSchedule import org.incendo.cloud.execution.ExecutionCoordinator import org.incendo.cloud.minestom.MinestomCommandManager +import space.chunks.customname.minestom.CustomNamesMinestom import java.nio.file.Path import java.util.function.BiPredicate @@ -34,7 +35,7 @@ class PrefixesMinestom internal constructor( private val permissions = MinestomPermissions(permissionHandler) private val platform = MinestomPlatformImpl(directory, permissions.getChecker(), luckPerms) private val prefixes = Prefixes(platform) - private val manager = MinestomDisplayManager(prefixes) + private val manager = MinestomDisplayManager(prefixes, CustomNamesMinestom.getManager()) private val tablist = MinestomTablist() private val logger = prefixes.getPlatform().getLogger() @@ -42,6 +43,7 @@ class PrefixesMinestom internal constructor( fun getApi(): PrefixesApi = prefixes.api fun enable(): PrefixesMinestom { + CustomNamesMinestom.init() prefixes.startup() prefixes.addListener(MinestomPrefixesListener(prefixes, manager, tablist)) @@ -64,6 +66,7 @@ class PrefixesMinestom internal constructor( MinecraftServer.getGlobalEventHandler().removeChild(node) manager.clear() tablist.clear() + CustomNamesMinestom.shutdown() prefixes.shutdown() } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt index 7c368b1..2475c61 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt @@ -9,19 +9,21 @@ import app.simplecloud.prefixes.shared.sync.tablist.TablistGameMode import app.simplecloud.prefixes.shared.utilities.PlayerDisplayFormatter import net.kyori.adventure.text.Component import net.minestom.server.MinecraftServer +import net.minestom.server.entity.Entity import net.minestom.server.entity.Player import net.minestom.server.scoreboard.Team +import space.chunks.customname.api.CustomNameManager import java.util.UUID import java.util.concurrent.ConcurrentHashMap class MinestomDisplayManager( private val prefixes: Prefixes, + private val customNameManager: CustomNameManager ) { private val logger = prefixes.getPlatform().getLogger() private val cache = ConcurrentHashMap() private val teams = ConcurrentHashMap() - private val nameTags = ConcurrentHashMap() private val entries = ConcurrentHashMap() fun getPlayer(id: UUID): PrefixesPlayerData? { @@ -46,19 +48,13 @@ class MinestomDisplayManager( fun removePlayer(player: Player) { cache.remove(player.uuid) removeTeam(player.uuid) - removeNameTag(player.uuid) + customNameManager.unregister(player) entries.remove(player.uuid) prefixes.sync?.publisher?.publishTablistRemove(player.uuid) } - fun setSneaking(player: Player, sneaking: Boolean) { - nameTags[player.uuid]?.setSneaking(sneaking) - } - fun refreshNameTag(player: Player) { - if (removeNameTag(player.uuid) == null) return - val data = cache[player.uuid] ?: return val features = prefixes.config.get().features updateNameTag(player, displayName(data, player, features), features) @@ -68,7 +64,6 @@ class MinestomDisplayManager( cache.clear() entries.clear() teams.keys.toList().forEach(::removeTeam) - nameTags.keys.toList().forEach(::removeNameTag) } fun sync(force: Boolean = false) { @@ -117,16 +112,11 @@ class MinestomDisplayManager( private fun updateNameTag(player: Player, displayName: Component, features: FeaturesConfig) { if (!features.displayName) { - removeNameTag(player.uuid) + customNameManager.unregister(player) return } - val nameTag = nameTags.computeIfAbsent(player.uuid) { MinestomNameTag(player).also(MinestomNameTag::spawn) } - nameTag.setName(displayName) - } - - private fun removeNameTag(id: UUID): MinestomNameTag? { - return nameTags.remove(id)?.also(MinestomNameTag::remove) + customNameManager.forEntity(player).setName(displayName) } private fun publish(player: Player, data: PrefixesPlayerData, displayName: Component, force: Boolean = false) { diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomNameTag.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomNameTag.kt deleted file mode 100644 index 3049c52..0000000 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomNameTag.kt +++ /dev/null @@ -1,69 +0,0 @@ -package app.simplecloud.prefixes.minestom.display - -import net.kyori.adventure.text.Component -import net.minestom.server.component.DataComponents -import net.minestom.server.entity.Entity -import net.minestom.server.entity.EntityPose -import net.minestom.server.entity.EntityType -import net.minestom.server.entity.Player -import net.minestom.server.entity.metadata.other.InteractionMeta -import java.util.concurrent.atomic.AtomicBoolean - -class MinestomNameTag(private val player: Player) { - - private val entity = Entity(EntityType.INTERACTION) - private val removed = AtomicBoolean() - - init { - entity.updateViewableRule { viewer -> viewer != player } - - edit { meta -> - meta.width = WIDTH - meta.height = HEIGHT - meta.isInvisible = true - meta.pose = EntityPose.CROAKING - } - } - - fun spawn() { - val instance = player.instance ?: return - - // Spawned as close to its final position as possible, so the client does not - // interpolate it into place once it becomes a passenger. - entity.setInstance(instance, player.position.add(0.0, PASSENGER_OFFSET, 0.0)) - .thenRun { - // The player can disconnect while the instance is still being set, in which case - // addPassenger would throw on an entity that no longer has an instance. - if (removed.get() || entity.isRemoved || !player.isOnline || player.instance == null) return@thenRun - player.addPassenger(entity) - } - } - - fun setName(name: Component) { - entity.set(DataComponents.CUSTOM_NAME, name) - edit { meta -> meta.isCustomNameVisible = true } - } - - fun setSneaking(sneaking: Boolean) { - edit { meta -> meta.isSneaking = sneaking } - } - - fun remove() { - if (!removed.compareAndSet(false, true)) return - - if (player.instance != null) { - player.removePassenger(entity) - } - entity.remove() - } - - private fun edit(editor: (InteractionMeta) -> Unit) { - entity.editEntityMeta(InteractionMeta::class.java) { meta -> editor(meta) } - } - - private companion object { - const val WIDTH = 0.6f - const val HEIGHT = 0.0f - const val PASSENGER_OFFSET = 1.8 - } -} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt index 95c3a2e..9ae78f8 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt @@ -9,7 +9,6 @@ import net.minestom.server.event.Event import net.minestom.server.event.EventNode import net.minestom.server.event.player.PlayerChatEvent import net.minestom.server.event.player.PlayerDisconnectEvent -import net.minestom.server.event.player.PlayerInputEvent import net.minestom.server.event.player.PlayerSpawnEvent class PlayerListener( @@ -22,11 +21,10 @@ class PlayerListener( node.addListener(PlayerSpawnEvent::class.java, ::onSpawn) node.addListener(PlayerDisconnectEvent::class.java, ::onDisconnect) node.addListener(PlayerChatEvent::class.java, ::onChat) - node.addListener(PlayerInputEvent::class.java, ::onInput) } private fun onSpawn(event: PlayerSpawnEvent) { - // The entity carrying the name tag stays behind in the previous instance. + // custom-names drops the name tag when the player leaves an instance. if (!event.isFirstSpawn) { manager.refreshNameTag(event.player) return @@ -58,10 +56,4 @@ class PlayerListener( prefixes.sync?.publisher?.publishChatMessage(message) } - private fun onInput(event: PlayerInputEvent) { - if (event.hasPressedShiftKey() || event.hasReleasedShiftKey()) { - manager.setSneaking(event.player, event.hasPressedShiftKey()) - } - } - } diff --git a/prefixes-paper/build.gradle.kts b/prefixes-paper/build.gradle.kts index 4b40fd6..6699789 100644 --- a/prefixes-paper/build.gradle.kts +++ b/prefixes-paper/build.gradle.kts @@ -6,7 +6,7 @@ plugins { val customNamesRoot = rootProject.layout.projectDirectory.dir("custom-names") require( customNamesRoot.dir("custom-names-api/src/main/kotlin").asFile.isDirectory && - customNamesRoot.dir("custom-names-plugin/src/main/kotlin").asFile.isDirectory + customNamesRoot.dir("custom-names-paper/src/main/kotlin").asFile.isDirectory ) { "The custom-names submodule is missing. Run `git submodule update --init --recursive`." } @@ -23,7 +23,7 @@ sourceSets { main { kotlin { srcDir(customNamesRoot.dir("custom-names-api/src/main/kotlin")) - srcDir(customNamesRoot.dir("custom-names-plugin/src/main/kotlin")) + srcDir(customNamesRoot.dir("custom-names-paper/src/main/kotlin")) exclude("**/CustomNamesPlugin.kt") } } diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt index 3ac9bf6..59cb50f 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt @@ -17,7 +17,7 @@ import org.bukkit.plugin.ServicePriority import org.bukkit.plugin.java.JavaPlugin import org.incendo.cloud.execution.ExecutionCoordinator import org.incendo.cloud.paper.PaperCommandManager -import space.chunks.customname.plugin.CustomNameManagerImpl +import space.chunks.customname.paper.CustomNameManagerImpl class PrefixesPaper : JavaPlugin() { diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt index 96577f8..216ca33 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt @@ -12,6 +12,7 @@ import net.kyori.adventure.text.Component import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket import org.bukkit.Bukkit import org.bukkit.craftbukkit.entity.CraftPlayer +import org.bukkit.entity.Entity import org.bukkit.entity.Player import org.bukkit.plugin.Plugin import space.chunks.customname.api.CustomNameManager @@ -21,7 +22,7 @@ import java.util.concurrent.ConcurrentHashMap class PaperDisplayManager( private val plugin: Plugin, private val prefixes: Prefixes, - private val customNameManager: CustomNameManager + private val customNameManager: CustomNameManager ) { private val logger = prefixes.getPlatform().getLogger() diff --git a/settings.gradle.kts b/settings.gradle.kts index bb04fe4..a6e6b4b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,7 +5,6 @@ plugins { include( "prefixes-api", "prefixes-minestom", - "prefixes-minestom:example", "prefixes-paper", "prefixes-shared" ) From 7e15bf4ae2494ab5e585e172b33be412d2481437 Mon Sep 17 00:00:00 2001 From: Janis Date: Wed, 7 Oct 2026 23:38:45 +0200 Subject: [PATCH 11/22] feat: more api features --- build.gradle.kts | 24 +- .../simplecloud/prefixes/api/PrefixesApi.kt | 114 ++++++---- .../prefixes/api/PrefixesApiProvider.kt | 6 +- .../prefixes/api/group/GroupProvider.kt | 24 +- .../prefixes/api/group/PrefixesGroup.kt | 50 ++--- .../prefixes/api/group/PrefixesPlayerData.kt | 4 +- prefixes-minestom/build.gradle.kts | 2 +- .../prefixes/minestom/PrefixesMinestom.kt | 70 +++--- .../minestom/PrefixesMinestomBuilder.kt | 39 ++-- .../display/MinestomDisplayManager.kt | 177 ++++++++------- .../minestom/display/MinestomPlayerTeam.kt | 52 +++-- .../minestom/display/MinestomTablist.kt | 122 +++------- .../minestom/listener/PlayerListener.kt | 29 ++- .../permission/MinestomPermissions.kt | 16 +- .../minestom/platform/MinestomPlatformImpl.kt | 5 +- .../platform/MinestomPrefixesListener.kt | 15 +- .../platform/MinestomPrefixesLogger.kt | 22 -- prefixes-paper/build.gradle.kts | 2 +- .../prefixes/paper/PrefixesPaper.kt | 43 +--- .../paper/display/PaperDisplayManager.kt | 210 +++++++++--------- .../prefixes/paper/display/PaperPlayerTeam.kt | 2 +- .../prefixes/paper/display/PaperTablist.kt | 145 +++--------- .../prefixes/paper/listener/PlayerListener.kt | 28 +-- .../paper/platform/PaperPlatformImpl.kt | 7 +- .../paper/platform/PaperPrefixesListener.kt | 17 +- .../paper/platform/PaperPrefixesLogger.kt | 21 -- .../src/main/resources/paper-plugin.yml | 2 +- prefixes-shared/build.gradle.kts | 3 +- .../simplecloud/prefixes/shared/Prefixes.kt | 45 ++-- .../prefixes/shared/PrefixesConstants.kt | 34 +++ .../prefixes/shared/api/PrefixesApiImpl.kt | 145 +++++++++--- .../shared/command/PrefixesCommand.kt | 5 - .../prefixes/shared/config/MessageConfig.kt | 6 - .../prefixes/shared/config/PrefixesConfig.kt | 16 +- .../shared/data/PrefixesDataRegistry.kt | 92 ++++++++ .../prefixes/shared/data/PrefixesOverride.kt | 43 ++++ .../prefixes/shared/data/ViewerKey.kt | 5 + .../shared/group/GroupProviderRegistry.kt | 26 ++- .../group/config/ConfigGroupProvider.kt | 23 +- .../group/config/ConfigPrefixesGroup.kt | 11 +- .../shared/group/luckperms/LuckPermsGroup.kt | 25 ++- .../group/luckperms/LuckPermsGroupProvider.kt | 50 +++-- .../shared/platform/PrefixesListener.kt | 16 +- .../shared/platform/PrefixesLogger.kt | 30 --- .../shared/platform/PrefixesPlatform.kt | 10 +- .../prefixes/shared/sync/PrefixesSubjects.kt | 6 +- .../prefixes/shared/sync/PrefixesSync.kt | 13 +- .../prefixes/shared/sync/SyncPublisher.kt | 47 +--- .../prefixes/shared/sync/SyncSubscriber.kt | 69 ++---- .../shared/utilities/AudienceResolver.kt | 14 ++ .../prefixes/shared/utilities/ColorParser.kt | 16 +- .../prefixes/shared/utilities/Constants.kt | 11 - .../utilities/PlayerDisplayFormatter.kt | 14 +- .../shared/utilities/config/DefaultConfigs.kt | 37 +-- prefixes-shared/src/main/resources/config.yml | 11 +- .../shared/config/PrefixesConfigTest.kt | 49 ---- .../luckperms/LuckPermsGroupProviderTest.kt | 97 -------- .../shared/sync/PrefixesSubjectsTest.kt | 71 ------ .../shared/sync/tablist/TablistEntryTest.kt | 47 ---- .../utilities/PlayerDisplayFormatterTest.kt | 38 ---- 60 files changed, 1058 insertions(+), 1315 deletions(-) delete mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesLogger.kt delete mode 100644 prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesLogger.kt create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesOverride.kt create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/ViewerKey.kt delete mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesLogger.kt create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/AudienceResolver.kt delete mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/Constants.kt delete mode 100644 prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfigTest.kt delete mode 100644 prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProviderTest.kt delete mode 100644 prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjectsTest.kt delete mode 100644 prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntryTest.kt delete mode 100644 prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatterTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 92d31df..ef3ec22 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -66,22 +66,22 @@ subprojects { } publishing { - publications { - create("maven") { - from(components["java"]) - } + publications { + create("maven") { + from(components["java"]) } + } - repositories { - maven { - name = "simplecloud" - url = uri("https://repo.simplecloud.app/snapshots") - credentials { - username = System.getenv("SIMPLECLOUD_USERNAME") - password = System.getenv("SIMPLECLOUD_PASSWORD") - } + repositories { + maven { + name = "simplecloud" + url = uri("https://repo.simplecloud.app/snapshots") + credentials { + username = System.getenv("SIMPLECLOUD_USERNAME") + password = System.getenv("SIMPLECLOUD_PASSWORD") } } } + } } \ No newline at end of file diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt index cd396b9..17b85a5 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt @@ -3,6 +3,9 @@ package app.simplecloud.prefixes.api import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.api.group.PrefixesPlayerData +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor import java.util.UUID import java.util.concurrent.CompletableFuture @@ -11,64 +14,93 @@ import java.util.concurrent.CompletableFuture */ interface PrefixesApi { - /** - * Returns all registered [PrefixesGroup] ordered by priority. - */ + /** Returns all registered groups ordered by priority. */ fun getGroups(): CompletableFuture> - /** - * Returns the highest-priority applicable [PrefixesGroup] of a player. - * @param id Target player UUID - */ + /** Returns the highest-priority group of the player [id], or null if none matches. */ fun getPrimaryGroup(id: UUID): CompletableFuture - /** - * Returns the prefix data for a player. - * @param id Target player UUID - */ + /** Returns the prefix data everyone sees for the player [id]. */ fun getPrefixData(id: UUID): CompletableFuture - /** - * Adds a new group to the group provider. - * - * @param group The group to add - * @return A future completing with `true` if the group was created, or `false` if a group with that name already exists. - */ + /** Returns the prefix data [viewer] sees for the player [id]. */ + fun getPrefixData(id: UUID, viewer: Audience): CompletableFuture + + /** Sets the [prefix] of the player [id] for everyone. */ + fun setPrefix(id: UUID, prefix: Component) + + /** Sets the [prefix] of the player [id], visible only to [viewers]. */ + fun setPrefix(id: UUID, prefix: Component, viewers: Audience) + + /** Sets the [suffix] of the player [id] for everyone. */ + fun setSuffix(id: UUID, suffix: Component) + + /** Sets the [suffix] of the player [id], visible only to [viewers]. */ + fun setSuffix(id: UUID, suffix: Component, viewers: Audience) + + /** Sets the name [color] of the player [id] for everyone. */ + fun setColor(id: UUID, color: TextColor) + + /** Sets the name [color] of the player [id], visible only to [viewers]. */ + fun setColor(id: UUID, color: TextColor, viewers: Audience) + + /** Sets the tablist [priority] of the player [id] for everyone, higher is listed further up. */ + fun setPriority(id: UUID, priority: Int) + + /** Sets the tablist [priority] of the player [id], visible only to [viewers]. */ + fun setPriority(id: UUID, priority: Int, viewers: Audience) + + /** Shows the player [id] with the group named [group] for everyone, completes with `false` if it does not exist. */ + fun setGroup(id: UUID, group: String): CompletableFuture + + /** Shows the player [id] with the group named [group] to [viewers], completes with `false` if it does not exist. */ + fun setGroup(id: UUID, group: String, viewers: Audience): CompletableFuture + + /** Shows all players in [targets] with the group named [group] for everyone, completes with `false` if it does not exist. */ + fun setGroup(targets: Audience, group: String): CompletableFuture + + /** Shows all players in [targets] with the group named [group] to [viewers], completes with `false` if it does not exist. */ + fun setGroup(targets: Audience, group: String, viewers: Audience): CompletableFuture + + /** Shows the player [id] with [group] for everyone. */ + fun setGroup(id: UUID, group: PrefixesGroup) + + /** Shows the player [id] with [group] to [viewers]. */ + fun setGroup(id: UUID, group: PrefixesGroup, viewers: Audience) + + /** Shows all players in [targets] with [group] for everyone. */ + fun setGroup(targets: Audience, group: PrefixesGroup) + + /** Shows all players in [targets] with [group] to [viewers]. */ + fun setGroup(targets: Audience, group: PrefixesGroup, viewers: Audience) + + /** Removes all manually set values of the player [id]. */ + fun reset(id: UUID) + + /** Removes all manually set values of the players in [targets]. */ + fun reset(targets: Audience) + + /** Removes the values set for [viewers] from the players in [targets]. */ + fun reset(targets: Audience, viewers: Audience) + + /** Adds [group] to the current group provider, completes with `false` if it already exists. */ fun addGroup(group: PrefixesGroup): CompletableFuture - /** - * Registers a group provider under its [GroupProvider.name]. - * - * @param provider The provider to register - */ + /** Registers [provider] under its name. */ fun registerGroupProvider(provider: GroupProvider) - /** - * Removes a registered group provider. - * - * @param name Name of the provider to remove - */ + /** Removes the group provider named [name]. */ fun unregisterGroupProvider(name: String) - /** - * Returns the provider currently used. - */ + /** Returns the group provider currently used. */ fun getGroupProvider(): GroupProvider - /** - * Returns all registered group providers. - */ + /** Returns all registered group providers. */ fun getGroupProviders(): Collection - /** - * Reapplies the prefix data of a player on this server. - * - * @param id Target player UUID - */ + /** Reloads the group of the player [id], manually set values stay. */ fun refreshPlayer(id: UUID) - /** - * Reapplies the prefix data of all players on this server. - */ + /** Reloads the groups of all players on this server, manually set values stay. */ fun refreshAll() } diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApiProvider.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApiProvider.kt index ccfcb58..3e6b11f 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApiProvider.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApiProvider.kt @@ -7,13 +7,13 @@ import org.jetbrains.annotations.ApiStatus */ object PrefixesApiProvider { + @Volatile private var instance: PrefixesApi? = null /** - * Gets the registered [PrefixesApi] instance. + * Returns the [PrefixesApi] instance. * - * @return The prefixes API instance - * @throws IllegalStateException If the API is not loaded yet + * @throws IllegalStateException if prefixes is not loaded yet */ @JvmStatic fun get(): PrefixesApi { diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt index bfb97ff..b4b90af 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt @@ -4,33 +4,19 @@ import java.util.UUID import java.util.concurrent.CompletableFuture /** - * Represents a source provider for groups. + * A source of prefix groups, e.g. the config or LuckPerms. */ interface GroupProvider { - /** - * The name of this provider. - */ + /** Returns the name of this provider. */ fun getName(): String - /** - * Returns all registered prefix groups. - */ + /** Returns all groups of this provider. */ fun getGroups(): CompletableFuture> - /** - * Gets the highest-priority applicable group of a player. - * - * @param id The player's UUID - * @return A future completing with the player's highest-priority group, or null if none matches. - */ + /** Returns the highest-priority group of the player [id], or null if none matches. */ fun getGroup(id: UUID): CompletableFuture - /** - * Adds a new group to this provider's source. - * - * @param group The group to add - * @return A future completing with `true` if the group was created, or `false` if a group with that name already exists. - */ + /** Adds [group] to this provider, completes with `false` if it already exists. */ fun addGroup(group: PrefixesGroup): CompletableFuture } diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt index 9787ec6..7a5e638 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt @@ -6,56 +6,34 @@ import java.util.UUID import java.util.concurrent.CompletableFuture /** - * Represents a prefixes group. + * A group that defines how its players are shown. */ interface PrefixesGroup { - /** - * The name of this group. - */ + /** The name of this group. */ val name: String - /** - * The priority of this group (higher priority values override lower ones). - */ + /** The priority of this group, higher wins and is listed further up. */ val priority: Int - /** - * The permission required for this group, or an empty string if none. - */ + /** The permission required for this group, or an empty string if none. */ val permission: String - /** - * The prefix of this group. - */ - val prefix: Component? + /** The prefix of this group. */ + val prefix: Component - /** - * The suffix of this group. - */ - val suffix: Component? + /** The suffix of this group. */ + val suffix: Component - /** - * The primary color of this group. - */ - val color: TextColor? + /** The name color of this group. */ + val color: TextColor - /** - * The display name format of this group. - */ + /** The MiniMessage format of the display name. */ val displayName: String - /** - * The chat format of this group. - */ + /** The MiniMessage format of chat messages. */ val chatFormat: String - /** - * Checks whether the specified player is a member of this group. - * - * @param id the UUID of the player - * @return a future completing with `true` if the player belongs to this group, - * otherwise `false` - */ + /** Returns whether the player [id] is a member of this group. */ fun containsPlayer(id: UUID): CompletableFuture -} \ No newline at end of file +} diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesPlayerData.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesPlayerData.kt index bd55687..d39c602 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesPlayerData.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesPlayerData.kt @@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.TextColor /** - * Represents the prefixes data of a player. + * The prefixes data from a player. */ data class PrefixesPlayerData( val prefix: Component, @@ -13,4 +13,4 @@ data class PrefixesPlayerData( val displayName: Component, val chatFormat: String, val priority: Int -) \ No newline at end of file +) diff --git a/prefixes-minestom/build.gradle.kts b/prefixes-minestom/build.gradle.kts index 908fff9..8af7b68 100644 --- a/prefixes-minestom/build.gradle.kts +++ b/prefixes-minestom/build.gradle.kts @@ -1,8 +1,8 @@ dependencies { api(project(":prefixes-shared")) implementation(libs.minestom) - implementation(libs.cloud.command.minestom) implementation(libs.slf4j.api) + implementation(libs.cloud.command.minestom) } sourceSets { diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt index 4a19706..0dfd52c 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt @@ -11,38 +11,42 @@ import app.simplecloud.prefixes.minestom.platform.MinestomPlatformImpl import app.simplecloud.prefixes.minestom.platform.MinestomPrefixesListener import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.command.PrefixesCommand -import app.simplecloud.prefixes.shared.utilities.Constants +import app.simplecloud.prefixes.shared.PrefixesConstants +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import net.luckperms.api.LuckPerms import net.minestom.server.MinecraftServer import net.minestom.server.adventure.audience.Audiences -import net.minestom.server.command.CommandSender +import net.minestom.server.entity.Player import net.minestom.server.event.EventNode -import net.minestom.server.timer.TaskSchedule import org.incendo.cloud.execution.ExecutionCoordinator import org.incendo.cloud.minestom.MinestomCommandManager import space.chunks.customname.minestom.CustomNamesMinestom import java.nio.file.Path import java.util.function.BiPredicate +import kotlin.time.Duration.Companion.seconds class PrefixesMinestom internal constructor( directory: Path, - private val permissionHandler: BiPredicate?, - private val commands: Boolean, - luckPerms: LuckPerms? + luckPerms: LuckPerms?, + groupPermission: BiPredicate?, + private val registerCommands: Boolean, + commandPermission: BiPredicate? ) { private val node = EventNode.all("simplecloud-prefixes") - private val permissions = MinestomPermissions(permissionHandler) + private val permissions = MinestomPermissions(groupPermission, commandPermission) private val platform = MinestomPlatformImpl(directory, permissions.getChecker(), luckPerms) private val prefixes = Prefixes(platform) private val manager = MinestomDisplayManager(prefixes, CustomNamesMinestom.getManager()) private val tablist = MinestomTablist() - private val logger = prefixes.getPlatform().getLogger() + private val logger = PrefixesConstants.LOGGER /** Gets the [PrefixesApi] instance. */ fun getApi(): PrefixesApi = prefixes.api - fun enable(): PrefixesMinestom { + fun init(): PrefixesMinestom { CustomNamesMinestom.init() prefixes.startup() prefixes.addListener(MinestomPrefixesListener(prefixes, manager, tablist)) @@ -50,67 +54,49 @@ class PrefixesMinestom internal constructor( PlayerListener(prefixes, manager, tablist).register(node) MinecraftServer.getGlobalEventHandler().addChild(node) - if (prefixes.config.get().general.source.equals(Constants.CONFIG_SOURCE, ignoreCase = true) && permissionHandler == null) { - logger.warn("Source Type is set to '${Constants.CONFIG_SOURCE}', but no permission handler was registered!") + if (prefixes.config.get().general.source.equals(PrefixesConstants.CONFIG_SOURCE, ignoreCase = true) && !permissions.hasGroupPermission) { + logger.warn("Source Type is set to '${PrefixesConstants.CONFIG_SOURCE}', but no group permission check was registered!") } registerSync() registerLuckPermsListener() registerCommands() - - MinecraftServer.getSchedulerManager().buildShutdownTask { disable() } + prefixes.api.refreshAll() return this } - private fun disable() { + fun shutdown() { MinecraftServer.getGlobalEventHandler().removeChild(node) - manager.clear() - tablist.clear() - CustomNamesMinestom.shutdown() prefixes.shutdown() + CustomNamesMinestom.shutdown() } - private fun registerSync() { val sync = prefixes.sync ?: return - val config = prefixes.config.get() - - if (config.features.chat && config.sync.channels.chat) { - sync.subscriber.subscribeChatMessage { message -> Audiences.players().sendMessage(message) } - } - - if (!config.features.tablist || !config.sync.channels.tablist) return + sync.subscriber.subscribeChatMessage { message -> Audiences.players().sendMessage(message) } sync.subscriber.subscribeTablist( onUpdate = tablist::update, onRemove = { publisherId, id -> tablist.remove(publisherId, id) }, - onRequest = { manager.sync(force = true) } + onRequest = { MinecraftServer.getSchedulerManager().scheduleNextTick { manager.publishAll(true) } } ) sync.publisher.publishTablistRequest() - MinecraftServer.getSchedulerManager() - .buildTask { manager.sync() } - .delay(TaskSchedule.tick(600)) - .repeat(TaskSchedule.tick(600)) - .schedule() + PrefixesConstants.SCOPE.launch { + while (isActive) { + delay(30.seconds) + MinecraftServer.getSchedulerManager().scheduleNextTick { manager.publishAll() } + } + } } private fun registerLuckPermsListener() { - val source = prefixes.config.get().general.source - logger.info("Using Source Type: $source") - if (!source.equals(Constants.LUCKPERMS_SOURCE, ignoreCase = true)) return - - val luckPerms = platform.getLuckPerms() - if (luckPerms == null) { - logger.warn("Source Type is set to ${Constants.LUCKPERMS_SOURCE}, but LuckPerms was not found on the server!") - return - } - + val luckPerms = platform.getLuckPerms() ?: return LuckPermsListener(luckPerms, manager).register() } private fun registerCommands() { - if (!commands) return + if (!registerCommands) return val commandManager = MinestomCommandManager( ExecutionCoordinator.asyncCoordinator(), diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt index 8849d94..31d5cf2 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt @@ -1,7 +1,7 @@ package app.simplecloud.prefixes.minestom import net.luckperms.api.LuckPerms -import net.minestom.server.command.CommandSender +import net.minestom.server.entity.Player import java.nio.file.Path import java.util.function.BiPredicate @@ -10,35 +10,48 @@ import java.util.function.BiPredicate */ class PrefixesMinestomBuilder internal constructor(private val directory: Path) { - private var permissionHandler: BiPredicate? = null - private var commands = true private var luckPerms: LuckPerms? = null + private var groupPermission: BiPredicate? = null + private var registerCommands = true + private var commandPermission: BiPredicate? = null /** - * Sets the permission handler. + * Sets the LuckPerms instance used when the group source is `luckperms`. */ - fun permissionHandler(handler: BiPredicate): PrefixesMinestomBuilder = apply { - this.permissionHandler = handler + fun luckPerms(luckPerms: LuckPerms): PrefixesMinestomBuilder { + this.luckPerms = luckPerms + return this } /** - * Registers all commands from prefixes. + * Sets the check that decides which group a player gets when the group source is `config`. */ - fun commands(enabled: Boolean): PrefixesMinestomBuilder = apply { - this.commands = enabled + fun groupPermission(check: BiPredicate): PrefixesMinestomBuilder { + this.groupPermission = check + return this } /** - * Sets the LuckPerms instance used when the group source is `luckperms`. + * Sets whether the prefixes commands are registered. */ - fun luckPerms(luckPerms: LuckPerms): PrefixesMinestomBuilder = apply { - this.luckPerms = luckPerms + fun registerCommands(enabled: Boolean): PrefixesMinestomBuilder { + this.registerCommands = enabled + return this + } + + /** + * Sets the check that decides whether a player may use a prefixes command. + * The console is always allowed. Without a check, only players with permission level 4 are allowed. + */ + fun commandPermission(check: BiPredicate): PrefixesMinestomBuilder { + this.commandPermission = check + return this } /** * Creates the instance and starts prefixes. */ fun enable(): PrefixesMinestom { - return PrefixesMinestom(directory, permissionHandler, commands, luckPerms).enable() + return PrefixesMinestom(directory, luckPerms, groupPermission, registerCommands, commandPermission).init() } } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt index 2475c61..90dfd63 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt @@ -2,16 +2,20 @@ package app.simplecloud.prefixes.minestom.display import app.simplecloud.prefixes.api.group.PrefixesPlayerData import app.simplecloud.prefixes.shared.Prefixes -import app.simplecloud.prefixes.shared.config.FeaturesConfig +import app.simplecloud.prefixes.shared.data.ViewerKey import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistGameMode +import app.simplecloud.prefixes.shared.PrefixesConstants import app.simplecloud.prefixes.shared.utilities.PlayerDisplayFormatter +import kotlinx.coroutines.future.await +import kotlinx.coroutines.launch import net.kyori.adventure.text.Component import net.minestom.server.MinecraftServer import net.minestom.server.entity.Entity import net.minestom.server.entity.Player -import net.minestom.server.scoreboard.Team +import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket +import net.minestom.server.network.packet.server.play.TeamsPacket import space.chunks.customname.api.CustomNameManager import java.util.UUID import java.util.concurrent.ConcurrentHashMap @@ -21,133 +25,154 @@ class MinestomDisplayManager( private val customNameManager: CustomNameManager ) { - private val logger = prefixes.getPlatform().getLogger() - private val cache = ConcurrentHashMap() - private val teams = ConcurrentHashMap() - private val entries = ConcurrentHashMap() - - fun getPlayer(id: UUID): PrefixesPlayerData? { - return cache[id] - } + private val registry = prefixes.dataRegistry - fun addPlayer(player: Player) { - updatePlayer(player) - } + private val teams = ConcurrentHashMap() + private val entries = ConcurrentHashMap() fun updatePlayer(player: Player) { - prefixes.api.getPrefixData(player.uuid).thenAccept { data -> + PrefixesConstants.SCOPE.launch { + val group = prefixes.api.getPrimaryGroup(player.uuid).await() MinecraftServer.getSchedulerManager().scheduleNextTick { - applyPrefixData(player, data) + if (!player.isOnline) return@scheduleNextTick + registry.loadGroup(player.uuid, group) + render(player) } - }.exceptionally { throwable -> - logger.error("Failed to update prefix data of ${player.username}", throwable) - null } } fun removePlayer(player: Player) { - cache.remove(player.uuid) - removeTeam(player.uuid) + val id = player.uuid + registry.remove(id) + teams.keys.filter { key -> key.target == id || key.viewer == id }.forEach { key -> + if (key.viewer == id) teams.remove(key) else removeTeam(key) + } customNameManager.unregister(player) - entries.remove(player.uuid) - prefixes.sync?.publisher?.publishTablistRemove(player.uuid) - } - - fun refreshNameTag(player: Player) { - val data = cache[player.uuid] ?: return - val features = prefixes.config.get().features - updateNameTag(player, displayName(data, player, features), features) + entries.remove(id) + prefixes.sync?.publisher?.publishTablistRemove(id) } fun clear() { - cache.clear() entries.clear() teams.keys.toList().forEach(::removeTeam) + onlinePlayers().forEach { player -> player.displayName = null } } - fun sync(force: Boolean = false) { - MinecraftServer.getConnectionManager().onlinePlayers.forEach { player -> - val data = cache[player.uuid] ?: return@forEach - val features = prefixes.config.get().features - - publish(player, data, displayName(data, player, features), force) - } - } - - private fun applyPrefixData(player: Player, data: PrefixesPlayerData) { + fun render(player: Player) { if (!player.isOnline) return - cache[player.uuid] = data + val data = registry.getData(player.uuid) ?: return - val features = prefixes.config.get().features - val displayName = displayName(data, player, features) - player.displayName = if (features.tablist) PlayerDisplayFormatter.formatTablistName(data, displayName) else null + player.displayName = if (prefixes.config.get().features.tablist) formatTablistName(player, data) else null - updateTeam(player, data, features) - updateNameTag(player, displayName, features) + val team = createTeam(player, data) + onlinePlayers().forEach { viewer -> renderFor(player, viewer, team) } - publish(player, data, displayName) + updateNameTag(player, data) + publish(player) } - private fun updateTeam(player: Player, data: PrefixesPlayerData, features: FeaturesConfig) { - removeTeam(player.uuid) - - val team = createTeam(player, data, features) ?: return - teams[player.uuid] = team + fun refreshNameTag(player: Player) { + val data = registry.getData(player.uuid) ?: return + updateNameTag(player, data) } - private fun createTeam(player: Player, data: PrefixesPlayerData, features: FeaturesConfig): Team? { - return when { - features.tablist -> MinestomPlayerTeam.create(player.username, data.priority, data.prefix, data.suffix, data.color, features.displayName) - // Only there to hide the vanilla name tag, which the name tag entity replaces. - features.displayName -> MinestomPlayerTeam.create(player.username, priority = 0, hideNameTag = true) - else -> null + fun addViewer(viewer: Player) { + onlinePlayers().forEach { player -> + val data = registry.getData(player.uuid) ?: return@forEach + renderFor(player, viewer, createTeam(player, data)) } } - private fun removeTeam(id: UUID) { - val team = teams.remove(id) ?: return - MinestomPlayerTeam.delete(team) + fun publishAll(force: Boolean = false) { + onlinePlayers().forEach { player -> publish(player, force) } } - private fun updateNameTag(player: Player, displayName: Component, features: FeaturesConfig) { - if (!features.displayName) { - customNameManager.unregister(player) + private fun renderFor(player: Player, viewer: Player, team: TeamsPacket?) { + val viewerData = registry.getViewerData(player.uuid, viewer.uuid) + if (viewerData == null) { + updateTeam(player, viewer, team) return } - customNameManager.forEntity(player).setName(displayName) + updateTeam(player, viewer, createTeam(player, viewerData)) + if (prefixes.config.get().features.tablist) { + viewer.sendPacket(createListNamePacket(player, formatTablistName(player, viewerData))) + } + } + + private fun updateNameTag(player: Player, data: PrefixesPlayerData) { + val name = customNameManager.forEntity(player) + name.setName { viewer -> (registry.getViewerData(player.uuid, viewer) ?: data).displayName } + name.setHidden(!prefixes.config.get().features.displayName) } - private fun publish(player: Player, data: PrefixesPlayerData, displayName: Component, force: Boolean = false) { - val config = prefixes.config.get() - if (!config.features.tablist || !config.sync.enabled || !config.sync.channels.tablist) return + private fun publish(player: Player, force: Boolean = false) { + if (!prefixes.config.get().isTablistSynced()) return val publisher = prefixes.sync?.publisher ?: return + val data = registry.getData(player.uuid) ?: return + val skin = player.skin val entry = TablistEntry( uniqueId = player.uuid, name = player.username, - displayName = PlayerDisplayFormatter.formatTablistName(data, displayName), + displayName = formatTablistName(player, data), priority = data.priority, - profileProperties = getProfileProperties(player), + profileProperties = if (skin == null) emptyList() else listOf(ProfileProperty("textures", skin.textures(), skin.signature())), latency = player.latency, gameMode = TablistGameMode.valueOf(player.gameMode.name), showHat = (player.settings.displayedSkinParts.toInt() and 0x40) != 0, listOrder = player.listOrder ) + val previous = entries.put(player.uuid, entry) - if (!force && previous == entry) return + if (force || previous != entry) { + publisher.publishTablistEntry(entry) + } + } - publisher.publishTablistEntry(entry) + private fun createTeam(player: Player, data: PrefixesPlayerData): TeamsPacket? { + val features = prefixes.config.get().features + return when { + features.tablist -> MinestomPlayerTeam.createPacket(player.username, data.priority, data.prefix, data.suffix, data.color, features.displayName) + features.displayName -> MinestomPlayerTeam.createPacket(player.username, priority = 0, hideNameTag = true) + else -> null + } } - private fun getProfileProperties(player: Player): List { - val skin = player.skin ?: return emptyList() - return listOf(ProfileProperty("textures", skin.textures(), skin.signature())) + private fun updateTeam(player: Player, viewer: Player, team: TeamsPacket?) { + val key = ViewerKey(player.uuid, viewer.uuid) + removeTeam(key) + if (team == null) return + + teams[key] = team + viewer.sendPacket(team) + } + + private fun removeTeam(key: ViewerKey) { + val team = teams.remove(key) ?: return + val viewer = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(key.viewer) ?: return + viewer.sendPacket(MinestomPlayerTeam.removePacket(team)) } - private fun displayName(data: PrefixesPlayerData, player: Player, features: FeaturesConfig): Component { - return PlayerDisplayFormatter.formatDisplayName(data, player.username, features.displayName) + private fun formatTablistName(player: Player, data: PrefixesPlayerData): Component = + PlayerDisplayFormatter.formatTablistName(data, player.username, prefixes.config.get().features.displayName) + + private fun createListNamePacket(player: Player, name: Component): PlayerInfoUpdatePacket { + val entry = PlayerInfoUpdatePacket.Entry( + player.uuid, + player.username, + emptyList(), + true, + player.latency, + player.gameMode, + name, + null, + player.listOrder, + true + ) + return PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, entry) } + private fun onlinePlayers(): Collection = MinecraftServer.getConnectionManager().onlinePlayers } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt index ae8d420..493741b 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt @@ -6,40 +6,58 @@ import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor import net.minestom.server.MinecraftServer import net.minestom.server.color.TeamColor +import net.minestom.server.network.packet.server.play.TeamsPacket +import net.minestom.server.network.packet.server.play.TeamsPacket.CollisionRule import net.minestom.server.network.packet.server.play.TeamsPacket.NameTagVisibility -import net.minestom.server.scoreboard.Team object MinestomPlayerTeam { - fun create( - name: String, - priority: Int, - prefix: Component = Component.empty(), - suffix: Component = Component.empty(), - color: TextColor = NamedTextColor.WHITE, - hideNameTag: Boolean = false - ): Team { + fun register(name: String, priority: Int) { val manager = MinecraftServer.getTeamManager() - val teamName = "${PriorityFormatter.format(priority)}_$name" + val teamName = createName(name, priority) manager.deleteTeam(teamName) val team = manager .createBuilder(teamName) - .prefix(prefix) - .suffix(suffix) - .teamColor(toTeamColor(color)) - .nameTagVisibility(if (hideNameTag) NameTagVisibility.NEVER else NameTagVisibility.ALWAYS) + .prefix(Component.empty()) + .suffix(Component.empty()) + .teamColor(toTeamColor(NamedTextColor.WHITE)) + .nameTagVisibility(NameTagVisibility.ALWAYS) .build() team.addMember(name) - return team } - fun delete(team: Team) { - MinecraftServer.getTeamManager().deleteTeam(team) + fun unregister(name: String, priority: Int) { + MinecraftServer.getTeamManager().deleteTeam(createName(name, priority)) } + fun createPacket( + name: String, + priority: Int, + prefix: Component = Component.empty(), + suffix: Component = Component.empty(), + color: TextColor = NamedTextColor.WHITE, + hideNameTag: Boolean + ): TeamsPacket { + val settings = TeamsPacket.Settings( + Component.empty(), + prefix, + suffix, + if (hideNameTag) NameTagVisibility.NEVER else NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + toTeamColor(color), + 0 + ) + + return TeamsPacket(createName(name, priority), TeamsPacket.CreateTeamAction(settings, listOf(name))) + } + + fun removePacket(packet: TeamsPacket): TeamsPacket = TeamsPacket(packet.teamName(), TeamsPacket.RemoveTeamAction()) + + private fun createName(name: String, priority: Int): String = "${PriorityFormatter.format(priority)}_$name" + private fun toTeamColor(color: TextColor): TeamColor { return TeamColor.valueOf(NamedTextColor.nearestTo(color).toString().uppercase()) } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt index 301953c..72b7851 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt @@ -1,6 +1,5 @@ package app.simplecloud.prefixes.minestom.display -import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty import app.simplecloud.prefixes.shared.sync.tablist.SourcedTablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import net.minestom.server.MinecraftServer @@ -8,7 +7,7 @@ import net.minestom.server.entity.GameMode import net.minestom.server.entity.Player import net.minestom.server.network.packet.server.play.PlayerInfoRemovePacket import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket -import net.minestom.server.scoreboard.Team +import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket.Action import net.minestom.server.utils.PacketSendingUtils import java.util.EnumSet import java.util.UUID @@ -17,28 +16,29 @@ import java.util.concurrent.ConcurrentHashMap class MinestomTablist { private val entries = ConcurrentHashMap() - private val teams = ConcurrentHashMap() fun update(publisherId: String, entry: TablistEntry) { if (isOnline(entry.uniqueId)) return val previous = entries.put(entry.uniqueId, SourcedTablistEntry(publisherId, entry))?.entry - val actions = getActions(entry, previous) - - if (actions.isNotEmpty()) { - PacketSendingUtils.broadcastPlayPacket(createInfoPacket(listOf(entry), actions)) + if (previous != entry) { + PacketSendingUtils.broadcastPlayPacket(createInfoPacket(listOf(entry))) } if (previous == null || previous.name != entry.name || previous.priority != entry.priority) { - updateTeam(entry) + if (previous != null) MinestomPlayerTeam.unregister(previous.name, previous.priority) + MinestomPlayerTeam.register(entry.name, entry.priority) } } fun remove(id: UUID) { - removeEntry(null, id) + val sourced = entries.remove(id) ?: return + hide(sourced.entry) } fun remove(publisherId: String, id: UUID) { - removeEntry(publisherId, id) + val sourced = entries[id] ?: return + if (sourced.publisherId != publisherId) return + if (entries.remove(id, sourced)) hide(sourced.entry) } fun clear() { @@ -46,45 +46,33 @@ class MinestomTablist { } fun sync(player: Player) { - val visibleEntries = entries.values - .map(SourcedTablistEntry::entry) - .filter { !isOnline(it.uniqueId) } - - visibleEntries.chunked(PlayerInfoUpdatePacket.MAX_ENTRIES).forEach { batch -> - player.sendPacket(createInfoPacket(batch, getFullUpdateActions())) - } - } - - private fun removeEntry(publisherId: String?, id: UUID) { - val sourcedEntry = entries[id] ?: return - if (publisherId != null && sourcedEntry.publisherId != publisherId) return - if (!entries.remove(id, sourcedEntry)) return - - removeTeam(id) - if (isOnline(id)) return - - PacketSendingUtils.broadcastPlayPacket(PlayerInfoRemovePacket(id)) - } - - private fun updateTeam(entry: TablistEntry) { - removeTeam(entry.uniqueId) - teams[entry.uniqueId] = MinestomPlayerTeam.create(entry.name, entry.priority) + val visible = entries.values.map(SourcedTablistEntry::entry).filter { entry -> !isOnline(entry.uniqueId) } + visible.chunked(PlayerInfoUpdatePacket.MAX_ENTRIES).forEach { batch -> player.sendPacket(createInfoPacket(batch)) } } - private fun removeTeam(id: UUID) { - val team = teams.remove(id) ?: return - MinestomPlayerTeam.delete(team) + private fun hide(entry: TablistEntry) { + MinestomPlayerTeam.unregister(entry.name, entry.priority) + if (!isOnline(entry.uniqueId)) { + PacketSendingUtils.broadcastPlayPacket(PlayerInfoRemovePacket(entry.uniqueId)) + } } - private fun createInfoPacket( - tablistEntries: Collection, - actions: EnumSet - ): PlayerInfoUpdatePacket { + private fun getUpdateActions(): EnumSet = EnumSet.of( + Action.ADD_PLAYER, + Action.UPDATE_LISTED, + Action.UPDATE_GAME_MODE, + Action.UPDATE_LATENCY, + Action.UPDATE_DISPLAY_NAME, + Action.UPDATE_LIST_ORDER, + Action.UPDATE_HAT + ) + + private fun createInfoPacket(tablistEntries: Collection): PlayerInfoUpdatePacket { val packetEntries = tablistEntries.map { entry -> PlayerInfoUpdatePacket.Entry( entry.uniqueId, entry.name, - entry.profileProperties.map(::createProperty), + entry.profileProperties.map { property -> PlayerInfoUpdatePacket.Property(property.name, property.value, property.signature) }, true, entry.latency, GameMode.valueOf(entry.gameMode.name), @@ -95,56 +83,8 @@ class MinestomTablist { ) } - return PlayerInfoUpdatePacket(actions, packetEntries) + return PlayerInfoUpdatePacket(getUpdateActions(), packetEntries) } - private fun getActions( - entry: TablistEntry, - previous: TablistEntry? - ): EnumSet { - if (previous == null || - previous.name != entry.name || - previous.profileProperties != entry.profileProperties - ) { - return getFullUpdateActions() - } - - val actions = EnumSet.noneOf(PlayerInfoUpdatePacket.Action::class.java) - if (previous.gameMode != entry.gameMode) { - actions.add(PlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE) - } - if (previous.latency != entry.latency) { - actions.add(PlayerInfoUpdatePacket.Action.UPDATE_LATENCY) - } - if (previous.displayName != entry.displayName) { - actions.add(PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME) - } - if (previous.listOrder != entry.listOrder) { - actions.add(PlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER) - } - if (previous.showHat != entry.showHat) { - actions.add(PlayerInfoUpdatePacket.Action.UPDATE_HAT) - } - return actions - } - - private fun getFullUpdateActions(): EnumSet = - EnumSet.of( - PlayerInfoUpdatePacket.Action.ADD_PLAYER, - PlayerInfoUpdatePacket.Action.UPDATE_LISTED, - PlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, - PlayerInfoUpdatePacket.Action.UPDATE_LATENCY, - PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, - PlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER, - PlayerInfoUpdatePacket.Action.UPDATE_HAT - ) - - private fun createProperty(property: ProfileProperty): PlayerInfoUpdatePacket.Property { - val signature = property.signature ?: return PlayerInfoUpdatePacket.Property(property.name, property.value) - return PlayerInfoUpdatePacket.Property(property.name, property.value, signature) - } - - private fun isOnline(id: UUID): Boolean { - return MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) != null - } + private fun isOnline(id: UUID): Boolean = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) != null } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt index 9ae78f8..57ed431 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt @@ -5,6 +5,7 @@ import app.simplecloud.prefixes.minestom.display.MinestomTablist import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.utilities.PlayerDisplayFormatter import net.kyori.adventure.text.Component +import net.minestom.server.MinecraftServer import net.minestom.server.event.Event import net.minestom.server.event.EventNode import net.minestom.server.event.player.PlayerChatEvent @@ -24,17 +25,16 @@ class PlayerListener( } private fun onSpawn(event: PlayerSpawnEvent) { - // custom-names drops the name tag when the player leaves an instance. if (!event.isFirstSpawn) { manager.refreshNameTag(event.player) return } tablist.remove(event.player.uuid) - manager.addPlayer(event.player) + manager.updatePlayer(event.player) + manager.addViewer(event.player) - val config = prefixes.config.get() - if (config.features.tablist && config.sync.enabled && config.sync.channels.tablist) { + if (prefixes.config.get().isTablistSynced()) { tablist.sync(event.player) } } @@ -48,12 +48,23 @@ class PlayerListener( if (!features.chat) return val player = event.player - val data = manager.getPlayer(player.uuid) ?: return - val displayName = PlayerDisplayFormatter.formatDisplayName(data, player.username, features.displayName) - val message = PlayerDisplayFormatter.formatChatMessage(data, player.username, Component.text(event.rawMessage), displayName) + val data = prefixes.dataRegistry.getData(player.uuid) ?: return + val rawMessage = Component.text(event.rawMessage) + val message = PlayerDisplayFormatter.formatChatMessage(data, player.username, rawMessage, features.displayName) + val viewerMessages = event.recipients.mapNotNull { viewer -> + val viewerData = prefixes.dataRegistry.getViewerData(player.uuid, viewer.uuid) ?: return@mapNotNull null + viewer to PlayerDisplayFormatter.formatChatMessage(viewerData, player.username, rawMessage, features.displayName) + } + + event.recipients.removeAll(viewerMessages.map { (viewer, _) -> viewer }.toSet()) event.formattedMessage = message - prefixes.sync?.publisher?.publishChatMessage(message) - } + MinecraftServer.getSchedulerManager().scheduleNextTick { + if (event.isCancelled) return@scheduleNextTick + + viewerMessages.forEach { (viewer, viewerMessage) -> viewer.sendMessage(viewerMessage) } + prefixes.sync?.publisher?.publishChatMessage(message) + } + } } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt index e36245b..70498d3 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt @@ -8,23 +8,27 @@ import net.minestom.server.entity.Player import java.util.UUID import java.util.function.BiPredicate -class MinestomPermissions(private val handler: BiPredicate?) { +class MinestomPermissions( + private val groupPermission: BiPredicate?, + private val commandPermission: BiPredicate? +) { - val hasHandler: Boolean = handler != null + val hasGroupPermission: Boolean = groupPermission != null fun hasPermission(sender: CommandSender, permission: String): Boolean { if (permission.isEmpty()) return true if (sender is ConsoleSender) return true - if (sender is Player && sender.permissionLevel >= 4) return true + if (sender !is Player) return false - return handler?.test(sender, permission) ?: false + if (commandPermission != null) return commandPermission.test(sender, permission) + return sender.permissionLevel >= 4 } fun getChecker(): PermissionChecker = PermissionChecker { id, permission -> - val handler = handler ?: return@PermissionChecker false + if (groupPermission == null) return@PermissionChecker false val player = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) ?: return@PermissionChecker false - handler.test(player, permission) + groupPermission.test(player, permission) } } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt index 090cce5..25d6285 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPlatformImpl.kt @@ -1,7 +1,6 @@ package app.simplecloud.prefixes.minestom.platform import app.simplecloud.plugin.api.shared.permission.PermissionChecker -import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import net.luckperms.api.LuckPerms import net.luckperms.api.LuckPermsProvider @@ -16,8 +15,6 @@ class MinestomPlatformImpl( private val luckPerms: LuckPerms? ) : PrefixesPlatform { - override fun getLogger(): PrefixesLogger = MinestomPrefixesLogger() - override fun getDataDirectory(): File = path.toFile() override fun getPermissionChecker(): PermissionChecker = permissionChecker @@ -27,4 +24,6 @@ class MinestomPlatformImpl( override fun getPlayerName(id: UUID): String { return MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id)?.username ?: id.toString() } + + override fun isOnline(id: UUID): Boolean = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) != null } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt index 44484ba..6ae671c 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt @@ -16,8 +16,7 @@ class MinestomPrefixesListener( override fun onReload() { onAllPlayersUpdate() - val config = prefixes.config.get() - if (!config.features.tablist || !config.sync.enabled || !config.sync.channels.tablist) { + if (!prefixes.config.get().isTablistSynced()) { tablist.clear() return } @@ -25,11 +24,23 @@ class MinestomPrefixesListener( prefixes.sync?.publisher?.publishTablistRequest() } + override fun onShutdown() { + manager.clear() + tablist.clear() + } + override fun onPlayerUpdate(id: UUID) { val player = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) ?: return manager.updatePlayer(player) } + override fun onPlayerDataChange(id: UUID) { + MinecraftServer.getSchedulerManager().scheduleNextTick { + val player = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) ?: return@scheduleNextTick + manager.render(player) + } + } + override fun onAllPlayersUpdate() { MinecraftServer.getConnectionManager().onlinePlayers.forEach { player -> manager.updatePlayer(player) diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesLogger.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesLogger.kt deleted file mode 100644 index 44d804c..0000000 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesLogger.kt +++ /dev/null @@ -1,22 +0,0 @@ -package app.simplecloud.prefixes.minestom.platform - -import app.simplecloud.prefixes.shared.platform.PrefixesLogger -import org.slf4j.LoggerFactory - -class MinestomPrefixesLogger : PrefixesLogger { - - private val logger = LoggerFactory.getLogger("simplecloud-prefixes") - - override fun info(msg: String) { - logger.info(msg) - } - - override fun warn(msg: String) { - logger.warn(msg) - } - - override fun error(msg: String, cause: Throwable) { - logger.error(msg, cause) - } - -} \ No newline at end of file diff --git a/prefixes-paper/build.gradle.kts b/prefixes-paper/build.gradle.kts index 6699789..cb42ec5 100644 --- a/prefixes-paper/build.gradle.kts +++ b/prefixes-paper/build.gradle.kts @@ -43,7 +43,7 @@ modrinth { versionType.set("release") uploadFile.set(tasks.shadowJar) gameVersions.addAll( - "26.2" + "26.3" ) loaders.addAll("paper", "purpur") changelog.set("https://docs.simplecloud.app/changelog") diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt index 59cb50f..cb1931b 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt @@ -11,7 +11,6 @@ import app.simplecloud.prefixes.paper.platform.PaperPrefixesListener import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.command.PrefixesCommand import app.simplecloud.prefixes.shared.platform.PrefixesPlatform -import app.simplecloud.prefixes.shared.utilities.Constants import org.bukkit.Bukkit import org.bukkit.plugin.ServicePriority import org.bukkit.plugin.java.JavaPlugin @@ -33,7 +32,6 @@ class PrefixesPaper : JavaPlugin() { prefixes.startup() - // Register Prefixes API as a Bukkit Service. Bukkit.getServicesManager().register(PrefixesApi::class.java, prefixes.api, this, ServicePriority.Normal) val manager = PaperDisplayManager(this, prefixes, customNameManager) @@ -41,55 +39,30 @@ class PrefixesPaper : JavaPlugin() { Bukkit.getPluginManager().registerEvents(PlayerListener(prefixes, manager, tablist), this) registerSync(prefixes, manager, tablist) - registerLuckPermsListener(prefixes, platform, manager) + registerLuckPermsListener(platform, manager) - // Refresh players on reload and on updates requested from the api. - prefixes.addListener(PaperPrefixesListener(prefixes, manager, tablist)) + prefixes.addListener(PaperPrefixesListener(this, prefixes, manager, tablist)) registerCommands(prefixes) + prefixes.api.refreshAll() } override fun onDisable() { prefixes?.shutdown() } - // Apply chat and tablist updates from the configured sync sources. private fun registerSync(prefixes: Prefixes, manager: PaperDisplayManager, tablist: PaperTablist) { val sync = prefixes.sync ?: return - val config = prefixes.config.get() - if (config.features.chat && config.sync.channels.chat) { - sync.subscriber.subscribeChatMessage { message -> Bukkit.getServer().sendMessage(message) } - } - - if (!config.features.tablist || !config.sync.channels.tablist) return - - sync.subscriber.subscribeTablist( - onUpdate = tablist::update, - onRemove = tablist::remove, - onRequest = { manager.sync(force = true) } - ) + sync.subscriber.subscribeChatMessage { message -> Bukkit.getServer().sendMessage(message) } + sync.subscriber.subscribeTablist(tablist::update, tablist::remove) { Bukkit.getScheduler().runTask(this, Runnable { manager.publishAll(true) }) } sync.publisher.publishTablistRequest() - Bukkit.getScheduler().runTaskTimer( - this, - Runnable { manager.sync() }, - 600L, - 600L - ) + Bukkit.getScheduler().runTaskTimer(this, Runnable { manager.publishAll() }, 600L, 600L) } - private fun registerLuckPermsListener(prefixes: Prefixes, platform: PrefixesPlatform, manager: PaperDisplayManager) { - val source = prefixes.config.get().general.source - platform.getLogger().info("Using Source Type: $source") - if (!source.equals(Constants.LUCKPERMS_SOURCE, ignoreCase = true)) return - - val luckPerms = platform.getLuckPerms() - if (luckPerms == null) { - platform.getLogger().warn("Source Type is set to ${Constants.LUCKPERMS_SOURCE}, but LuckPerms was not found on the server!") - return - } - + private fun registerLuckPermsListener(platform: PrefixesPlatform, manager: PaperDisplayManager) { + val luckPerms = platform.getLuckPerms() ?: return LuckPermsListener(this, luckPerms, manager).register() } diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt index 216ca33..f015925 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt @@ -2,13 +2,19 @@ package app.simplecloud.prefixes.paper.display import app.simplecloud.prefixes.api.group.PrefixesPlayerData import app.simplecloud.prefixes.shared.Prefixes -import app.simplecloud.prefixes.shared.config.FeaturesConfig +import app.simplecloud.prefixes.shared.data.ViewerKey import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistGameMode +import app.simplecloud.prefixes.shared.PrefixesConstants import app.simplecloud.prefixes.shared.utilities.PlayerDisplayFormatter import com.destroystokyo.paper.ClientOption +import io.papermc.paper.adventure.PaperAdventure +import kotlinx.coroutines.future.await +import kotlinx.coroutines.launch import net.kyori.adventure.text.Component +import net.minecraft.network.protocol.Packet +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket import org.bukkit.Bukkit import org.bukkit.craftbukkit.entity.CraftPlayer @@ -16,6 +22,7 @@ import org.bukkit.entity.Entity import org.bukkit.entity.Player import org.bukkit.plugin.Plugin import space.chunks.customname.api.CustomNameManager +import java.util.EnumSet import java.util.UUID import java.util.concurrent.ConcurrentHashMap @@ -24,153 +31,156 @@ class PaperDisplayManager( private val prefixes: Prefixes, private val customNameManager: CustomNameManager ) { - private val logger = prefixes.getPlatform().getLogger() - private val cache = ConcurrentHashMap() - private val teams = ConcurrentHashMap() - private val entries = ConcurrentHashMap() + private val registry = prefixes.dataRegistry - fun getPlayer(id: UUID): PrefixesPlayerData? { - return cache[id] - } + private val teams = ConcurrentHashMap() + private val entries = ConcurrentHashMap() fun addPlayer(player: Player) { updatePlayer(player) Bukkit.getScheduler().runTask(plugin, Runnable { - if (player.isOnline && cache[player.uniqueId] == null) { - publish(player, Component.text(player.name), 0) - } + if (player.isOnline && !registry.isLoaded(player.uniqueId)) publish(player) }) } fun updatePlayer(player: Player) { - prefixes.api.getPrefixData(player.uniqueId).thenAccept { data -> - Bukkit.getScheduler().runTask(plugin, Runnable { applyPrefixData(player, data) }) - }.exceptionally { throwable -> - logger.error("Failed to update prefix data of ${player.name}", throwable) - null + PrefixesConstants.SCOPE.launch { + val group = prefixes.api.getPrimaryGroup(player.uniqueId).await() + Bukkit.getScheduler().runTask(plugin, Runnable { + if (!player.isOnline) return@Runnable + registry.loadGroup(player.uniqueId, group) + render(player) + }) } } fun removePlayer(player: Player) { - cache.remove(player.uniqueId) - removeTeam(player.uniqueId) + val id = player.uniqueId + registry.remove(id) + teams.keys.filter { key -> key.target == id || key.viewer == id }.forEach { key -> + if (key.viewer == id) teams.remove(key) else removeTeam(key) + } - entries.remove(player.uniqueId) - prefixes.sync?.publisher?.publishTablistRemove(player.uniqueId) + entries.remove(id) + prefixes.sync?.publisher?.publishTablistRemove(id) } - private fun applyPrefixData(player: Player, data: PrefixesPlayerData) { - if (!player.isOnline) return - cache[player.uniqueId] = data + fun clear() { + entries.clear() + teams.keys.toList().forEach(::removeTeam) + Bukkit.getOnlinePlayers().forEach { player -> player.playerListName(null) } + } + fun render(player: Player) { + if (!player.isOnline) return + val data = registry.getData(player.uniqueId) ?: return val features = prefixes.config.get().features - val displayName = PlayerDisplayFormatter.formatDisplayName( - data, - player.name, - features.displayName - ) - - player.playerListName( - when { - features.tablist -> PlayerDisplayFormatter.formatTablistName(data, displayName) - else -> null - } - ) - - updateTeam(player, data, features) - updateCustomName(player, displayName, features) - publish(player, data, displayName) - } + player.playerListName(if (features.tablist) formatTablistName(player, data) else null) + val team = createTeam(player, data) + Bukkit.getOnlinePlayers().forEach { viewer -> renderFor(player, viewer, team) } - private fun updateTeam(player: Player, data: PrefixesPlayerData, features: FeaturesConfig) { - removeTeam(player.uniqueId) + val name = customNameManager.forEntity(player) + name.setName { viewer -> (registry.getViewerData(player.uniqueId, viewer) ?: data).displayName } + name.setHidden(!features.displayName) - val team = createTeam(player, data, features) ?: return - teams[player.uniqueId] = team - broadcast(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)) + publish(player) } - private fun createTeam(player: Player, data: PrefixesPlayerData, features: FeaturesConfig): PaperPlayerTeam? { - return when { - features.tablist -> PaperPlayerTeam(player.name, data.priority, data.prefix, data.suffix, data.color, features.displayName) - features.displayName -> PaperPlayerTeam(player.name, priority = 0) - else -> null + fun addViewer(viewer: Player) { + Bukkit.getOnlinePlayers().forEach { player -> + val data = registry.getData(player.uniqueId) ?: return@forEach + renderFor(player, viewer, createTeam(player, data)) } } - private fun removeTeam(id: UUID) { - val team = teams.remove(id) ?: return - broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(team)) + fun publishAll(force: Boolean = false) { + Bukkit.getOnlinePlayers() + .filter { player -> registry.isLoaded(player.uniqueId) } + .forEach { player -> publish(player, force) } } - private fun updateCustomName(player: Player, displayName: Component, features: FeaturesConfig) { - val name = customNameManager.forEntity(player) - when { - features.displayName -> { - name.setName(displayName) - name.setHidden(false) - } - - else -> name.setHidden(true) + private fun renderFor(player: Player, viewer: Player, team: PaperPlayerTeam?) { + val viewerData = registry.getViewerData(player.uniqueId, viewer.uniqueId) + if (viewerData == null) { + updateTeam(player, viewer, team) + return } - } - fun syncPlayers(player: Player) { - val connection = (player as CraftPlayer).handle.connection - - teams.values.forEach { team -> - connection.send(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)) + updateTeam(player, viewer, createTeam(player, viewerData)) + if (prefixes.config.get().features.tablist) { + send(viewer, createListNamePacket(player, formatTablistName(player, viewerData))) } } - fun sync(force: Boolean = false) { - Bukkit.getOnlinePlayers().forEach { player -> - val data = cache[player.uniqueId] ?: return@forEach - val features = prefixes.config.get().features - val displayName = PlayerDisplayFormatter.formatDisplayName( - data, - player.name, - features.displayName - ) - publish(player, data, displayName, force) - } - } - - private fun publish(player: Player, data: PrefixesPlayerData, displayName: Component, force: Boolean = false) { - publish(player, PlayerDisplayFormatter.formatTablistName(data, displayName), data.priority, force) - } - - private fun publish(player: Player, displayName: Component, priority: Int, force: Boolean = false) { - val config = prefixes.config.get() - if (!config.features.tablist || !config.sync.enabled || !config.sync.channels.tablist) return + private fun publish(player: Player, force: Boolean = false) { + if (!prefixes.config.get().isTablistSynced()) return val publisher = prefixes.sync?.publisher ?: return - val profileProperties = player.playerProfile.properties.map { property -> - ProfileProperty(property.name, property.value, property.signature) - } + val data = registry.getData(player.uniqueId) val entry = TablistEntry( uniqueId = player.uniqueId, name = player.name, - displayName = displayName, - priority = priority, - profileProperties = profileProperties, + displayName = if (data == null) Component.text(player.name) else formatTablistName(player, data), + priority = data?.priority ?: 0, + profileProperties = player.playerProfile.properties.map { property -> ProfileProperty(property.name, property.value, property.signature) }, latency = player.ping, gameMode = TablistGameMode.valueOf(player.gameMode.name), showHat = player.getClientOption(ClientOption.SKIN_PARTS).hasHatsEnabled(), listOrder = player.playerListOrder ) - val previous = entries.put(player.uniqueId, entry) - if (!force && previous == entry) return - publisher.publishTablistEntry(entry) + val previous = entries.put(player.uniqueId, entry) + if (force || previous != entry) { + publisher.publishTablistEntry(entry) + } } - private fun broadcast(packet: ClientboundSetPlayerTeamPacket) { - Bukkit.getOnlinePlayers().forEach { player -> - (player as CraftPlayer).handle.connection.send(packet) + private fun createTeam(player: Player, data: PrefixesPlayerData): PaperPlayerTeam? { + val features = prefixes.config.get().features + return when { + features.tablist -> PaperPlayerTeam(player.name, data.priority, data.prefix, data.suffix, data.color, features.displayName) + features.displayName -> PaperPlayerTeam(player.name, priority = 0, hideNameTag = true) + else -> null } } + private fun updateTeam(player: Player, viewer: Player, team: PaperPlayerTeam?) { + val key = ViewerKey(player.uniqueId, viewer.uniqueId) + removeTeam(key) + if (team == null) return + + teams[key] = team + send(viewer, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)) + } + + private fun removeTeam(key: ViewerKey) { + val team = teams.remove(key) ?: return + val viewer = Bukkit.getPlayer(key.viewer) ?: return + send(viewer, ClientboundSetPlayerTeamPacket.createRemovePacket(team)) + } + + private fun formatTablistName(player: Player, data: PrefixesPlayerData): Component = + PlayerDisplayFormatter.formatTablistName(data, player.name, prefixes.config.get().features.displayName) + + private fun createListNamePacket(player: Player, name: Component): ClientboundPlayerInfoUpdatePacket { + val handle = (player as CraftPlayer).handle + val entry = ClientboundPlayerInfoUpdatePacket.Entry( + player.uniqueId, + handle.gameProfile, + true, + player.ping, + handle.gameMode.gameModeForPlayer, + PaperAdventure.asVanilla(name), + true, + player.playerListOrder, + null + ) + return ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME), listOf(entry)) + } + + private fun send(viewer: Player, packet: Packet<*>) { + (viewer as CraftPlayer).handle.connection.send(packet) + } } diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperPlayerTeam.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperPlayerTeam.kt index 6b07e0c..fb9dcc3 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperPlayerTeam.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperPlayerTeam.kt @@ -16,7 +16,7 @@ class PaperPlayerTeam( prefix: Component = Component.empty(), suffix: Component = Component.empty(), color: TextColor = NamedTextColor.WHITE, - hideNameTag: Boolean = true + hideNameTag: Boolean ) : PlayerTeam(Scoreboard(), "${PriorityFormatter.format(priority)}_$name") { init { diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt index 93694f9..f6b6ca4 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt @@ -1,9 +1,7 @@ package app.simplecloud.prefixes.paper.display -import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty import app.simplecloud.prefixes.shared.sync.tablist.SourcedTablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry -import app.simplecloud.prefixes.shared.sync.tablist.TablistGameMode import com.google.common.collect.ImmutableMultimap import com.mojang.authlib.GameProfile import com.mojang.authlib.properties.Property @@ -12,6 +10,7 @@ import io.papermc.paper.adventure.PaperAdventure import net.minecraft.network.protocol.Packet import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket +import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Action import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket import net.minecraft.world.level.GameType import org.bukkit.Bukkit @@ -24,86 +23,59 @@ import java.util.concurrent.ConcurrentHashMap class PaperTablist { private val entries = ConcurrentHashMap() - private val teams = ConcurrentHashMap() fun update(publisherId: String, entry: TablistEntry) { if (Bukkit.getPlayer(entry.uniqueId) != null) return val previous = entries.put(entry.uniqueId, SourcedTablistEntry(publisherId, entry))?.entry - val actions = getActions(entry, previous) - - if (actions.isNotEmpty()) { - broadcast(createInfoPacket(listOf(entry), actions)) + if (previous != entry) { + broadcast(createInfoPacket(listOf(entry))) } if (previous == null || previous.name != entry.name || previous.priority != entry.priority) { - updateTeam(entry) + if (previous != null) broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(createTeam(previous))) + broadcast(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(createTeam(entry), true)) } } fun remove(id: UUID) { - removeEntry(null, id) + val sourced = entries.remove(id) ?: return + hide(sourced.entry) } fun remove(publisherId: String, id: UUID) { - removeEntry(publisherId, id) + val sourced = entries[id] ?: return + if (sourced.publisherId != publisherId) return + if (entries.remove(id, sourced)) hide(sourced.entry) } fun clear() { entries.keys.toList().forEach(::remove) } - private fun removeEntry(publisherId: String?, id: UUID) { - val sourcedEntry = entries[id] ?: return - if (publisherId != null && sourcedEntry.publisherId != publisherId) return - if (!entries.remove(id, sourcedEntry)) return - - removeTeam(id) - if (Bukkit.getPlayer(id) != null) return - - broadcast(ClientboundPlayerInfoRemovePacket(listOf(id))) - } - fun sync(player: Player) { - val connection = (player as CraftPlayer).handle.connection - - val visibleEntries = entries.values - .map(SourcedTablistEntry::entry) - .filter { Bukkit.getPlayer(it.uniqueId) == null } + val visible = entries.values.map(SourcedTablistEntry::entry).filter { entry -> Bukkit.getPlayer(entry.uniqueId) == null } - visibleEntries.chunked(100).forEach { batch -> - connection.send(createInfoPacket(batch, getFullUpdateActions())) - } - visibleEntries.forEach { entry -> - connection.send(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(createTeam(entry), true)) - } + visible.chunked(100).forEach { batch -> send(player, createInfoPacket(batch)) } + visible.forEach { entry -> send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(createTeam(entry), true)) } } - private fun createTeam(entry: TablistEntry) = PaperPlayerTeam(entry.name, entry.priority) - - private fun updateTeam(entry: TablistEntry) { - removeTeam(entry.uniqueId) - - val team = createTeam(entry) - teams[entry.uniqueId] = team - broadcast(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)) + private fun hide(entry: TablistEntry) { + broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(createTeam(entry))) + if (Bukkit.getPlayer(entry.uniqueId) == null) { + broadcast(ClientboundPlayerInfoRemovePacket(listOf(entry.uniqueId))) + } } - private fun removeTeam(id: UUID) { - val team = teams.remove(id) ?: return - broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(team)) - } + private fun createTeam(entry: TablistEntry): PaperPlayerTeam = PaperPlayerTeam(entry.name, entry.priority, hideNameTag = true) - private fun createInfoPacket( - tablistEntries: Collection, - actions: EnumSet - ): ClientboundPlayerInfoUpdatePacket { - val packet = tablistEntries.map { entry -> + private fun createInfoPacket(tablistEntries: Collection): ClientboundPlayerInfoUpdatePacket { + val packetEntries = tablistEntries.map { entry -> ClientboundPlayerInfoUpdatePacket.Entry( entry.uniqueId, createGameProfile(entry), true, entry.latency, - getGameType(entry.gameMode), + GameType.valueOf(entry.gameMode.name), PaperAdventure.asVanilla(entry.displayName), entry.showHat, entry.listOrder, @@ -111,75 +83,32 @@ class PaperTablist { ) } - return ClientboundPlayerInfoUpdatePacket(actions, packet) - } - - private fun getActions( - entry: TablistEntry, - previous: TablistEntry? - ): EnumSet { - if (previous == null || - previous.name != entry.name || - previous.profileProperties != entry.profileProperties - ) { - return getFullUpdateActions() - } - - val actions = EnumSet.noneOf(ClientboundPlayerInfoUpdatePacket.Action::class.java) - if (previous.gameMode != entry.gameMode) { - actions.add(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE) - } - if (previous.latency != entry.latency) { - actions.add(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY) - } - if (previous.displayName != entry.displayName) { - actions.add(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME) - } - if (previous.listOrder != entry.listOrder) { - actions.add(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER) - } - if (previous.showHat != entry.showHat) { - actions.add(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT) - } - return actions + return ClientboundPlayerInfoUpdatePacket(getUpdateActions(), packetEntries) } - private fun getFullUpdateActions(): EnumSet = - EnumSet.of( - ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, - ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, - ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, - ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, - ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, - ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER, - ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT - ) + private fun getUpdateActions(): EnumSet = EnumSet.of( + Action.ADD_PLAYER, + Action.UPDATE_LISTED, + Action.UPDATE_GAME_MODE, + Action.UPDATE_LATENCY, + Action.UPDATE_DISPLAY_NAME, + Action.UPDATE_LIST_ORDER, + Action.UPDATE_HAT + ) private fun createGameProfile(entry: TablistEntry): GameProfile { val properties = ImmutableMultimap.builder() - entry.profileProperties.forEach { property -> - properties.put(property.name, createProperty(property)) + properties.put(property.name, Property(property.name, property.value, property.signature)) } - return GameProfile(entry.uniqueId, entry.name, PropertyMap(properties.build())) } - private fun createProperty(property: ProfileProperty): Property { - val signature = property.signature ?: return Property(property.name, property.value) - return Property(property.name, property.value, signature) - } - - private fun getGameType(gameMode: TablistGameMode): GameType = when (gameMode) { - TablistGameMode.SURVIVAL -> GameType.SURVIVAL - TablistGameMode.CREATIVE -> GameType.CREATIVE - TablistGameMode.ADVENTURE -> GameType.ADVENTURE - TablistGameMode.SPECTATOR -> GameType.SPECTATOR + private fun broadcast(packet: Packet<*>) { + Bukkit.getOnlinePlayers().forEach { player -> send(player, packet) } } - private fun broadcast(packet: Packet<*>) { - Bukkit.getOnlinePlayers().forEach { player -> - (player as CraftPlayer).handle.connection.send(packet) - } + private fun send(player: Player, packet: Packet<*>) { + (player as CraftPlayer).handle.connection.send(packet) } } diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt index f767ff0..d501fd7 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt @@ -19,13 +19,13 @@ class PlayerListener( @EventHandler fun onJoin(event: PlayerJoinEvent) { - tablist.remove(event.player.uniqueId) - manager.addPlayer(event.player) - manager.syncPlayers(event.player) + val player = event.player + tablist.remove(player.uniqueId) + manager.addPlayer(player) + manager.addViewer(player) - val config = prefixes.config.get() - if (config.features.tablist && config.sync.enabled && config.sync.channels.tablist) { - tablist.sync(event.player) + if (prefixes.config.get().isTablistSynced()) { + tablist.sync(player) } } @@ -40,17 +40,13 @@ class PlayerListener( if (!features.chat) return val player = event.player - val data = manager.getPlayer(player.uniqueId) ?: return - val displayName = PlayerDisplayFormatter.formatDisplayName(data, player.name, features.displayName) - - val message = PlayerDisplayFormatter.formatChatMessage( - data, - player.name, - event.message(), - displayName - ) + val data = prefixes.dataRegistry.getData(player.uniqueId) ?: return + val message = PlayerDisplayFormatter.formatChatMessage(data, player.name, event.message(), features.displayName) - event.renderer { _, _, _, _ -> message } + event.renderer { _, _, _, viewer -> + val viewerData = prefixes.dataRegistry.getViewerData(player.uniqueId, viewer) + if (viewerData == null) message else PlayerDisplayFormatter.formatChatMessage(viewerData, player.name, event.message(), features.displayName) + } prefixes.sync?.publisher?.publishChatMessage(message) } } diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt index 4896e2e..70eeff5 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPlatformImpl.kt @@ -1,7 +1,6 @@ package app.simplecloud.prefixes.paper.platform import app.simplecloud.plugin.api.shared.permission.PermissionChecker -import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import net.luckperms.api.LuckPerms import org.bukkit.Bukkit @@ -11,10 +10,6 @@ import java.util.UUID class PaperPlatformImpl(private val plugin: Plugin) : PrefixesPlatform { - override fun getLogger(): PrefixesLogger { - return PaperPrefixesLogger(plugin) - } - override fun getDataDirectory(): File { return plugin.dataFolder } @@ -33,4 +28,6 @@ class PaperPlatformImpl(private val plugin: Plugin) : PrefixesPlatform { override fun getPlayerName(id: UUID): String { return Bukkit.getOfflinePlayer(id).name ?: id.toString() } + + override fun isOnline(id: UUID): Boolean = Bukkit.getPlayer(id) != null } diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt index d0b3593..b86236f 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt @@ -5,9 +5,11 @@ import app.simplecloud.prefixes.paper.display.PaperTablist import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.platform.PrefixesListener import org.bukkit.Bukkit +import org.bukkit.plugin.Plugin import java.util.UUID class PaperPrefixesListener( + private val plugin: Plugin, private val prefixes: Prefixes, private val manager: PaperDisplayManager, private val tablist: PaperTablist @@ -16,8 +18,7 @@ class PaperPrefixesListener( override fun onReload() { onAllPlayersUpdate() - val config = prefixes.config.get() - if (!config.features.tablist || !config.sync.enabled || !config.sync.channels.tablist) { + if (!prefixes.config.get().isTablistSynced()) { tablist.clear() return } @@ -25,11 +26,23 @@ class PaperPrefixesListener( prefixes.sync?.publisher?.publishTablistRequest() } + override fun onShutdown() { + manager.clear() + tablist.clear() + } + override fun onPlayerUpdate(id: UUID) { val player = Bukkit.getPlayer(id) ?: return manager.updatePlayer(player) } + override fun onPlayerDataChange(id: UUID) { + Bukkit.getScheduler().runTask(plugin, Runnable { + val player = Bukkit.getPlayer(id) ?: return@Runnable + manager.render(player) + }) + } + override fun onAllPlayersUpdate() { Bukkit.getOnlinePlayers().forEach { player -> manager.updatePlayer(player) diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesLogger.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesLogger.kt deleted file mode 100644 index 195b05c..0000000 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesLogger.kt +++ /dev/null @@ -1,21 +0,0 @@ -package app.simplecloud.prefixes.paper.platform - -import app.simplecloud.prefixes.shared.platform.PrefixesLogger -import org.bukkit.plugin.Plugin -import java.util.logging.Level - -class PaperPrefixesLogger(private val plugin: Plugin) : PrefixesLogger { - - override fun info(msg: String) { - return plugin.logger.info(msg) - } - - override fun warn(msg: String) { - return plugin.logger.warning(msg) - } - - override fun error(msg: String, thrown: Throwable) { - return plugin.logger.log(Level.SEVERE, msg, thrown) - } - -} \ No newline at end of file diff --git a/prefixes-paper/src/main/resources/paper-plugin.yml b/prefixes-paper/src/main/resources/paper-plugin.yml index d4623c6..3230bed 100644 --- a/prefixes-paper/src/main/resources/paper-plugin.yml +++ b/prefixes-paper/src/main/resources/paper-plugin.yml @@ -1,6 +1,6 @@ name: simplecloud-prefixes version: ${version} -api-version: 26.2 +api-version: 26.3 main: app.simplecloud.prefixes.paper.PrefixesPaper diff --git a/prefixes-shared/build.gradle.kts b/prefixes-shared/build.gradle.kts index 782aaec..62a0bc3 100644 --- a/prefixes-shared/build.gradle.kts +++ b/prefixes-shared/build.gradle.kts @@ -6,6 +6,7 @@ dependencies { api(project(":prefixes-api")) api(libs.simplecloud.plugin.api) implementation(libs.jnats) + implementation(libs.slf4j.api) implementation(libs.protobuf.kotlin) implementation(libs.bundles.configurate) implementation(libs.bundles.cloud.command) @@ -42,6 +43,6 @@ protobuf { configurations.all { resolutionStrategy { - force("com.google.protobuf:protobuf-java:4.35.1") + force("com.google.protobuf:protobuf-java:4.36.2") } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index f152d00..b3e169c 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -6,34 +6,43 @@ import app.simplecloud.prefixes.shared.api.PrefixesApiImpl import app.simplecloud.prefixes.shared.config.DefaultConfigInstaller import app.simplecloud.prefixes.shared.config.MessageConfig import app.simplecloud.prefixes.shared.config.PrefixesConfig +import app.simplecloud.prefixes.shared.config.SyncConfig +import app.simplecloud.prefixes.shared.data.PrefixesDataRegistry import app.simplecloud.prefixes.shared.group.GroupProviderRegistry import app.simplecloud.prefixes.shared.group.config.ConfigGroupProvider import app.simplecloud.prefixes.shared.group.luckperms.LuckPermsGroupProvider import app.simplecloud.prefixes.shared.platform.PrefixesListener import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import app.simplecloud.prefixes.shared.sync.PrefixesSync +import app.simplecloud.prefixes.shared.utilities.ColorParser +import kotlinx.coroutines.cancelChildren +import org.slf4j.LoggerFactory import java.io.File import java.util.concurrent.CopyOnWriteArrayList class Prefixes(private val platform: PrefixesPlatform) { - private val logger = platform.getLogger() + private val logger = LoggerFactory.getLogger(PrefixesConstants.LOGGER_NAME) private val listeners = CopyOnWriteArrayList() val config = createConfig() val messages = createMessages() val registry = createGroupRegistry() - val api = PrefixesApiImpl(registry, listeners, platform) + val dataRegistry = PrefixesDataRegistry(platform) + val api = PrefixesApiImpl(registry, dataRegistry, listeners, platform) val sync = createSync() fun startup() { PrefixesApiProvider.register(api) + validateConfig() } fun shutdown() { + listeners.forEach(PrefixesListener::onShutdown) PrefixesApiProvider.unregister() sync?.shutdown() + PrefixesConstants.SCOPE.coroutineContext.cancelChildren() } fun addListener(listener: PrefixesListener) { @@ -43,8 +52,11 @@ class Prefixes(private val platform: PrefixesPlatform) { fun reload(): Boolean { return runCatching { logger.info("Reloading simplecloud prefixes...") + val previousSync = config.get().sync config.reload() messages.reload() + validateConfig() + warnIfRestartRequired(previousSync) listeners.forEach(PrefixesListener::onReload) }.onSuccess { logger.info("Succesfully reloaded simplecloud prefixes") @@ -57,6 +69,20 @@ class Prefixes(private val platform: PrefixesPlatform) { return platform } + private fun validateConfig() { + registry.validateSource() + config.get().groups + .filter { group -> group.color.isNotBlank() && ColorParser.parse(group.color) == null } + .forEach { group -> logger.warn("The color '${group.color}' of the group '${group.name}' is invalid, using white instead") } + } + + private fun warnIfRestartRequired(previous: SyncConfig) { + val current = config.get().sync + if ((sync == null && current.enabled) || previous.sources != current.sources) { + logger.warn("Enabling sync or changing sync.sources only takes effect after a restart") + } + } + private fun createConfig(): ConfigurationFactory { val file = File(platform.getDataDirectory(), "config.yml") DefaultConfigInstaller.install(file.toPath(), javaClass.classLoader) @@ -73,18 +99,18 @@ class Prefixes(private val platform: PrefixesPlatform) { } private fun createGroupRegistry(): GroupProviderRegistry { - val registry = GroupProviderRegistry(config, ConfigGroupProvider(logger, config, platform.getPermissionChecker()), platform) + val registry = GroupProviderRegistry(config, ConfigGroupProvider(config, platform.getPermissionChecker())) val luckPerms = platform.getLuckPerms() if (luckPerms != null) { - registry.register(LuckPermsGroupProvider(logger, luckPerms)) + registry.register(LuckPermsGroupProvider(luckPerms)) } return registry } private fun createSync(): PrefixesSync? { - if (!isSyncEnabled()) return null + if (!config.get().sync.enabled) return null if (!isSimpleCloudAvailable()) { logger.warn("Sync is enabled in the config, but SimpleCloud was not found on this server.") @@ -92,7 +118,7 @@ class Prefixes(private val platform: PrefixesPlatform) { } return runCatching { - PrefixesSync(config, logger) + PrefixesSync(config) }.onFailure { throwable -> logger.error("Failed to initialize sync", throwable) }.getOrNull() @@ -106,11 +132,4 @@ class Prefixes(private val platform: PrefixesPlatform) { false } } - - private fun isSyncEnabled(): Boolean { - val current = config.get() - if (!current.sync.enabled) return false - - return current.features.chat && current.sync.channels.chat || current.features.tablist && current.sync.channels.tablist - } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt new file mode 100644 index 0000000..44345ae --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt @@ -0,0 +1,34 @@ +package app.simplecloud.prefixes.shared + +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +object PrefixesConstants { + + const val LOGGER_NAME = "simplecloud-prefixes" + + const val CONFIG_SOURCE = "config" + + const val CURRENT_SYNC_SOURCE = "CURRENT" + const val ALL_SYNC_SOURCE = "ALL" + + const val DEFAULT_DISPLAY_NAME = "" + const val DEFAULT_CHAT_FORMAT = " <#475569>ยป <#F8FAFC>" + + val LOGGER: Logger = LoggerFactory.getLogger(LOGGER_NAME) + + val SCOPE = CoroutineScope( + CoroutineName("simplecloud-prefixes") + + SupervisorJob() + + Dispatchers.IO + + CoroutineExceptionHandler { _, exception -> + LOGGER.error("An error occurred in a coroutine", exception) + } + ) + +} \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt index adfd6c5..1fdda43 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt @@ -1,21 +1,27 @@ package app.simplecloud.prefixes.shared.api -import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.prefixes.api.PrefixesApi import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.api.group.PrefixesPlayerData +import app.simplecloud.prefixes.shared.data.PrefixesDataRegistry +import app.simplecloud.prefixes.shared.data.PrefixesOverride import app.simplecloud.prefixes.shared.group.GroupProviderRegistry import app.simplecloud.prefixes.shared.platform.PrefixesListener import app.simplecloud.prefixes.shared.platform.PrefixesPlatform +import app.simplecloud.prefixes.shared.utilities.AudienceResolver +import app.simplecloud.prefixes.shared.PrefixesConstants +import kotlinx.coroutines.future.await +import kotlinx.coroutines.future.future +import net.kyori.adventure.audience.Audience import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder +import net.kyori.adventure.text.format.TextColor import java.util.UUID import java.util.concurrent.CompletableFuture class PrefixesApiImpl( private val registry: GroupProviderRegistry, + private val dataRegistry: PrefixesDataRegistry, private val listeners: List, private val platform: PrefixesPlatform ) : PrefixesApi { @@ -24,6 +30,93 @@ class PrefixesApiImpl( override fun getPrimaryGroup(id: UUID): CompletableFuture = registry.getCurrentGroupProvider().getGroup(id) + override fun getPrefixData(id: UUID): CompletableFuture { + return PrefixesConstants.SCOPE.future { dataRegistry.getData(id, getPrimaryGroup(id).await()) } + } + + override fun getPrefixData(id: UUID, viewer: Audience): CompletableFuture { + val viewerId = AudienceResolver.resolveId(viewer) ?: return getPrefixData(id) + return PrefixesConstants.SCOPE.future { dataRegistry.getData(id, getPrimaryGroup(id).await(), viewerId) } + } + + override fun setPrefix(id: UUID, prefix: Component) = set(id, PrefixesOverride(prefix = prefix)) + + override fun setPrefix(id: UUID, prefix: Component, viewers: Audience) = set(id, PrefixesOverride(prefix = prefix), viewers) + + override fun setSuffix(id: UUID, suffix: Component) = set(id, PrefixesOverride(suffix = suffix)) + + override fun setSuffix(id: UUID, suffix: Component, viewers: Audience) = set(id, PrefixesOverride(suffix = suffix), viewers) + + override fun setColor(id: UUID, color: TextColor) = set(id, PrefixesOverride(color = color)) + + override fun setColor(id: UUID, color: TextColor, viewers: Audience) = set(id, PrefixesOverride(color = color), viewers) + + override fun setPriority(id: UUID, priority: Int) = set(id, PrefixesOverride(priority = priority)) + + override fun setPriority(id: UUID, priority: Int, viewers: Audience) = set(id, PrefixesOverride(priority = priority), viewers) + + override fun setGroup(id: UUID, group: String): CompletableFuture { + return PrefixesConstants.SCOPE.future { + val found = findGroup(group) ?: return@future false + setGroup(id, found) + true + } + } + + override fun setGroup(id: UUID, group: String, viewers: Audience): CompletableFuture { + return PrefixesConstants.SCOPE.future { + val found = findGroup(group) ?: return@future false + setGroup(id, found, viewers) + true + } + } + + override fun setGroup(targets: Audience, group: String): CompletableFuture { + return PrefixesConstants.SCOPE.future { + val found = findGroup(group) ?: return@future false + setGroup(targets, found) + true + } + } + + override fun setGroup(targets: Audience, group: String, viewers: Audience): CompletableFuture { + return PrefixesConstants.SCOPE.future { + val found = findGroup(group) ?: return@future false + setGroup(targets, found, viewers) + true + } + } + + override fun setGroup(id: UUID, group: PrefixesGroup) = set(id, PrefixesOverride.of(group)) + + override fun setGroup(id: UUID, group: PrefixesGroup, viewers: Audience) = set(id, PrefixesOverride.of(group), viewers) + + override fun setGroup(targets: Audience, group: PrefixesGroup) { + AudienceResolver.resolveIds(targets).forEach { id -> setGroup(id, group) } + } + + override fun setGroup(targets: Audience, group: PrefixesGroup, viewers: Audience) { + val viewerIds = AudienceResolver.resolveIds(viewers) + AudienceResolver.resolveIds(targets).forEach { id -> set(id, PrefixesOverride.of(group), viewerIds) } + } + + override fun reset(id: UUID) { + dataRegistry.reset(id) + notifyChange(id) + } + + override fun reset(targets: Audience) { + AudienceResolver.resolveIds(targets).forEach(::reset) + } + + override fun reset(targets: Audience, viewers: Audience) { + val viewerIds = AudienceResolver.resolveIds(viewers) + AudienceResolver.resolveIds(targets).forEach { id -> + viewerIds.forEach { viewer -> dataRegistry.reset(id, viewer) } + notifyChange(id) + } + } + override fun addGroup(group: PrefixesGroup): CompletableFuture = registry.getCurrentGroupProvider().addGroup(group) override fun registerGroupProvider(provider: GroupProvider) = registry.register(provider) @@ -34,33 +127,31 @@ class PrefixesApiImpl( override fun getGroupProviders(): Collection = registry.getAllGroupProviders() - override fun refreshPlayer(id: UUID) { - listeners.forEach { listener -> listener.onPlayerUpdate(id) } + override fun refreshPlayer(id: UUID) { listeners.forEach { it.onPlayerUpdate(id) } } + + override fun refreshAll() { listeners.forEach(PrefixesListener::onAllPlayersUpdate) } + + private fun set(id: UUID, override: PrefixesOverride) { + if (!platform.isOnline(id)) return + dataRegistry.set(id, override) + notifyChange(id) } - override fun refreshAll() { - listeners.forEach(PrefixesListener::onAllPlayersUpdate) + private fun set(id: UUID, override: PrefixesOverride, viewers: Audience) { + set(id, override, AudienceResolver.resolveIds(viewers)) } - override fun getPrefixData(id: UUID): CompletableFuture { - return getPrimaryGroup(id).thenApply { group -> createPlayerData(id, group) } - } - - private fun createPlayerData(id: UUID, group: PrefixesGroup?): PrefixesPlayerData { - val color = group?.color ?: NamedTextColor.WHITE - val displayName = miniMessage.deserialize( - group?.displayName ?: "", - Placeholder.styling("color", color), - Placeholder.unparsed("playername", platform.getPlayerName(id)) - ) - - return PrefixesPlayerData( - group?.prefix ?: Component.empty(), - group?.suffix ?: Component.empty(), - color, - displayName, - group?.chatFormat ?: " ", - group?.priority ?: 0 - ) + private fun set(id: UUID, override: PrefixesOverride, viewers: List) { + if (!platform.isOnline(id)) return + viewers.forEach { viewer -> dataRegistry.set(id, viewer, override) } + notifyChange(id) + } + + private suspend fun findGroup(name: String): PrefixesGroup? { + return getGroups().await().firstOrNull { group -> group.name.equals(name, ignoreCase = true) } + } + + private fun notifyChange(id: UUID) { + listeners.forEach { listener -> listener.onPlayerDataChange(id) } } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt index 87cb373..01c973a 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt @@ -45,11 +45,6 @@ class PrefixesCommand( .permission(PrefixesPermissions.RELOAD) .suspendingHandler { val sender = it.sender() - if (!sender.hasPermission(PrefixesPermissions.RELOAD)) { - sender.sendMessage(messages.msg(messages.command.permission.denied)) - return@suspendingHandler - } - val reloaded = prefixes.reload() if (reloaded) { sender.sendMessage(messages.msg(messages.command.reload.success)) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/MessageConfig.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/MessageConfig.kt index bbf1fe0..c6f3594 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/MessageConfig.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/MessageConfig.kt @@ -16,7 +16,6 @@ data class MessageConfig( @ConfigSerializable data class CommandMessages( val help: CommandHelpMessages = CommandHelpMessages(), - val usage: CommandUsageMessages = CommandUsageMessages(), val permission: CommandPermissionMessages = CommandPermissionMessages(), val reload: CommandReloadMessages = CommandReloadMessages() ) @@ -27,11 +26,6 @@ data class CommandHelpMessages( val entry: String = "<#E2E8F0>" ) -@ConfigSerializable -data class CommandUsageMessages( - val invalid: String = " <#DC2626>Use <#F8FAFC> <#DC2626>instead." -) - @ConfigSerializable data class CommandPermissionMessages( val denied: String = " <#DC2626>You do not have permission to use this command." diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt index 86da173..d98c804 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt @@ -1,9 +1,8 @@ package app.simplecloud.prefixes.shared.config import app.simplecloud.plugin.api.shared.config.VersionedConfig -import app.simplecloud.prefixes.shared.utilities.Constants +import app.simplecloud.prefixes.shared.PrefixesConstants import app.simplecloud.prefixes.shared.utilities.config.ConfigVersion -import app.simplecloud.prefixes.shared.utilities.config.DefaultConfigs import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable @@ -12,12 +11,17 @@ data class PrefixesConfig( val general: GeneralConfig = GeneralConfig(), val features: FeaturesConfig = FeaturesConfig(), val sync: SyncConfig = SyncConfig(), - val groups: List = DefaultConfigs.GROUPS -) : VersionedConfig + val groups: List = emptyList() +) : VersionedConfig { + + fun isChatSynced(): Boolean = features.chat && sync.enabled && sync.channels.chat + + fun isTablistSynced(): Boolean = features.tablist && sync.enabled && sync.channels.tablist +} @ConfigSerializable data class GeneralConfig( - val source: String = Constants.CONFIG_SOURCE, + val source: String = PrefixesConstants.CONFIG_SOURCE, val defaultGroup: String = "default" ) @@ -32,7 +36,7 @@ data class FeaturesConfig( data class SyncConfig( val enabled: Boolean = true, val channels: SyncChannels = SyncChannels(), - val sources: List = listOf(Constants.CURRENT_SYNC_SOURCE) + val sources: List = listOf(PrefixesConstants.CURRENT_SYNC_SOURCE) ) @ConfigSerializable diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt new file mode 100644 index 0000000..f545ac6 --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt @@ -0,0 +1,92 @@ +package app.simplecloud.prefixes.shared.data + +import app.simplecloud.plugin.api.shared.extension.miniMessage +import app.simplecloud.prefixes.api.group.PrefixesGroup +import app.simplecloud.prefixes.api.group.PrefixesPlayerData +import app.simplecloud.prefixes.shared.platform.PrefixesPlatform +import app.simplecloud.prefixes.shared.utilities.AudienceResolver +import app.simplecloud.prefixes.shared.PrefixesConstants +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +class PrefixesDataRegistry(private val platform: PrefixesPlatform) { + + private val groups = ConcurrentHashMap() + private val overrides = ConcurrentHashMap() + private val viewerOverrides = ConcurrentHashMap() + + fun isLoaded(id: UUID): Boolean = groups.containsKey(id) + + fun getData(id: UUID): PrefixesPlayerData? { + val group = groups[id] ?: return null + return createData(id, group.merge(overrides[id])) + } + + fun getData(id: UUID, group: PrefixesGroup?): PrefixesPlayerData { + return createData(id, PrefixesOverride.of(group).merge(overrides[id])) + } + + fun getData(id: UUID, group: PrefixesGroup?, viewer: UUID): PrefixesPlayerData { + return createData(id, PrefixesOverride.of(group).merge(overrides[id]).merge(viewerOverrides[ViewerKey(id, viewer)])) + } + + fun getViewerData(id: UUID, viewer: UUID): PrefixesPlayerData? { + val group = groups[id] ?: return null + val viewerOverride = viewerOverrides[ViewerKey(id, viewer)] ?: return null + return createData(id, group.merge(overrides[id]).merge(viewerOverride)) + } + + fun getViewerData(id: UUID, viewer: Audience): PrefixesPlayerData? { + val viewerId = AudienceResolver.resolveId(viewer) ?: return null + return getViewerData(id, viewerId) + } + + fun loadGroup(id: UUID, group: PrefixesGroup?) { + groups[id] = PrefixesOverride.of(group) + } + + fun set(id: UUID, override: PrefixesOverride) { + overrides.merge(id, override) { current, new -> current.merge(new) } + } + + fun set(id: UUID, viewer: UUID, override: PrefixesOverride) { + viewerOverrides.merge(ViewerKey(id, viewer), override) { current, new -> current.merge(new) } + } + + fun reset(id: UUID) { + overrides.remove(id) + viewerOverrides.keys.removeIf { key -> key.target == id } + } + + fun reset(id: UUID, viewer: UUID) { + viewerOverrides.remove(ViewerKey(id, viewer)) + } + + fun remove(id: UUID) { + groups.remove(id) + overrides.remove(id) + viewerOverrides.keys.removeIf { key -> key.target == id || key.viewer == id } + } + + private fun createData(id: UUID, override: PrefixesOverride): PrefixesPlayerData { + val color = override.color ?: NamedTextColor.WHITE + val displayName = miniMessage.deserialize( + override.displayName ?: PrefixesConstants.DEFAULT_DISPLAY_NAME, + Placeholder.styling("color", color), + Placeholder.unparsed("playername", platform.getPlayerName(id)) + ) + + return PrefixesPlayerData( + override.prefix ?: Component.empty(), + override.suffix ?: Component.empty(), + color, + displayName, + override.chatFormat ?: PrefixesConstants.DEFAULT_CHAT_FORMAT, + override.priority ?: 0 + ) + } +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesOverride.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesOverride.kt new file mode 100644 index 0000000..ec285b9 --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesOverride.kt @@ -0,0 +1,43 @@ +package app.simplecloud.prefixes.shared.data + +import app.simplecloud.prefixes.api.group.PrefixesGroup +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor + +data class PrefixesOverride( + val prefix: Component? = null, + val suffix: Component? = null, + val color: TextColor? = null, + val priority: Int? = null, + val displayName: String? = null, + val chatFormat: String? = null +) { + + fun merge(other: PrefixesOverride?): PrefixesOverride { + if (other == null) return this + + return PrefixesOverride( + other.prefix ?: prefix, + other.suffix ?: suffix, + other.color ?: color, + other.priority ?: priority, + other.displayName ?: displayName, + other.chatFormat ?: chatFormat + ) + } + + companion object { + fun of(group: PrefixesGroup?): PrefixesOverride { + if (group == null) return PrefixesOverride() + + return PrefixesOverride( + group.prefix, + group.suffix, + group.color, + group.priority, + group.displayName.ifBlank { null }, + group.chatFormat.ifBlank { null } + ) + } + } +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/ViewerKey.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/ViewerKey.kt new file mode 100644 index 0000000..35d5e56 --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/ViewerKey.kt @@ -0,0 +1,5 @@ +package app.simplecloud.prefixes.shared.data + +import java.util.UUID + +data class ViewerKey(val target: UUID, val viewer: UUID) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt index c9f5772..f678943 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/GroupProviderRegistry.kt @@ -3,16 +3,15 @@ package app.simplecloud.prefixes.shared.group import app.simplecloud.plugin.api.shared.config.ConfigurationFactory import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.shared.config.PrefixesConfig -import app.simplecloud.prefixes.shared.platform.PrefixesPlatform +import app.simplecloud.prefixes.shared.PrefixesConstants import java.util.concurrent.ConcurrentHashMap class GroupProviderRegistry( private val config: ConfigurationFactory, - private val provider: GroupProvider, - private val platform: PrefixesPlatform + private val provider: GroupProvider ) { - private val logger = platform.getLogger() + private val logger = PrefixesConstants.LOGGER private val providers = ConcurrentHashMap() init { @@ -30,14 +29,17 @@ class GroupProviderRegistry( fun getAllGroupProviders(): Collection = providers.values.toList() - fun getCurrentGroupProvider(): GroupProvider { - val source = config.get().general.source.trim().lowercase() - val provider = providers[source] ?: return warnMissingProvider(source) - return provider - } + fun getCurrentGroupProvider(): GroupProvider = providers[getSource()] ?: provider + + fun validateSource() { + val source = getSource() + if (providers.containsKey(source)) { + logger.info("Using the group source '$source'") + return + } - private fun warnMissingProvider(source: String): GroupProvider { - logger.warn("No group provider named '$source' is registered, using '${provider.getName()}' instead") - return provider + logger.warn("No group provider named '$source' is registered yet, using '${provider.getName()}' until it is registered") } + + private fun getSource(): String = config.get().general.source.trim().lowercase() } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt index b390489..4b9f553 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt @@ -7,18 +7,19 @@ import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.shared.config.ConfigGroup import app.simplecloud.prefixes.shared.config.PrefixesConfig -import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.utilities.ColorParser -import net.kyori.adventure.text.Component +import app.simplecloud.prefixes.shared.PrefixesConstants +import kotlinx.coroutines.future.future import java.util.UUID import java.util.concurrent.CompletableFuture class ConfigGroupProvider( - private val logger: PrefixesLogger, private val configFactory: ConfigurationFactory, private val permissionChecker: PermissionChecker ) : GroupProvider { + private val logger = PrefixesConstants.LOGGER + override fun getName(): String = "config" override fun getGroups(): CompletableFuture> { @@ -26,23 +27,21 @@ class ConfigGroupProvider( } override fun getGroup(id: UUID): CompletableFuture { - return CompletableFuture.supplyAsync { + return PrefixesConstants.SCOPE.future { val groups = loadGroups() val config = configFactory.get() - // Check if player has permission for any group, highest priority first. val group = groups.firstOrNull { - it.name != config.general.defaultGroup && it.hasPermission(id) + !it.name.equals(config.general.defaultGroup, ignoreCase = true) && it.hasPermission(id) } - // Fallback to the default group if no group matched. - group ?: groups.firstOrNull { it.name == config.general.defaultGroup } + group ?: groups.firstOrNull { it.name.equals(config.general.defaultGroup, ignoreCase = true) } } } override fun addGroup(group: PrefixesGroup): CompletableFuture { - return CompletableFuture.supplyAsync { - synchronized(this) { + return PrefixesConstants.SCOPE.future { + synchronized(this@ConfigGroupProvider) { val config = configFactory.get() if (config.groups.any { it.name.equals(group.name, ignoreCase = true) }) { return@synchronized false @@ -69,8 +68,8 @@ class ConfigGroupProvider( name = group.name, priority = group.priority, permission = group.permission, - prefix = miniMessage.serialize(group.prefix ?: Component.empty()), - suffix = miniMessage.serialize(group.suffix ?: Component.empty()), + prefix = miniMessage.serialize(group.prefix), + suffix = miniMessage.serialize(group.suffix), color = ColorParser.serialize(group.color), displayName = group.displayName, chatFormat = group.chatFormat diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt index 1479364..5f99ebd 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigPrefixesGroup.kt @@ -6,6 +6,7 @@ import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.shared.config.ConfigGroup import app.simplecloud.prefixes.shared.utilities.ColorParser import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor import java.util.UUID import java.util.concurrent.CompletableFuture @@ -18,18 +19,18 @@ class ConfigPrefixesGroup( override val name: String = group.name override val priority: Int = group.priority override val permission: String = group.permission - override val prefix: Component? = miniMessage.deserialize(group.prefix) - override val suffix: Component? = miniMessage.deserialize(group.suffix) - override val color: TextColor? = ColorParser.parse(group.color) + override val prefix: Component = miniMessage.deserialize(group.prefix) + override val suffix: Component = miniMessage.deserialize(group.suffix) + override val color: TextColor = ColorParser.parse(group.color) ?: NamedTextColor.WHITE override val displayName: String = group.displayName override val chatFormat: String = group.chatFormat override fun containsPlayer(id: UUID): CompletableFuture { - return CompletableFuture.supplyAsync { hasPermission(id) } + return CompletableFuture.completedFuture(hasPermission(id)) } fun hasPermission(id: UUID): Boolean { - if (permission.isEmpty()) return true // Default group matches everyone + if (permission.isEmpty()) return true return permissionChecker.checkPermission(id, permission) } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt index 64d6943..ab09df0 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt @@ -3,7 +3,11 @@ package app.simplecloud.prefixes.shared.group.luckperms import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.prefixes.api.group.PrefixesGroup import app.simplecloud.prefixes.shared.utilities.ColorParser +import app.simplecloud.prefixes.shared.PrefixesConstants +import kotlinx.coroutines.future.await +import kotlinx.coroutines.future.future import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor import net.luckperms.api.LuckPerms import net.luckperms.api.model.group.Group @@ -12,27 +16,28 @@ import java.util.concurrent.CompletableFuture class LuckPermsGroup( private val group: Group, - private val luckPerms: LuckPerms, + private val luckPerms: LuckPerms ) : PrefixesGroup { override val name: String = group.name override val priority: Int = group.weight.orElse(0) override val permission: String = "group.${group.name}" - override val prefix: Component? = miniMessage.deserialize(group.cachedData.metaData.prefix ?: "") - override val suffix: Component? = miniMessage.deserialize(group.cachedData.metaData.suffix ?: "") - override val color: TextColor? = parseColor() - override val displayName: String = getMetaValue("display-name") ?: "" - override val chatFormat: String = getMetaValue("chat-format") ?: " <#475569>ยป <#F8FAFC>" + override val prefix: Component = miniMessage.deserialize(group.cachedData.metaData.prefix ?: "") + override val suffix: Component = miniMessage.deserialize(group.cachedData.metaData.suffix ?: "") + override val color: TextColor = parseColor() + override val displayName: String = getMetaValue("display-name") ?: PrefixesConstants.DEFAULT_DISPLAY_NAME + override val chatFormat: String = getMetaValue("chat-format") ?: PrefixesConstants.DEFAULT_CHAT_FORMAT override fun containsPlayer(id: UUID): CompletableFuture { - return luckPerms.userManager.loadUser(id).thenApply { user -> + return PrefixesConstants.SCOPE.future { + val user = luckPerms.userManager.loadUser(id).await() user.primaryGroup == name || user.getInheritedGroups(user.queryOptions).any { it.name == name } } } - private fun parseColor(): TextColor? { - val value = getMetaValue("color") ?: return TextColor.fromHexString("#FFFFFF") - return ColorParser.parse(value) ?: TextColor.fromHexString("#FFFFFF") + private fun parseColor(): TextColor { + val value = getMetaValue("color") ?: return NamedTextColor.WHITE + return ColorParser.parse(value) ?: NamedTextColor.WHITE } private fun getMetaValue(key: String): String? { diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt index df418c9..c0addf6 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt @@ -3,9 +3,10 @@ package app.simplecloud.prefixes.shared.group.luckperms import app.simplecloud.plugin.api.shared.extension.miniMessage import app.simplecloud.prefixes.api.group.GroupProvider import app.simplecloud.prefixes.api.group.PrefixesGroup -import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.utilities.ColorParser -import net.kyori.adventure.text.Component +import app.simplecloud.prefixes.shared.PrefixesConstants +import kotlinx.coroutines.future.await +import kotlinx.coroutines.future.future import net.luckperms.api.LuckPerms import net.luckperms.api.model.group.Group import net.luckperms.api.model.user.User @@ -17,10 +18,11 @@ import java.util.UUID import java.util.concurrent.CompletableFuture class LuckPermsGroupProvider( - private val logger: PrefixesLogger, private val luckPerms: LuckPerms ) : GroupProvider { + private val logger = PrefixesConstants.LOGGER + override fun getName(): String = "luckperms" override fun getGroups(): CompletableFuture> { @@ -37,24 +39,27 @@ class LuckPermsGroupProvider( return CompletableFuture.completedFuture(resolveGroup(user)) } - return luckPerms.userManager.loadUser(id) - .thenApply(::resolveGroup) - .exceptionally { throwable -> - logger.error("Failed to load LuckPerms user $id", throwable) - null - } + return PrefixesConstants.SCOPE.future { loadGroup(id) } } override fun addGroup(group: PrefixesGroup): CompletableFuture { - return luckPerms.groupManager.loadGroup(group.name).thenCompose { existing -> - if (existing.isPresent) { - return@thenCompose CompletableFuture.completedFuture(false) - } - - luckPerms.groupManager.createAndLoadGroup(group.name).thenCompose { created -> - applyPrefixes(created, group) - luckPerms.groupManager.saveGroup(created).thenApply { true } - } + return PrefixesConstants.SCOPE.future { + val existing = luckPerms.groupManager.loadGroup(group.name).await() + if (existing.isPresent) return@future false + + val created = luckPerms.groupManager.createAndLoadGroup(group.name).await() + applyPrefixes(created, group) + luckPerms.groupManager.saveGroup(created).await() + true + } + } + + private suspend fun loadGroup(id: UUID): PrefixesGroup? { + return try { + resolveGroup(luckPerms.userManager.loadUser(id).await()) + } catch (exception: Exception) { + logger.error("Failed to load LuckPerms user $id", exception) + null } } @@ -62,20 +67,17 @@ class LuckPermsGroupProvider( val data = target.data() data.add(WeightNode.builder(source.priority).build()) - val prefix = miniMessage.serialize(source.prefix ?: Component.empty()) + val prefix = miniMessage.serialize(source.prefix) if (prefix.isNotEmpty()) { data.add(PrefixNode.builder(prefix, source.priority).build()) } - val suffix = miniMessage.serialize(source.suffix ?: Component.empty()) + val suffix = miniMessage.serialize(source.suffix) if (suffix.isNotEmpty()) { data.add(SuffixNode.builder(suffix, source.priority).build()) } - val color = ColorParser.serialize(source.color) - if (color.isNotEmpty()) { - data.add(MetaNode.builder("color", color).build()) - } + data.add(MetaNode.builder("color", ColorParser.serialize(source.color)).build()) data.add(MetaNode.builder("display-name", source.displayName).build()) data.add(MetaNode.builder("chat-format", source.chatFormat).build()) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt index 58272cd..d03a596 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt @@ -2,23 +2,15 @@ package app.simplecloud.prefixes.shared.platform import java.util.UUID -/** - * The listener that listen to update events. - */ interface PrefixesListener { - /** - * Called when the plugin reloads. - */ fun onReload() - /** - * Called when a player was update or requested by the api. - */ + fun onShutdown() + fun onPlayerUpdate(id: UUID) - /** - * Called when all players on this server updated or by the api. - */ + fun onPlayerDataChange(id: UUID) + fun onAllPlayersUpdate() } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesLogger.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesLogger.kt deleted file mode 100644 index f56b69d..0000000 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesLogger.kt +++ /dev/null @@ -1,30 +0,0 @@ -package app.simplecloud.prefixes.shared.platform - -/** - * A basic logger interface to allow for platform logger implementations. - */ -interface PrefixesLogger { - - /** - * Logs an info message. - * - * @param msg the message to log - */ - fun info(msg: String) - - /** - * Logs a warning message. - * - * @param msg the message to log - */ - fun warn(msg: String) - - /** - * Logs an error message. - * - * @param msg the message to log - * @param cause the throwable that caused the error - */ - fun error(msg: String, cause: Throwable) - -} \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt index 1e4597a..7a4168b 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesPlatform.kt @@ -10,11 +10,6 @@ import java.util.UUID */ interface PrefixesPlatform { - /** - * Returns this platform's logger. - */ - fun getLogger(): PrefixesLogger - /** * Returns this platform's data directory. */ @@ -34,4 +29,9 @@ interface PrefixesPlatform { * Returns the name of a player. */ fun getPlayerName(id: UUID): String + + /** + * Returns whether a player is online on this server. + */ + fun isOnline(id: UUID): Boolean } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt index fc1ec6d..3dcccab 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt @@ -1,7 +1,7 @@ package app.simplecloud.prefixes.shared.sync import app.simplecloud.api.runtime.SimpleCloudRuntime -import app.simplecloud.prefixes.shared.utilities.Constants +import app.simplecloud.prefixes.shared.PrefixesConstants class PrefixesSubjects( networkId: String, @@ -26,12 +26,12 @@ class PrefixesSubjects( .distinct() .toList() - if (Constants.ALL_SYNC_SOURCE in resolvedSources) { + if (PrefixesConstants.ALL_SYNC_SOURCE in resolvedSources) { return listOf("$root.*.*.$subject") } return resolvedSources - .map { source -> if (source == Constants.CURRENT_SYNC_SOURCE) origin else source } + .map { source -> if (source == PrefixesConstants.CURRENT_SYNC_SOURCE) origin else source } .distinct() .map { source -> "$root.$source.*.$subject" } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt index 3bfc5e0..484ef6f 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt @@ -3,22 +3,23 @@ package app.simplecloud.prefixes.shared.sync import app.simplecloud.api.CloudApi import app.simplecloud.plugin.api.shared.config.ConfigurationFactory import app.simplecloud.prefixes.shared.config.PrefixesConfig -import app.simplecloud.prefixes.shared.platform.PrefixesLogger +import app.simplecloud.prefixes.shared.PrefixesConstants import io.nats.client.Connection import io.nats.client.Nats import io.nats.client.Options +import org.slf4j.LoggerFactory class PrefixesSync( - config: ConfigurationFactory, - private val logger: PrefixesLogger + config: ConfigurationFactory ) { - private val api = CloudApi.create() + private val logger = LoggerFactory.getLogger(PrefixesConstants.LOGGER_NAME) private val connection = createNatsConnection() + private val api = CloudApi.create() private val subjects = PrefixesSubjects(api.networkId) - val publisher = SyncPublisher(connection, subjects, config, logger) - val subscriber = SyncSubscriber(connection, subjects, config, logger) + val publisher = SyncPublisher(connection, subjects, config) + val subscriber = SyncSubscriber(connection, subjects, config) fun shutdown() { runCatching { diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt index c854523..0bdc454 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt @@ -1,13 +1,11 @@ package app.simplecloud.prefixes.shared.sync import app.simplecloud.plugin.api.shared.config.ConfigurationFactory -import app.simplecloud.prefixes.shared.config.FeaturesConfig import app.simplecloud.prefixes.shared.config.PrefixesConfig -import app.simplecloud.prefixes.shared.config.SyncChannels -import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistEntryMapper import app.simplecloud.prefixes.shared.utilities.ComponentSerializer +import app.simplecloud.prefixes.shared.PrefixesConstants import app.simplecloud.prefixes.v1.ChatMessageEvent import app.simplecloud.prefixes.v1.TablistEntryRemoveEvent import app.simplecloud.prefixes.v1.TablistSyncRequest @@ -15,64 +13,39 @@ import com.google.protobuf.MessageLite import io.nats.client.Connection import net.kyori.adventure.text.Component import java.util.UUID -import java.util.concurrent.atomic.AtomicBoolean class SyncPublisher( private val connection: Connection, private val subjects: PrefixesSubjects, - private val config: ConfigurationFactory, - private val logger: PrefixesLogger + private val config: ConfigurationFactory ) { + private val logger = PrefixesConstants.LOGGER - fun publishChatMessage(message: Component) = publish( - FeaturesConfig::chat, - SyncChannels::chat, - PrefixesSubjects.CHAT - ) { + fun publishChatMessage(message: Component) = publish(PrefixesConfig::isChatSynced, PrefixesSubjects.CHAT) { ChatMessageEvent.newBuilder() .setJson(ComponentSerializer.serialize(message)) .build() } - fun publishTablistEntry(entry: TablistEntry) = publish( - FeaturesConfig::tablist, - SyncChannels::tablist, - PrefixesSubjects.TABLIST_UPDATE - ) { + fun publishTablistEntry(entry: TablistEntry) = publish(PrefixesConfig::isTablistSynced, PrefixesSubjects.TABLIST_UPDATE) { TablistEntryMapper.toDefinition(entry) } - fun publishTablistRemove(uniqueId: UUID) = publish( - FeaturesConfig::tablist, - SyncChannels::tablist, - PrefixesSubjects.TABLIST_REMOVE - ) { + fun publishTablistRemove(uniqueId: UUID) = publish(PrefixesConfig::isTablistSynced, PrefixesSubjects.TABLIST_REMOVE) { TablistEntryRemoveEvent.newBuilder() .setPlayerId(uniqueId.toString()) .build() } - fun publishTablistRequest() = publish( - FeaturesConfig::tablist, - SyncChannels::tablist, - PrefixesSubjects.TABLIST_REQUEST - ) { + fun publishTablistRequest() = publish(PrefixesConfig::isTablistSynced, PrefixesSubjects.TABLIST_REQUEST) { TablistSyncRequest.getDefaultInstance() } - private fun publish( - feature: (FeaturesConfig) -> Boolean, - channel: (SyncChannels) -> Boolean, - subject: String, - message: () -> MessageLite - ) { - val config = config.get() - if (!feature(config.features) || !config.sync.enabled || !channel(config.sync.channels)) return - - val target = subjects.own(subject) + private fun publish(enabled: (PrefixesConfig) -> Boolean, subject: String, message: () -> MessageLite) { + if (!enabled(config.get())) return try { - connection.publish(target, message().toByteArray()) + connection.publish(subjects.own(subject), message().toByteArray()) } catch (e: Exception) { logger.error("Failed to publish sync message on '$subject'", e) } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt index 535c954..d5fbc96 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncSubscriber.kt @@ -1,13 +1,11 @@ package app.simplecloud.prefixes.shared.sync import app.simplecloud.plugin.api.shared.config.ConfigurationFactory -import app.simplecloud.prefixes.shared.config.FeaturesConfig import app.simplecloud.prefixes.shared.config.PrefixesConfig -import app.simplecloud.prefixes.shared.config.SyncChannels -import app.simplecloud.prefixes.shared.platform.PrefixesLogger import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistEntryMapper import app.simplecloud.prefixes.shared.utilities.ComponentSerializer +import app.simplecloud.prefixes.shared.PrefixesConstants import app.simplecloud.prefixes.v1.ChatMessageEvent import app.simplecloud.prefixes.v1.TablistEntryRemoveEvent import app.simplecloud.prefixes.v1.TablistEntryUpdateEvent @@ -20,14 +18,13 @@ import java.util.UUID class SyncSubscriber( connection: Connection, private val subjects: PrefixesSubjects, - private val config: ConfigurationFactory, - private val logger: PrefixesLogger + private val config: ConfigurationFactory ) { - + private val logger = PrefixesConstants.LOGGER private val dispatcher = connection.createDispatcher(null) fun subscribeChatMessage(handler: (Component) -> Unit) { - subscribe(FeaturesConfig::chat, SyncChannels::chat, PrefixesSubjects.CHAT) { message -> + subscribe(PrefixesConfig::isChatSynced, PrefixesSubjects.CHAT) { message -> handler(ComponentSerializer.deserialize(ChatMessageEvent.parseFrom(message.data).json)) } } @@ -37,31 +34,19 @@ class SyncSubscriber( onRemove: (String, UUID) -> Unit, onRequest: () -> Unit ) { - subscribe( - FeaturesConfig::tablist, - SyncChannels::tablist, - PrefixesSubjects.TABLIST_UPDATE - ) { message -> + subscribe(PrefixesConfig::isTablistSynced, PrefixesSubjects.TABLIST_UPDATE) { message -> onUpdate( subjects.publisherId(message.subject, PrefixesSubjects.TABLIST_UPDATE), TablistEntryMapper.fromDefinition(TablistEntryUpdateEvent.parseFrom(message.data)) ) } - subscribe( - FeaturesConfig::tablist, - SyncChannels::tablist, - PrefixesSubjects.TABLIST_REMOVE - ) { message -> + subscribe(PrefixesConfig::isTablistSynced, PrefixesSubjects.TABLIST_REMOVE) { message -> onRemove( subjects.publisherId(message.subject, PrefixesSubjects.TABLIST_REMOVE), UUID.fromString(TablistEntryRemoveEvent.parseFrom(message.data).playerId) ) } - subscribe( - FeaturesConfig::tablist, - SyncChannels::tablist, - PrefixesSubjects.TABLIST_REQUEST - ) { + subscribe(PrefixesConfig::isTablistSynced, PrefixesSubjects.TABLIST_REQUEST) { onRequest() } } @@ -74,42 +59,24 @@ class SyncSubscriber( } } - private fun subscribe( - feature: (FeaturesConfig) -> Boolean, - channel: (SyncChannels) -> Boolean, - subject: String, - handler: (Message) -> Unit - ) { - val config = config.get() - if (!feature(config.features) || !config.sync.enabled || !channel(config.sync.channels)) return - - subjects.patterns(config.sync.sources, subject).forEach { pattern -> - runCatching { subscribe(pattern, feature, channel, handler) }.onFailure { throwable -> + private fun subscribe(enabled: (PrefixesConfig) -> Boolean, subject: String, handler: (Message) -> Unit) { + subjects.patterns(config.get().sync.sources, subject).forEach { pattern -> + runCatching { subscribe(pattern, enabled, handler) }.onFailure { throwable -> logger.error("Failed to subscribe to the sync subject '$pattern'", throwable) } } } - private fun subscribe( - pattern: String, - feature: (FeaturesConfig) -> Boolean, - channel: (SyncChannels) -> Boolean, - handler: (Message) -> Unit - ) { + private fun subscribe(pattern: String, enabled: (PrefixesConfig) -> Boolean, handler: (Message) -> Unit) { dispatcher.subscribe(pattern) { message -> - val currentConfig = this.config.get() - if (feature(currentConfig.features) && - currentConfig.sync.enabled && - channel(currentConfig.sync.channels) && - !subjects.isOwn(message.subject) - ) { - try { - handler(message) - } catch (e: Exception) { - logger.error("Failed to handle sync message on '${message.subject}'", e) - } + if (!enabled(config.get()) || subjects.isOwn(message.subject)) return@subscribe + + try { + handler(message) + } catch (e: Exception) { + logger.error("Failed to handle sync message on '${message.subject}'", e) } } } -} +} \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/AudienceResolver.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/AudienceResolver.kt new file mode 100644 index 0000000..729ff4c --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/AudienceResolver.kt @@ -0,0 +1,14 @@ +package app.simplecloud.prefixes.shared.utilities + +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.identity.Identity +import java.util.UUID + +object AudienceResolver { + + fun resolveIds(audience: Audience): List = buildList { + audience.forEachAudience { member -> member.get(Identity.UUID).ifPresent(::add) } + } + + fun resolveId(audience: Audience): UUID? = audience.get(Identity.UUID).orElse(null) +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt index 02554fd..a7c94b5 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt @@ -1,14 +1,15 @@ package app.simplecloud.prefixes.shared.utilities +import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor import net.kyori.adventure.text.minimessage.MiniMessage object ColorParser { fun parse(color: String): TextColor? { - if (color.isEmpty()) return null - val trimmed = color.trim() + if (trimmed.isEmpty()) return null + if (trimmed.startsWith("#")) { return TextColor.fromHexString(trimmed) } @@ -16,12 +17,11 @@ object ColorParser { return TextColor.fromHexString(trimmed.substring(1, trimmed.length - 1)) } - val parsed = MiniMessage.miniMessage().deserialize(trimmed) - return parsed.color() ?: parsed.style().color() - } + val named = NamedTextColor.NAMES.value(trimmed.lowercase()) + if (named != null) return named - fun serialize(color: TextColor?): String { - if (color == null) return "" - return "<${color.asHexString().uppercase()}>" + return MiniMessage.miniMessage().deserialize(trimmed).color() } + + fun serialize(color: TextColor): String = "<${color.asHexString().uppercase()}>" } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/Constants.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/Constants.kt deleted file mode 100644 index 11f0ce6..0000000 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/Constants.kt +++ /dev/null @@ -1,11 +0,0 @@ -package app.simplecloud.prefixes.shared.utilities - -object Constants { - - const val CONFIG_SOURCE = "config" - const val LUCKPERMS_SOURCE = "luckperms" - - const val CURRENT_SYNC_SOURCE = "CURRENT" - const val ALL_SYNC_SOURCE = "ALL" - -} \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt index 02b6e31..019cc11 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt @@ -7,15 +7,17 @@ import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder object PlayerDisplayFormatter { - fun formatDisplayName(data: PrefixesPlayerData, playerName: String, enabled: Boolean): Component { - return if (enabled) data.displayName else Component.text(playerName) + fun formatDisplayName(data: PrefixesPlayerData, playerName: String, displayNameEnabled: Boolean): Component { + return if (displayNameEnabled) data.displayName else Component.text(playerName) } - fun formatTablistName(data: PrefixesPlayerData, displayName: Component = data.displayName): Component { - return data.prefix.append(displayName).append(data.suffix) + fun formatTablistName(data: PrefixesPlayerData, playerName: String, displayNameEnabled: Boolean): Component { + return data.prefix + .append(formatDisplayName(data, playerName, displayNameEnabled)) + .append(data.suffix) } - fun formatChatMessage(data: PrefixesPlayerData, playerName: String, message: Component, displayName: Component = data.displayName): Component { + fun formatChatMessage(data: PrefixesPlayerData, playerName: String, message: Component, displayNameEnabled: Boolean): Component { return miniMessage.deserialize( data.chatFormat, Placeholder.component("prefix", data.prefix), @@ -23,7 +25,7 @@ object PlayerDisplayFormatter { Placeholder.styling("color", data.color), Placeholder.unparsed("playername", playerName), Placeholder.unparsed("name", playerName), - Placeholder.component("displayname", displayName), + Placeholder.component("displayname", formatDisplayName(data, playerName, displayNameEnabled)), Placeholder.component("message", message) ) } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/config/DefaultConfigs.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/config/DefaultConfigs.kt index c1ea24c..6897060 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/config/DefaultConfigs.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/config/DefaultConfigs.kt @@ -1,42 +1,7 @@ package app.simplecloud.prefixes.shared.utilities.config -import app.simplecloud.prefixes.shared.config.ConfigGroup - object DefaultConfigs { val VARIABLES: Map = mapOf("prefix" to "โšก") - val GROUPS: List = listOf( - ConfigGroup( - name = "owner", - priority = 100, - permission = "simplecloud.prefix.group.owner", - prefix = "<#DC2626>Owner <#475569>| ", - suffix = "", - color = "<#DC2626>", - displayName = "", - chatFormat = " <#475569>ยป <#F8FAFC>" - ), - ConfigGroup( - name = "admin", - priority = 90, - permission = "simplecloud.prefix.group.admin", - prefix = "<#F59E0B>Admin <#475569>| ", - suffix = "", - color = "<#F59E0B>", - displayName = "", - chatFormat = " <#475569>ยป <#F8FAFC>" - ), - ConfigGroup( - name = "default", - priority = 0, - permission = "", - prefix = "<#94A3B8>Player <#475569>| ", - suffix = "", - color = "<#94A3B8>", - displayName = "", - chatFormat = " <#475569>ยป <#F8FAFC>" - ) - ) - -} \ No newline at end of file +} diff --git a/prefixes-shared/src/main/resources/config.yml b/prefixes-shared/src/main/resources/config.yml index c17a872..1493948 100644 --- a/prefixes-shared/src/main/resources/config.yml +++ b/prefixes-shared/src/main/resources/config.yml @@ -59,13 +59,20 @@ sync: # Groups # Only used when general.source is config. # -# Supported placeholders: +# Placeholders in display-name: +# Group color +# Normal Minecraft player name +# +# Placeholders in chat-format: # Group prefix # Group suffix # Group color -# Normal Minecraft player name +# Normal Minecraft player name (alias: ) # Formatted display name # Chat message +# +# Colors can be a hex code ('#DC2626' or '<#DC2626>') or a color name ('red'). +# If display-name or chat-format is empty, the default format is used. # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ groups: - name: owner diff --git a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfigTest.kt b/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfigTest.kt deleted file mode 100644 index 393fda6..0000000 --- a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfigTest.kt +++ /dev/null @@ -1,49 +0,0 @@ -package app.simplecloud.prefixes.shared.config - -import app.simplecloud.plugin.api.shared.config.ConfigurationFactory -import java.nio.file.Files -import kotlin.io.path.readText -import kotlin.io.path.writeText -import kotlin.test.Test -import kotlin.test.assertContains -import kotlin.test.assertEquals - -class PrefixesConfigTest { - - @Test - fun `installs and loads the documented version one config`() { - val directory = Files.createTempDirectory("prefixes-config-test") - val path = directory.resolve("config.yml") - - try { - DefaultConfigInstaller.install(path, javaClass.classLoader) - - val yaml = path.readText() - assertContains(yaml, "version: 1") - assertContains(yaml, "# General") - assertContains(yaml, "features:") - assertContains(yaml, "display-name: true") - assertContains(yaml, "# Sync") - assertContains(yaml, "channels:") - assertContains(yaml, "sources:") - assertContains(yaml, "CURRENT") - assertContains(yaml, "Special values:") - assertContains(yaml, "# Groups") - assertContains(yaml, "Supported placeholders:") - - val factory = ConfigurationFactory(path.toFile(), PrefixesConfig::class.java) - val expected = PrefixesConfig() - val loaded = factory.loadOrCreate(expected) - - assertEquals(expected, loaded) - - val customized = "$yaml\n# This user comment must be kept.\n" - path.writeText(customized) - DefaultConfigInstaller.install(path, javaClass.classLoader) - assertEquals(customized, path.readText()) - } finally { - Files.deleteIfExists(path) - Files.deleteIfExists(directory) - } - } -} diff --git a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProviderTest.kt b/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProviderTest.kt deleted file mode 100644 index f71f01f..0000000 --- a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProviderTest.kt +++ /dev/null @@ -1,97 +0,0 @@ -package app.simplecloud.prefixes.shared.group.luckperms - -import net.luckperms.api.model.group.Group -import java.lang.reflect.Proxy -import java.util.OptionalInt -import kotlin.test.Test -import kotlin.test.assertEquals - -class LuckPermsGroupProviderTest { - - @Test - fun `selects the applicable group with the highest weight`() { - val selected = selectHighestWeightedGroup( - listOf( - group("member", 10), - group("admin", 100), - group("moderator", 50) - ), - primaryGroupName = "member" - ) - - assertEquals("admin", selected?.name) - } - - @Test - fun `prefers the primary group when weights are equal`() { - val selected = selectHighestWeightedGroup( - listOf( - group("admin", 100), - group("moderator", 100) - ), - primaryGroupName = "moderator" - ) - - assertEquals("moderator", selected?.name) - } - - @Test - fun `uses the group name as a stable final tie breaker`() { - val groups = listOf( - group("moderator", 100), - group("admin", 100) - ) - - val selected = selectHighestWeightedGroup(groups, primaryGroupName = "member") - val selectedFromReversedInput = selectHighestWeightedGroup(groups.reversed(), primaryGroupName = "member") - - assertEquals("admin", selected?.name) - assertEquals("admin", selectedFromReversedInput?.name) - } - - @Test - fun `treats a missing group weight as zero`() { - val selected = selectHighestWeightedGroup( - listOf( - group("unweighted"), - group("negative", -1) - ), - primaryGroupName = "negative" - ) - - assertEquals("unweighted", selected?.name) - } - - private fun group(name: String, weight: Int? = null): Group { - return Proxy.newProxyInstance( - Group::class.java.classLoader, - arrayOf(Group::class.java) - ) { proxy, method, arguments -> - when (method.name) { - "getName" -> name - "getWeight" -> weightOf(weight) - "equals" -> proxy === arguments?.firstOrNull() - "hashCode" -> System.identityHashCode(proxy) - "toString" -> "Group(name=$name, weight=$weight)" - else -> error("Unexpected Group method: ${method.name}") - } - } as Group - } - - private fun weightOf(weight: Int?): OptionalInt { - if (weight == null) return OptionalInt.empty() - return OptionalInt.of(weight) - } - - private fun selectHighestWeightedGroup(groups: Iterable, primaryGroupName: String): Group? { - return groups - .distinctBy { it.name } - .sortedWith( - compareByDescending { it.weight.orElse(0) } - .thenByDescending { it.name == primaryGroupName } - .thenBy { it.name } - ) - .firstOrNull() - } - -} diff --git a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjectsTest.kt b/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjectsTest.kt deleted file mode 100644 index 189dd2b..0000000 --- a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjectsTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -package app.simplecloud.prefixes.shared.sync - -import app.simplecloud.prefixes.shared.config.ALL_SYNC_SOURCE -import app.simplecloud.prefixes.shared.config.CURRENT_SYNC_SOURCE -import kotlin.test.Test -import kotlin.test.assertEquals - -class PrefixesSubjectsTest { - - private val subjects = PrefixesSubjects( - networkId = "network", - origin = "lobby", - serverId = "lobby-1" - ) - - @Test - fun `extracts the same publisher from update and remove subjects`() { - val updateSubject = subjects.own(PrefixesSubjects.TABLIST_UPDATE) - val removeSubject = subjects.own(PrefixesSubjects.TABLIST_REMOVE) - - assertEquals( - "network.prefixes.lobby.lobby-1", - subjects.publisherId(updateSubject, PrefixesSubjects.TABLIST_UPDATE) - ) - assertEquals( - "network.prefixes.lobby.lobby-1", - subjects.publisherId(removeSubject, PrefixesSubjects.TABLIST_REMOVE) - ) - } - - @Test - fun `resolves the current server group`() { - assertEquals( - listOf("network.prefixes.lobby.*.chat"), - subjects.patterns(listOf(CURRENT_SYNC_SOURCE), PrefixesSubjects.CHAT) - ) - } - - @Test - fun `accepts group names and persistent server ids in the same source list`() { - assertEquals( - listOf( - "network.prefixes.survival.*.chat", - "network.prefixes.standalone-lobby-1.*.chat" - ), - subjects.patterns( - listOf("survival", "standalone-lobby-1"), - PrefixesSubjects.CHAT - ) - ) - } - - @Test - fun `resolves all servers to one wildcard pattern`() { - assertEquals( - listOf("network.prefixes.*.*.tablist.update"), - subjects.patterns(listOf(ALL_SYNC_SOURCE), PrefixesSubjects.TABLIST_UPDATE) - ) - } - - @Test - fun `ignores blank and duplicate sources`() { - assertEquals( - listOf("network.prefixes.lobby.*.chat"), - subjects.patterns( - listOf("", " lobby ", CURRENT_SYNC_SOURCE, CURRENT_SYNC_SOURCE), - PrefixesSubjects.CHAT - ) - ) - } -} diff --git a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntryTest.kt b/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntryTest.kt deleted file mode 100644 index 18f3ba4..0000000 --- a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/sync/tablist/TablistEntryTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -package app.simplecloud.prefixes.shared.sync.tablist - -import net.kyori.adventure.text.Component -import java.util.UUID -import kotlin.test.Test -import kotlin.test.assertEquals - -class TablistEntryTest { - - @Test - fun `serializes all player info`() { - val entry = TablistEntry( - uniqueId = UUID.fromString("c1688a0f-13e4-4b1c-82c6-7f576f4d9021"), - name = "Player", - displayName = Component.text("Displayed Player"), - priority = 42, - profileProperties = listOf( - ProfileProperty("textures", "texture-value", "texture-signature"), - ProfileProperty("unsigned", "value", null) - ), - latency = 123, - gameMode = TablistGameMode.CREATIVE, - showHat = false, - listOrder = 7 - ) - - assertEquals(entry, TablistEntryMapper.fromDefinition(TablistEntryMapper.toDefinition(entry))) - } - - @Test - fun `uses safe defaults for older sync messages`() { - val definition = TablistEntry( - uniqueId = UUID.fromString("b2460751-a9e8-4b4a-ace2-25b883231667"), - name = "Player", - displayName = Component.text("Player"), - priority = 0 - ).let(TablistEntryMapper::toDefinition).toBuilder() - .clearGameMode() - .clearShowHat() - .build() - - val entry = TablistEntryMapper.fromDefinition(definition) - - assertEquals(TablistGameMode.SURVIVAL, entry.gameMode) - assertEquals(true, entry.showHat) - } -} diff --git a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatterTest.kt b/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatterTest.kt deleted file mode 100644 index 35c2c01..0000000 --- a/prefixes-shared/src/test/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatterTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -package app.simplecloud.prefixes.shared.utilities - -import app.simplecloud.prefixes.api.group.PrefixesPlayerData -import net.kyori.adventure.text.Component -import net.kyori.adventure.text.format.NamedTextColor -import kotlin.test.Test -import kotlin.test.assertEquals - -class PlayerDisplayFormatterTest { - - private val data = PrefixesPlayerData( - prefix = Component.text("[Admin] "), - suffix = Component.text(" !"), - color = NamedTextColor.RED, - displayName = Component.text("Fancy"), - chatFormat = ": ", - priority = 90 - ) - - @Test - fun `uses the configured display name when enabled`() { - assertEquals( - Component.text("Fancy"), - PlayerDisplayFormatter.formatDisplayName(data, "Player", enabled = true) - ) - } - - @Test - fun `uses the minecraft player name when display names are disabled`() { - val displayName = PlayerDisplayFormatter.formatDisplayName(data, "Player", enabled = false) - - assertEquals(Component.text("Player"), displayName) - assertEquals( - Component.text("[Admin] ").append(displayName).append(Component.text(" !")), - PlayerDisplayFormatter.formatTablistName(data, displayName) - ) - } -} From ac0e1bbd1c3a558f5f902d6c104bd5f93d2f1d2e Mon Sep 17 00:00:00 2001 From: Janis Date: Thu, 8 Oct 2026 01:16:35 +0200 Subject: [PATCH 12/22] fix: allow prefix in name tag without duplicating it in tablist --- README.md | 15 +- build.gradle.kts | 4 +- .../display/MinestomDisplayManager.kt | 9 +- prefixes-paper/build.gradle.kts | 15 +- .../cloud/bukkit/parser/ItemStackParser.java | 358 ------------------ .../paper/display/PaperDisplayManager.kt | 9 +- .../paper/CloudItemStackParserTest.kt | 23 -- .../shared/data/PrefixesDataRegistry.kt | 8 +- .../utilities/PlayerDisplayFormatter.kt | 4 +- prefixes-shared/src/main/resources/config.yml | 11 +- 10 files changed, 35 insertions(+), 421 deletions(-) delete mode 100644 prefixes-paper/src/main/java/org/incendo/cloud/bukkit/parser/ItemStackParser.java delete mode 100644 prefixes-paper/src/test/kotlin/app/simplecloud/prefixes/paper/CloudItemStackParserTest.kt diff --git a/README.md b/README.md index 4f3d442..2bc6a9f 100644 --- a/README.md +++ b/README.md @@ -25,16 +25,17 @@ > All information about this project can be found in our detailed [documentation][docs-thisproject]. -The Prefixes Plugin provides comprehensive rank management for chat, tab list and name tag displays. Groups are either defined in the plugin config or read from LuckPerms, and within a SimpleCloud network chat messages and tab list entries can be synced across servers. +A Paper plugin and Minestom library for prefixes, suffixes and name colors in chat, the tab list and name tags. Groups come from a simple config or straight from LuckPerms, and on a SimpleCloud network chat and tab list can be shared across servers. ## Features -- [x] **LuckPerms Integration**: Use the built-in config groups or let the plugin convert your LuckPerms groups into prefix groups. -- [x] **Chat, Tab List & Name Tags**: Prefixes, suffixes, colors and display names are applied to chat messages, the tab list and player name tags. -- [x] **Cross-Server Sync**: Share chat messages and tab list entries between all servers, selected server groups or persistent servers of your network. -- [x] **MiniMessage Formats**: Prefixes, suffixes, display names and chat formats are fully customizable with placeholder support. -- [x] **Supported Server Software**: Supports Paper and Forks, as well Minestom. -- [x] **Quick Setup**: Easy installation process for all supported software. +- [x] **Chat, Tab List & Name Tags**: Prefixes, suffixes and colors everywhere. +- [x] **Config or LuckPerms**: Define groups in the config or use your LuckPerms groups. +- [x] **Sorted Tab List**: Players are ordered by group priority. +- [x] **Network Sync**: Share chat and tab list across your SimpleCloud network. +- [x] **MiniMessage Formats**: Fully customizable with placeholders. +- [x] **Developer API**: Set prefixes per player or even per viewer, e.g. for teams or friends. +- [x] **Supported Software**: Paper plugin (and forks) or Minestom library. ## Dependency diff --git a/build.gradle.kts b/build.gradle.kts index b658e87..9cf7e2e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,11 +9,10 @@ plugins { allprojects { group = "app.simplecloud.plugin" - version = "1.0.3" + version = "1.1.0-beta.1" repositories { mavenCentral() - maven("https://repo.papermc.io/repository/maven-public/") maven("https://oss.sonatype.org/content/repositories/snapshots") maven("https://oss.sonatype.org/content/repositories/central") maven("https://repo.papermc.io/repository/maven-public/") @@ -57,6 +56,7 @@ subprojects { tasks.shadowJar { mergeServiceFiles() + duplicatesStrategy = DuplicatesStrategy.INCLUDE archiveFileName.set("${project.name}.jar") } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt index 90dfd63..9c1fa5f 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt @@ -63,7 +63,7 @@ class MinestomDisplayManager( if (!player.isOnline) return val data = registry.getData(player.uuid) ?: return - player.displayName = if (prefixes.config.get().features.tablist) formatTablistName(player, data) else null + player.displayName = if (prefixes.config.get().features.tablist) PlayerDisplayFormatter.formatTablistName(data, player.username) else null val team = createTeam(player, data) onlinePlayers().forEach { viewer -> renderFor(player, viewer, team) } @@ -97,7 +97,7 @@ class MinestomDisplayManager( updateTeam(player, viewer, createTeam(player, viewerData)) if (prefixes.config.get().features.tablist) { - viewer.sendPacket(createListNamePacket(player, formatTablistName(player, viewerData))) + viewer.sendPacket(createListNamePacket(player, PlayerDisplayFormatter.formatTablistName(viewerData, player.username))) } } @@ -116,7 +116,7 @@ class MinestomDisplayManager( val entry = TablistEntry( uniqueId = player.uuid, name = player.username, - displayName = formatTablistName(player, data), + displayName = PlayerDisplayFormatter.formatTablistName(data, player.username), priority = data.priority, profileProperties = if (skin == null) emptyList() else listOf(ProfileProperty("textures", skin.textures(), skin.signature())), latency = player.latency, @@ -155,9 +155,6 @@ class MinestomDisplayManager( viewer.sendPacket(MinestomPlayerTeam.removePacket(team)) } - private fun formatTablistName(player: Player, data: PrefixesPlayerData): Component = - PlayerDisplayFormatter.formatTablistName(data, player.username, prefixes.config.get().features.displayName) - private fun createListNamePacket(player: Player, name: Component): PlayerInfoUpdatePacket { val entry = PlayerInfoUpdatePacket.Entry( player.uuid, diff --git a/prefixes-paper/build.gradle.kts b/prefixes-paper/build.gradle.kts index 58d7a73..e605529 100644 --- a/prefixes-paper/build.gradle.kts +++ b/prefixes-paper/build.gradle.kts @@ -6,7 +6,7 @@ plugins { val customNamesRoot = rootProject.layout.projectDirectory.dir("custom-names") require( customNamesRoot.dir("custom-names-api/src/main/kotlin").asFile.isDirectory && - customNamesRoot.dir("custom-names-paper/src/main/kotlin").asFile.isDirectory + customNamesRoot.dir("custom-names-paper/src/main/kotlin").asFile.isDirectory ) { "The custom-names submodule is missing. Run `git submodule update --init --recursive`." } @@ -43,17 +43,10 @@ modrinth { versionType.set("release") uploadFile.set(tasks.shadowJar) gameVersions.addAll( - "26.3" + "26.2", + "26.3", ) loaders.addAll("paper", "purpur") changelog.set("https://docs.simplecloud.app/changelog") syncBodyFrom.set(rootProject.file("README.md").readText()) -} - -// Paper bootstrap writes logs; keep regression-test output under build/. -tasks.test { - workingDir = layout.buildDirectory.dir("test-server").get().asFile - doFirst { - workingDir.mkdirs() - } -} +} \ No newline at end of file diff --git a/prefixes-paper/src/main/java/org/incendo/cloud/bukkit/parser/ItemStackParser.java b/prefixes-paper/src/main/java/org/incendo/cloud/bukkit/parser/ItemStackParser.java deleted file mode 100644 index 294445c..0000000 --- a/prefixes-paper/src/main/java/org/incendo/cloud/bukkit/parser/ItemStackParser.java +++ /dev/null @@ -1,358 +0,0 @@ -// -// MIT License -// -// Copyright (c) 2024 Incendo -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -// Backport for Paper 26.3 from https://github.com/Incendo/cloud-minecraft/issues/174. -// Based on cloud-bukkit 2.0.0; remove when upstream ships the fallback. -package org.incendo.cloud.bukkit.parser; - -import com.google.common.base.Suppliers; -import com.mojang.brigadier.arguments.ArgumentType; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import org.apiguardian.api.API; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.inventory.ItemStack; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.incendo.cloud.brigadier.parser.WrappedBrigadierParser; -import org.incendo.cloud.bukkit.BukkitCommandManager; -import org.incendo.cloud.bukkit.data.ProtoItemStack; -import org.incendo.cloud.bukkit.internal.CommandBuildContextSupplier; -import org.incendo.cloud.bukkit.internal.CraftBukkitReflection; -import org.incendo.cloud.bukkit.internal.MinecraftArgumentTypes; -import org.incendo.cloud.component.CommandComponent; -import org.incendo.cloud.context.CommandContext; -import org.incendo.cloud.context.CommandInput; -import org.incendo.cloud.parser.ArgumentParseResult; -import org.incendo.cloud.parser.ArgumentParser; -import org.incendo.cloud.parser.ParserDescriptor; -import org.incendo.cloud.suggestion.BlockingSuggestionProvider; -import org.incendo.cloud.suggestion.SuggestionProvider; - -import static java.util.Objects.requireNonNull; - -/** - * Parser for a {@link Material} and optional extra NBT data into a {@link ProtoItemStack}. - * - *

This parser only provides basic suggestions by default. On Minecraft 1.13 and newer, enabling Brigadier - * compatibility through {@link BukkitCommandManager#registerBrigadier()} will allow client side validation and - * suggestions to be utilized.

- * - * @param Command sender type - * @since 1.5.0 - */ -public class ItemStackParser implements ArgumentParser.FutureArgumentParser { - - /** - * Creates a new item stack parser. - * - * @param command sender type - * @return the created parser - * @since 2.0.0 - */ - @API(status = API.Status.STABLE, since = "2.0.0") - public static @NonNull ParserDescriptor itemStackParser() { - return ParserDescriptor.of(new ItemStackParser<>(), ProtoItemStack.class); - } - - /** - * Returns a {@link CommandComponent.Builder} using {@link #itemStackParser()} as the parser. - * - * @param the command sender type - * @return the component builder - * @since 2.0.0 - */ - @API(status = API.Status.STABLE, since = "2.0.0") - public static CommandComponent.@NonNull Builder itemStackComponent() { - return CommandComponent.builder().parser(itemStackParser()); - } - - private final ArgumentParser parser; - - private static @Nullable Class findItemInputClass() { - final Class[] classes = new Class[]{ - CraftBukkitReflection.findNMSClass("ArgumentPredicateItemStack"), - CraftBukkitReflection.findMCClass("commands.arguments.item.ArgumentPredicateItemStack"), - CraftBukkitReflection.findMCClass("commands.arguments.item.ItemInput") - }; - for (final Class clazz : classes) { - if (clazz != null) { - return clazz; - } - } - return null; - } - - /** - * Create a new {@link ItemStackParser}. - * - * @since 1.5.0 - */ - public ItemStackParser() { - if (findItemInputClass() != null) { - this.parser = new ModernParser<>(); - } else { - this.parser = new LegacyParser<>(); - } - } - - @Override - public final @NonNull CompletableFuture<@NonNull ArgumentParseResult> parseFuture( - final @NonNull CommandContext commandContext, - final @NonNull CommandInput commandInput - ) { - return this.parser.parseFuture(commandContext, commandInput); - } - - @Override - public final @NonNull SuggestionProvider suggestionProvider() { - return this.parser.suggestionProvider(); - } - - - private static final class ModernParser implements ArgumentParser.FutureArgumentParser { - - private static final Class NMS_ITEM_STACK_CLASS = CraftBukkitReflection.needNMSClassOrElse( - "ItemStack", - "net.minecraft.world.item.ItemStack" - ); - private static final Class CRAFT_ITEM_STACK_CLASS = - CraftBukkitReflection.needOBCClass("inventory.CraftItemStack"); - private static final Supplier> ARGUMENT_ITEM_STACK_CLASS = - Suppliers.memoize(() -> MinecraftArgumentTypes.getClassByKey(NamespacedKey.minecraft("item_stack"))); - private static final Class ITEM_INPUT_CLASS = requireNonNull(findItemInputClass(), "ItemInput class"); - private static final Class NMS_ITEM_CLASS = CraftBukkitReflection.needNMSClassOrElse( - "Item", - "net.minecraft.world.item.Item" - ); - private static final Supplier GET_MATERIAL_METHOD = Suppliers.memoize(() -> CraftBukkitReflection - .needMethod(CraftBukkitReflection.needOBCClass("util.CraftMagicNumbers"), "getMaterial", NMS_ITEM_CLASS)); - private static final Method CREATE_ITEM_STACK_METHOD = CraftBukkitReflection.firstNonNullOrThrow( - () -> "Couldn't find createItemStack method on ItemInput", - CraftBukkitReflection.findMethod(ITEM_INPUT_CLASS, "a", int.class, boolean.class), - CraftBukkitReflection.findMethod(ITEM_INPUT_CLASS, "createItemStack", int.class, boolean.class), - CraftBukkitReflection.findMethod(ITEM_INPUT_CLASS, "createItemStack", int.class) - ); - private static final Method AS_BUKKIT_STACK_METHOD = CraftBukkitReflection.firstNonNullOrThrow( - () -> "Couldn't find asBukkitCopy or asCraftMirror method on CraftItemStack", - CraftBukkitReflection.findMethod(CRAFT_ITEM_STACK_CLASS, "asBukkitCopy", NMS_ITEM_STACK_CLASS), - CraftBukkitReflection.findMethod(CRAFT_ITEM_STACK_CLASS, "asCraftMirror", NMS_ITEM_STACK_CLASS), - CraftBukkitReflection.findMethod(CRAFT_ITEM_STACK_CLASS, "asBukkitMirror", NMS_ITEM_STACK_CLASS) - ); - private static final Field ITEM_FIELD = CraftBukkitReflection.firstNonNullOrThrow( - () -> "Couldn't find item field on ItemInput", - CraftBukkitReflection.findField(ITEM_INPUT_CLASS, "b"), - CraftBukkitReflection.findField(ITEM_INPUT_CLASS, "item") - ); - private static final Field EXTRA_DATA_FIELD = CraftBukkitReflection.firstNonNullOrThrow( - () -> "Couldn't find tag field on ItemInput", - CraftBukkitReflection.findField(ITEM_INPUT_CLASS, "c"), - CraftBukkitReflection.findField(ITEM_INPUT_CLASS, "tag"), - CraftBukkitReflection.findField(ITEM_INPUT_CLASS, "components") - ); - private static final Class HOLDER_CLASS = CraftBukkitReflection.findMCClass("core.Holder"); - private static final @Nullable Method VALUE_METHOD = HOLDER_CLASS == null - ? null - : CraftBukkitReflection.firstNonNullOrThrow( - () -> "Couldn't find Holder#value", - CraftBukkitReflection.findMethod(HOLDER_CLASS, "value"), - CraftBukkitReflection.findMethod(HOLDER_CLASS, "a") - ); - private static final Class NBT_TAG_CLASS = CraftBukkitReflection.firstNonNullOrThrow( - () -> "Cloud not find net.minecraft.nbt.Tag", - CraftBukkitReflection.findClass("net.minecraft.nbt.Tag"), - CraftBukkitReflection.findClass("net.minecraft.nbt.NBTBase"), - CraftBukkitReflection.findNMSClass("NBTBase") - ); - - private final ArgumentParser parser; - - ModernParser() { - this.parser = this.createParser(); - } - - @SuppressWarnings("unchecked") - private ArgumentParser createParser() { - final Supplier> inst = () -> { - final Constructor ctr = ARGUMENT_ITEM_STACK_CLASS.get().getDeclaredConstructors()[0]; - try { - if (ctr.getParameterCount() == 0) { - return (ArgumentType) ctr.newInstance(); - } else { - // 1.19+ - return (ArgumentType) ctr.newInstance(CommandBuildContextSupplier.commandBuildContext()); - } - } catch (final ReflectiveOperationException e) { - throw new RuntimeException("Failed to initialize modern ItemStack parser.", e); - } - }; - return new WrappedBrigadierParser(inst) - .flatMapSuccess((ctx, itemInput) -> ArgumentParseResult.successFuture( - new ModernProtoItemStack(itemInput))); - } - - @Override - public @NonNull CompletableFuture<@NonNull ArgumentParseResult<@NonNull ProtoItemStack>> parseFuture( - final @NonNull CommandContext<@NonNull C> commandContext, - final @NonNull CommandInput commandInput - ) { - // Minecraft has a parser for this - just use it - return this.parser.parseFuture(commandContext, commandInput); - } - - @Override - public @NonNull SuggestionProvider suggestionProvider() { - return this.parser.suggestionProvider(); - } - - - private static final class ModernProtoItemStack implements ProtoItemStack { - - private final Object itemInput; - private final Material material; - private final boolean hasExtraData; - - ModernProtoItemStack(final @NonNull Object itemInput) { - this.itemInput = itemInput; - try { - Object item = ITEM_FIELD.get(itemInput); - if (HOLDER_CLASS != null && HOLDER_CLASS.isInstance(item)) { - item = VALUE_METHOD.invoke(item); - } - this.material = (Material) GET_MATERIAL_METHOD.get().invoke(null, item); - final Object extraData = EXTRA_DATA_FIELD.get(itemInput); - if (NBT_TAG_CLASS.isInstance(extraData) || extraData == null) { - this.hasExtraData = extraData != null; - } else { - final List isEmptyMethod = Arrays.stream(extraData.getClass().getMethods()) - .filter(it -> it.getParameterCount() == 0 && it.getReturnType().equals(boolean.class)) - .collect(Collectors.toList()); - if (isEmptyMethod.size() != 1) { - throw new IllegalStateException( - "Failed to locate DataComponentMap/Patch#isEmpty; size=" + isEmptyMethod.size()); - } - this.hasExtraData = !(boolean) isEmptyMethod.get(0).invoke(extraData); - } - } catch (final ReflectiveOperationException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public @NonNull Material material() { - return this.material; - } - - @Override - public boolean hasExtraData() { - return this.hasExtraData; - } - - @Override - public @NonNull ItemStack createItemStack(final int stackSize) { - try { - final Object nmsItemStack = CREATE_ITEM_STACK_METHOD.getParameterCount() == 1 - ? CREATE_ITEM_STACK_METHOD.invoke(this.itemInput, stackSize) - : CREATE_ITEM_STACK_METHOD.invoke(this.itemInput, stackSize, true); - return (ItemStack) AS_BUKKIT_STACK_METHOD.invoke( - null, - nmsItemStack - ); - } catch (final InvocationTargetException ex) { - final Throwable cause = ex.getCause(); - if (cause instanceof CommandSyntaxException) { - throw new IllegalArgumentException(cause.getMessage(), cause); - } - throw new RuntimeException(ex); - } catch (final ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - } - } - - private static final class LegacyParser implements ArgumentParser.FutureArgumentParser, - BlockingSuggestionProvider.Strings { - - private final ArgumentParser parser = new MaterialParser() - .mapSuccess((ctx, material) -> CompletableFuture.completedFuture(new LegacyProtoItemStack(material))); - - @Override - public @NonNull CompletableFuture<@NonNull ArgumentParseResult<@NonNull ProtoItemStack>> parseFuture( - final @NonNull CommandContext<@NonNull C> commandContext, - final @NonNull CommandInput commandInput - ) { - return this.parser.parseFuture(commandContext, commandInput); - } - - @Override - public @NonNull Iterable<@NonNull String> stringSuggestions(final @NonNull CommandContext commandContext, - final @NonNull CommandInput input) { - return Arrays.stream(Material.values()) - .filter(Material::isItem) - .map(value -> value.name().toLowerCase(Locale.ROOT)) - .collect(Collectors.toList()); - } - - private static final class LegacyProtoItemStack implements ProtoItemStack { - - private final Material material; - - private LegacyProtoItemStack(final @NonNull Material material) { - this.material = material; - } - - @Override - public @NonNull Material material() { - return this.material; - } - - @Override - public boolean hasExtraData() { - return false; - } - - @Override - public @NonNull ItemStack createItemStack(final int stackSize) throws IllegalArgumentException { - if (stackSize > this.material.getMaxStackSize()) { - throw new IllegalArgumentException(String.format( - "The maximum stack size for %s is %d", - this.material, - this.material.getMaxStackSize() - )); - } - return new ItemStack(this.material, stackSize); - } - } - } -} diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt index f015925..3c4ec16 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt @@ -77,7 +77,7 @@ class PaperDisplayManager( val data = registry.getData(player.uniqueId) ?: return val features = prefixes.config.get().features - player.playerListName(if (features.tablist) formatTablistName(player, data) else null) + player.playerListName(if (features.tablist) PlayerDisplayFormatter.formatTablistName(data, player.name) else null) val team = createTeam(player, data) Bukkit.getOnlinePlayers().forEach { viewer -> renderFor(player, viewer, team) } @@ -110,7 +110,7 @@ class PaperDisplayManager( updateTeam(player, viewer, createTeam(player, viewerData)) if (prefixes.config.get().features.tablist) { - send(viewer, createListNamePacket(player, formatTablistName(player, viewerData))) + send(viewer, createListNamePacket(player, PlayerDisplayFormatter.formatTablistName(viewerData, player.name))) } } @@ -122,7 +122,7 @@ class PaperDisplayManager( val entry = TablistEntry( uniqueId = player.uniqueId, name = player.name, - displayName = if (data == null) Component.text(player.name) else formatTablistName(player, data), + displayName = if (data == null) Component.text(player.name) else PlayerDisplayFormatter.formatTablistName(data, player.name), priority = data?.priority ?: 0, profileProperties = player.playerProfile.properties.map { property -> ProfileProperty(property.name, property.value, property.signature) }, latency = player.ping, @@ -161,9 +161,6 @@ class PaperDisplayManager( send(viewer, ClientboundSetPlayerTeamPacket.createRemovePacket(team)) } - private fun formatTablistName(player: Player, data: PrefixesPlayerData): Component = - PlayerDisplayFormatter.formatTablistName(data, player.name, prefixes.config.get().features.displayName) - private fun createListNamePacket(player: Player, name: Component): ClientboundPlayerInfoUpdatePacket { val handle = (player as CraftPlayer).handle val entry = ClientboundPlayerInfoUpdatePacket.Entry( diff --git a/prefixes-paper/src/test/kotlin/app/simplecloud/prefixes/paper/CloudItemStackParserTest.kt b/prefixes-paper/src/test/kotlin/app/simplecloud/prefixes/paper/CloudItemStackParserTest.kt deleted file mode 100644 index e70281e..0000000 --- a/prefixes-paper/src/test/kotlin/app/simplecloud/prefixes/paper/CloudItemStackParserTest.kt +++ /dev/null @@ -1,23 +0,0 @@ -package app.simplecloud.prefixes.paper - -import net.minecraft.SharedConstants -import net.minecraft.server.Bootstrap -import org.incendo.cloud.bukkit.parser.ItemStackParser -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class CloudItemStackParserTest { - @Test - fun `item parser initializes against Paper 26_3`() { - SharedConstants.tryDetectVersion() - Bootstrap.bootStrap() - - assertNotNull(ItemStackParser.itemStackParser()) - val parser = Class.forName("org.incendo.cloud.bukkit.parser.ItemStackParser\$ModernParser") - val field = parser.getDeclaredField("AS_BUKKIT_STACK_METHOD") - field.isAccessible = true - val method = field.get(null) as java.lang.reflect.Method - assertEquals("asBukkitMirror", method.name) - } -} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt index f545ac6..b9d0fb5 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt @@ -73,16 +73,20 @@ class PrefixesDataRegistry(private val platform: PrefixesPlatform) { } private fun createData(id: UUID, override: PrefixesOverride): PrefixesPlayerData { + val prefix = override.prefix ?: Component.empty() + val suffix = override.suffix ?: Component.empty() val color = override.color ?: NamedTextColor.WHITE val displayName = miniMessage.deserialize( override.displayName ?: PrefixesConstants.DEFAULT_DISPLAY_NAME, + Placeholder.component("prefix", prefix), + Placeholder.component("suffix", suffix), Placeholder.styling("color", color), Placeholder.unparsed("playername", platform.getPlayerName(id)) ) return PrefixesPlayerData( - override.prefix ?: Component.empty(), - override.suffix ?: Component.empty(), + prefix, + suffix, color, displayName, override.chatFormat ?: PrefixesConstants.DEFAULT_CHAT_FORMAT, diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt index 019cc11..e21f673 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PlayerDisplayFormatter.kt @@ -11,9 +11,9 @@ object PlayerDisplayFormatter { return if (displayNameEnabled) data.displayName else Component.text(playerName) } - fun formatTablistName(data: PrefixesPlayerData, playerName: String, displayNameEnabled: Boolean): Component { + fun formatTablistName(data: PrefixesPlayerData, playerName: String): Component { return data.prefix - .append(formatDisplayName(data, playerName, displayNameEnabled)) + .append(Component.text(playerName, data.color)) .append(data.suffix) } diff --git a/prefixes-shared/src/main/resources/config.yml b/prefixes-shared/src/main/resources/config.yml index 1493948..fe298df 100644 --- a/prefixes-shared/src/main/resources/config.yml +++ b/prefixes-shared/src/main/resources/config.yml @@ -22,11 +22,12 @@ features: # Format chat messages using the group's chat-format. chat: true - # Apply prefixes, suffixes, colors and sorting to the tab list. + # Show the prefix, the colored player name and the suffix in the tab list, + # sorted by group priority. tablist: true - # Apply the group's configured display-name. - # When disabled, the player's normal Minecraft name is used. + # Show the group's display-name above the player's head. + # When disabled, the normal Minecraft name tag is shown instead. display-name: true # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @@ -60,6 +61,8 @@ sync: # Only used when general.source is config. # # Placeholders in display-name: +# Group prefix +# Group suffix # Group color # Normal Minecraft player name # @@ -84,7 +87,7 @@ groups: suffix: '' # Used by the placeholder and player name coloring. color: '<#DC2626>' - # Formatted player name. + # Name shown above the player's head, e.g. ''. display-name: '' # Format used when features.chat is enabled. chat-format: ' <#475569>ยป <#F8FAFC>' From ccd18f65bc1674493c271ada593dc8d1b46df22f Mon Sep 17 00:00:00 2001 From: Janis Date: Thu, 8 Oct 2026 02:16:25 +0200 Subject: [PATCH 13/22] fix: color parsing --- build.gradle.kts | 2 +- .../app/simplecloud/prefixes/shared/Prefixes.kt | 7 +------ .../prefixes/shared/PrefixesConstants.kt | 4 +--- .../prefixes/shared/sync/PrefixesSync.kt | 2 +- .../prefixes/shared/utilities/ColorParser.kt | 17 +++++------------ 5 files changed, 9 insertions(+), 23 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9cf7e2e..0b4f395 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { allprojects { group = "app.simplecloud.plugin" - version = "1.1.0-beta.1" + version = "1.1.0-beta.2" repositories { mavenCentral() diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index b3e169c..2c8a8f8 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -16,13 +16,12 @@ import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import app.simplecloud.prefixes.shared.sync.PrefixesSync import app.simplecloud.prefixes.shared.utilities.ColorParser import kotlinx.coroutines.cancelChildren -import org.slf4j.LoggerFactory import java.io.File import java.util.concurrent.CopyOnWriteArrayList class Prefixes(private val platform: PrefixesPlatform) { - private val logger = LoggerFactory.getLogger(PrefixesConstants.LOGGER_NAME) + private val logger = PrefixesConstants.LOGGER private val listeners = CopyOnWriteArrayList() val config = createConfig() @@ -65,10 +64,6 @@ class Prefixes(private val platform: PrefixesPlatform) { }.isSuccess } - fun getPlatform(): PrefixesPlatform { - return platform - } - private fun validateConfig() { registry.validateSource() config.get().groups diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt index 44345ae..97209b0 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt @@ -10,8 +10,6 @@ import org.slf4j.LoggerFactory object PrefixesConstants { - const val LOGGER_NAME = "simplecloud-prefixes" - const val CONFIG_SOURCE = "config" const val CURRENT_SYNC_SOURCE = "CURRENT" @@ -20,7 +18,7 @@ object PrefixesConstants { const val DEFAULT_DISPLAY_NAME = "" const val DEFAULT_CHAT_FORMAT = " <#475569>ยป <#F8FAFC>" - val LOGGER: Logger = LoggerFactory.getLogger(LOGGER_NAME) + val LOGGER: Logger = LoggerFactory.getLogger("simplecloud-prefixes") val SCOPE = CoroutineScope( CoroutineName("simplecloud-prefixes") + diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt index 484ef6f..367e29d 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt @@ -13,7 +13,7 @@ class PrefixesSync( config: ConfigurationFactory ) { - private val logger = LoggerFactory.getLogger(PrefixesConstants.LOGGER_NAME) + private val logger = PrefixesConstants.LOGGER private val connection = createNatsConnection() private val api = CloudApi.create() private val subjects = PrefixesSubjects(api.networkId) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt index a7c94b5..7cb63fa 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ColorParser.kt @@ -1,26 +1,19 @@ package app.simplecloud.prefixes.shared.utilities -import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor import net.kyori.adventure.text.minimessage.MiniMessage object ColorParser { fun parse(color: String): TextColor? { - val trimmed = color.trim() - if (trimmed.isEmpty()) return null + val value = color.trim().removePrefix("<").removeSuffix(">").lowercase() + if (value.isEmpty()) return null - if (trimmed.startsWith("#")) { - return TextColor.fromHexString(trimmed) + if (value.startsWith("#")) { + return TextColor.fromHexString(value) } - if (trimmed.startsWith("<#") && trimmed.endsWith(">")) { - return TextColor.fromHexString(trimmed.substring(1, trimmed.length - 1)) - } - - val named = NamedTextColor.NAMES.value(trimmed.lowercase()) - if (named != null) return named - return MiniMessage.miniMessage().deserialize(trimmed).color() + return MiniMessage.miniMessage().deserialize("<$value>.").color() } fun serialize(color: TextColor): String = "<${color.asHexString().uppercase()}>" From 87d4b5fb8f25d5b0feb65267d04090ba86826c2d Mon Sep 17 00:00:00 2001 From: Janis Date: Thu, 8 Oct 2026 02:44:07 +0200 Subject: [PATCH 14/22] feat: better coroutine exception handling --- .../prefixes/minestom/PrefixesMinestom.kt | 3 ++- .../minestom/display/MinestomDisplayManager.kt | 3 ++- .../paper/display/PaperDisplayManager.kt | 3 ++- .../prefixes/shared/PrefixesConstants.kt | 18 ++++++++++++++++-- .../prefixes/shared/api/PrefixesApiImpl.kt | 10 +++++----- .../utilities/PrefixesCoroutineDetails.kt | 12 ++++++++++++ 6 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PrefixesCoroutineDetails.kt diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt index 0dfd52c..80c6bfd 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt @@ -12,6 +12,7 @@ import app.simplecloud.prefixes.minestom.platform.MinestomPrefixesListener import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.command.PrefixesCommand import app.simplecloud.prefixes.shared.PrefixesConstants +import app.simplecloud.prefixes.shared.utilities.PrefixesCoroutineDetails import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -82,7 +83,7 @@ class PrefixesMinestom internal constructor( ) sync.publisher.publishTablistRequest() - PrefixesConstants.SCOPE.launch { + PrefixesConstants.SCOPE.launch(PrefixesCoroutineDetails(null, "publish tablist")) { while (isActive) { delay(30.seconds) MinecraftServer.getSchedulerManager().scheduleNextTick { manager.publishAll() } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt index 9c1fa5f..913f9e5 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt @@ -7,6 +7,7 @@ import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistGameMode import app.simplecloud.prefixes.shared.PrefixesConstants +import app.simplecloud.prefixes.shared.utilities.PrefixesCoroutineDetails import app.simplecloud.prefixes.shared.utilities.PlayerDisplayFormatter import kotlinx.coroutines.future.await import kotlinx.coroutines.launch @@ -31,7 +32,7 @@ class MinestomDisplayManager( private val entries = ConcurrentHashMap() fun updatePlayer(player: Player) { - PrefixesConstants.SCOPE.launch { + PrefixesConstants.SCOPE.launch(PrefixesCoroutineDetails("${player.username} (${player.uuid})", "update player")) { val group = prefixes.api.getPrimaryGroup(player.uuid).await() MinecraftServer.getSchedulerManager().scheduleNextTick { if (!player.isOnline) return@scheduleNextTick diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt index 3c4ec16..71981cd 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt @@ -7,6 +7,7 @@ import app.simplecloud.prefixes.shared.sync.tablist.ProfileProperty import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import app.simplecloud.prefixes.shared.sync.tablist.TablistGameMode import app.simplecloud.prefixes.shared.PrefixesConstants +import app.simplecloud.prefixes.shared.utilities.PrefixesCoroutineDetails import app.simplecloud.prefixes.shared.utilities.PlayerDisplayFormatter import com.destroystokyo.paper.ClientOption import io.papermc.paper.adventure.PaperAdventure @@ -45,7 +46,7 @@ class PaperDisplayManager( } fun updatePlayer(player: Player) { - PrefixesConstants.SCOPE.launch { + PrefixesConstants.SCOPE.launch(PrefixesCoroutineDetails("${player.name} (${player.uniqueId})", "update player")) { val group = prefixes.api.getPrimaryGroup(player.uniqueId).await() Bukkit.getScheduler().runTask(plugin, Runnable { if (!player.isOnline) return@Runnable diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt index 97209b0..df7e8a7 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/PrefixesConstants.kt @@ -1,5 +1,6 @@ package app.simplecloud.prefixes.shared +import app.simplecloud.prefixes.shared.utilities.PrefixesCoroutineDetails import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope @@ -24,8 +25,21 @@ object PrefixesConstants { CoroutineName("simplecloud-prefixes") + SupervisorJob() + Dispatchers.IO + - CoroutineExceptionHandler { _, exception -> - LOGGER.error("An error occurred in a coroutine", exception) + CoroutineExceptionHandler { context, exception -> + val details = context[PrefixesCoroutineDetails] + if (details == null) { + LOGGER.error("An error occurred in a coroutine!", exception) + return@CoroutineExceptionHandler + } + + LOGGER.error( + """ + An error occurred in a coroutine! + - Player: ${details.player ?: "N/A"} + - Launch reason: ${details.reason} + """.trimIndent(), + exception + ) } ) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt index 1fdda43..6c82b39 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/api/PrefixesApiImpl.kt @@ -102,7 +102,7 @@ class PrefixesApiImpl( override fun reset(id: UUID) { dataRegistry.reset(id) - notifyChange(id) + notifyDataChange(id) } override fun reset(targets: Audience) { @@ -113,7 +113,7 @@ class PrefixesApiImpl( val viewerIds = AudienceResolver.resolveIds(viewers) AudienceResolver.resolveIds(targets).forEach { id -> viewerIds.forEach { viewer -> dataRegistry.reset(id, viewer) } - notifyChange(id) + notifyDataChange(id) } } @@ -134,7 +134,7 @@ class PrefixesApiImpl( private fun set(id: UUID, override: PrefixesOverride) { if (!platform.isOnline(id)) return dataRegistry.set(id, override) - notifyChange(id) + notifyDataChange(id) } private fun set(id: UUID, override: PrefixesOverride, viewers: Audience) { @@ -144,14 +144,14 @@ class PrefixesApiImpl( private fun set(id: UUID, override: PrefixesOverride, viewers: List) { if (!platform.isOnline(id)) return viewers.forEach { viewer -> dataRegistry.set(id, viewer, override) } - notifyChange(id) + notifyDataChange(id) } private suspend fun findGroup(name: String): PrefixesGroup? { return getGroups().await().firstOrNull { group -> group.name.equals(name, ignoreCase = true) } } - private fun notifyChange(id: UUID) { + private fun notifyDataChange(id: UUID) { listeners.forEach { listener -> listener.onPlayerDataChange(id) } } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PrefixesCoroutineDetails.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PrefixesCoroutineDetails.kt new file mode 100644 index 0000000..9c424a0 --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/PrefixesCoroutineDetails.kt @@ -0,0 +1,12 @@ +package app.simplecloud.prefixes.shared.utilities + +import kotlin.coroutines.AbstractCoroutineContextElement +import kotlin.coroutines.CoroutineContext + +data class PrefixesCoroutineDetails( + val player: String?, + val reason: String +) : AbstractCoroutineContextElement(PrefixesCoroutineDetails) { + + companion object Key : CoroutineContext.Key +} \ No newline at end of file From e3f7b1f2fcb2798fc2982c77dba9cf3c42f3dd20 Mon Sep 17 00:00:00 2001 From: Janis Date: Thu, 8 Oct 2026 02:56:17 +0200 Subject: [PATCH 15/22] refactor: use packets for scoreboards in minestom --- .../minestom/display/MinestomPlayerTeam.kt | 22 ------------------- .../minestom/display/MinestomTablist.kt | 10 ++++++--- .../src/main/resources/paper-plugin.yml | 1 + 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt index 493741b..c6deeb7 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomPlayerTeam.kt @@ -4,7 +4,6 @@ import app.simplecloud.prefixes.shared.utilities.PriorityFormatter import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor -import net.minestom.server.MinecraftServer import net.minestom.server.color.TeamColor import net.minestom.server.network.packet.server.play.TeamsPacket import net.minestom.server.network.packet.server.play.TeamsPacket.CollisionRule @@ -12,27 +11,6 @@ import net.minestom.server.network.packet.server.play.TeamsPacket.NameTagVisibil object MinestomPlayerTeam { - fun register(name: String, priority: Int) { - val manager = MinecraftServer.getTeamManager() - val teamName = createName(name, priority) - - manager.deleteTeam(teamName) - - val team = manager - .createBuilder(teamName) - .prefix(Component.empty()) - .suffix(Component.empty()) - .teamColor(toTeamColor(NamedTextColor.WHITE)) - .nameTagVisibility(NameTagVisibility.ALWAYS) - .build() - - team.addMember(name) - } - - fun unregister(name: String, priority: Int) { - MinecraftServer.getTeamManager().deleteTeam(createName(name, priority)) - } - fun createPacket( name: String, priority: Int, diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt index 72b7851..87f58e2 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt @@ -8,6 +8,7 @@ import net.minestom.server.entity.Player import net.minestom.server.network.packet.server.play.PlayerInfoRemovePacket import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket.Action +import net.minestom.server.network.packet.server.play.TeamsPacket import net.minestom.server.utils.PacketSendingUtils import java.util.EnumSet import java.util.UUID @@ -25,8 +26,8 @@ class MinestomTablist { PacketSendingUtils.broadcastPlayPacket(createInfoPacket(listOf(entry))) } if (previous == null || previous.name != entry.name || previous.priority != entry.priority) { - if (previous != null) MinestomPlayerTeam.unregister(previous.name, previous.priority) - MinestomPlayerTeam.register(entry.name, entry.priority) + if (previous != null) PacketSendingUtils.broadcastPlayPacket(MinestomPlayerTeam.removePacket(createTeam(previous))) + PacketSendingUtils.broadcastPlayPacket(createTeam(entry)) } } @@ -48,15 +49,18 @@ class MinestomTablist { fun sync(player: Player) { val visible = entries.values.map(SourcedTablistEntry::entry).filter { entry -> !isOnline(entry.uniqueId) } visible.chunked(PlayerInfoUpdatePacket.MAX_ENTRIES).forEach { batch -> player.sendPacket(createInfoPacket(batch)) } + visible.forEach { entry -> player.sendPacket(createTeam(entry)) } } private fun hide(entry: TablistEntry) { - MinestomPlayerTeam.unregister(entry.name, entry.priority) + PacketSendingUtils.broadcastPlayPacket(MinestomPlayerTeam.removePacket(createTeam(entry))) if (!isOnline(entry.uniqueId)) { PacketSendingUtils.broadcastPlayPacket(PlayerInfoRemovePacket(entry.uniqueId)) } } + private fun createTeam(entry: TablistEntry): TeamsPacket = MinestomPlayerTeam.createPacket(entry.name, entry.priority, hideNameTag = false) + private fun getUpdateActions(): EnumSet = EnumSet.of( Action.ADD_PLAYER, Action.UPDATE_LISTED, diff --git a/prefixes-paper/src/main/resources/paper-plugin.yml b/prefixes-paper/src/main/resources/paper-plugin.yml index 3230bed..9eb63ce 100644 --- a/prefixes-paper/src/main/resources/paper-plugin.yml +++ b/prefixes-paper/src/main/resources/paper-plugin.yml @@ -1,5 +1,6 @@ name: simplecloud-prefixes version: ${version} +description: Prefixes, suffixes and colors for your SimpleCloud network api-version: 26.3 main: app.simplecloud.prefixes.paper.PrefixesPaper From 992ef1cbca50bba5594b388ceb559fb71ba0a771 Mon Sep 17 00:00:00 2001 From: Janis Date: Thu, 8 Oct 2026 06:59:50 +0200 Subject: [PATCH 16/22] feat: improve minestom builder --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 3 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../simplecloud/prefixes/api/PrefixesApi.kt | 12 ++-- .../prefixes/api/group/GroupProvider.kt | 4 +- .../prefixes/api/group/PrefixesGroup.kt | 8 +-- .../prefixes/api/group/PrefixesPlayerData.kt | 2 +- .../prefixes/minestom/PrefixesMinestom.kt | 49 +++++++++-------- .../minestom/PrefixesMinestomBuilder.kt | 55 +++++-------------- .../permission/MinestomPermissions.kt | 13 ++--- prefixes-paper/build.gradle.kts | 11 ++-- .../src/main/resources/paper-plugin.yml | 2 +- prefixes-shared/build.gradle.kts | 1 - .../simplecloud/prefixes/shared/Prefixes.kt | 5 +- .../shared/config/DefaultConfigInstaller.kt | 7 +-- .../prefixes/shared/config/MessageConfig.kt | 3 +- .../prefixes/shared/sync/PrefixesSync.kt | 1 - .../shared/utilities/config/DefaultConfigs.kt | 7 --- .../simplecloud/prefixes/v1/prefixes.proto | 2 +- prefixes-shared/src/main/resources/config.yml | 7 +-- .../src/main/resources/messages.yml | 25 +++++++++ 21 files changed, 104 insertions(+), 117 deletions(-) delete mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/config/DefaultConfigs.kt create mode 100644 prefixes-shared/src/main/resources/messages.yml diff --git a/build.gradle.kts b/build.gradle.kts index 0b4f395..cceed7c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { allprojects { group = "app.simplecloud.plugin" - version = "1.1.0-beta.2" + version = "1.1.0-beta.3" repositories { mavenCentral() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fccadda..3a9696c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,6 +39,7 @@ simplecloud-plugin-api = { module = "app.simplecloud.plugin:plugin-shared", vers adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure" } adventure-text-minimessage = { module = "net.kyori:adventure-text-minimessage", version.ref = "adventure" } adventure-text-serializer-gson = { module = "net.kyori:adventure-text-serializer-gson", version.ref = "adventure" } +adventure-text-serializer-legacy = { module = "net.kyori:adventure-text-serializer-legacy", version.ref = "adventure" } cloud-command-core = { module = "org.incendo:cloud-core", version.ref = "cloud-command" } cloud-command-coroutines = { module = "org.incendo:cloud-kotlin-coroutines", version.ref = "cloud-command" } @@ -62,7 +63,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } [bundles] configurate = ["configurate-kotlin", "configurate-yaml"] -adventure = ["adventure-api", "adventure-text-minimessage", "adventure-text-serializer-gson"] +adventure = ["adventure-api", "adventure-text-minimessage", "adventure-text-serializer-gson", "adventure-text-serializer-legacy"] cloud-command = ["cloud-command-core", "cloud-command-coroutines"] [plugins] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b589e64..b2f8f63 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists \ No newline at end of file diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt index 17b85a5..dd02b8c 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/PrefixesApi.kt @@ -10,14 +10,14 @@ import java.util.UUID import java.util.concurrent.CompletableFuture /** - * Main entrypoint to interacting with prefixes. + * Entrypoint to interacting with prefixes. */ interface PrefixesApi { /** Returns all registered groups ordered by priority. */ fun getGroups(): CompletableFuture> - /** Returns the highest-priority group of the player [id], or null if none matches. */ + /** Returns the primary group of the player [id], or null if none matches. */ fun getPrimaryGroup(id: UUID): CompletableFuture /** Returns the prefix data everyone sees for the player [id]. */ @@ -29,25 +29,25 @@ interface PrefixesApi { /** Sets the [prefix] of the player [id] for everyone. */ fun setPrefix(id: UUID, prefix: Component) - /** Sets the [prefix] of the player [id], visible only to [viewers]. */ + /** Sets the [prefix] of the player [id] for [viewers]. */ fun setPrefix(id: UUID, prefix: Component, viewers: Audience) /** Sets the [suffix] of the player [id] for everyone. */ fun setSuffix(id: UUID, suffix: Component) - /** Sets the [suffix] of the player [id], visible only to [viewers]. */ + /** Sets the [suffix] of the player [id] for [viewers]. */ fun setSuffix(id: UUID, suffix: Component, viewers: Audience) /** Sets the name [color] of the player [id] for everyone. */ fun setColor(id: UUID, color: TextColor) - /** Sets the name [color] of the player [id], visible only to [viewers]. */ + /** Sets the name [color] of the player [id] for [viewers]. */ fun setColor(id: UUID, color: TextColor, viewers: Audience) /** Sets the tablist [priority] of the player [id] for everyone, higher is listed further up. */ fun setPriority(id: UUID, priority: Int) - /** Sets the tablist [priority] of the player [id], visible only to [viewers]. */ + /** Sets the tablist [priority] of the player [id] for [viewers]. */ fun setPriority(id: UUID, priority: Int, viewers: Audience) /** Shows the player [id] with the group named [group] for everyone, completes with `false` if it does not exist. */ diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt index b4b90af..914b3f3 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/GroupProvider.kt @@ -4,7 +4,7 @@ import java.util.UUID import java.util.concurrent.CompletableFuture /** - * A source of prefix groups, e.g. the config or LuckPerms. + * A source provider of prefix groups, e.g. the config or LuckPerms. */ interface GroupProvider { @@ -14,7 +14,7 @@ interface GroupProvider { /** Returns all groups of this provider. */ fun getGroups(): CompletableFuture> - /** Returns the highest-priority group of the player [id], or null if none matches. */ + /** Returns the primary group of the player [id], or null if none matches. */ fun getGroup(id: UUID): CompletableFuture /** Adds [group] to this provider, completes with `false` if it already exists. */ diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt index 7a5e638..68f549e 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesGroup.kt @@ -6,14 +6,14 @@ import java.util.UUID import java.util.concurrent.CompletableFuture /** - * A group that defines how its players are shown. + * Represents a prefixes group. */ interface PrefixesGroup { /** The name of this group. */ val name: String - /** The priority of this group, higher wins and is listed further up. */ + /** The priority of this group. */ val priority: Int /** The permission required for this group, or an empty string if none. */ @@ -28,10 +28,10 @@ interface PrefixesGroup { /** The name color of this group. */ val color: TextColor - /** The MiniMessage format of the display name. */ + /** The display name of this group. */ val displayName: String - /** The MiniMessage format of chat messages. */ + /** The chat format of this group. */ val chatFormat: String /** Returns whether the player [id] is a member of this group. */ diff --git a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesPlayerData.kt b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesPlayerData.kt index d39c602..ad92fac 100644 --- a/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesPlayerData.kt +++ b/prefixes-api/src/main/kotlin/app/simplecloud/prefixes/api/group/PrefixesPlayerData.kt @@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component import net.kyori.adventure.text.format.TextColor /** - * The prefixes data from a player. + * The prefix data from a player. */ data class PrefixesPlayerData( val prefix: Component, diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt index 80c6bfd..30157bd 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt @@ -13,9 +13,7 @@ import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.command.PrefixesCommand import app.simplecloud.prefixes.shared.PrefixesConstants import app.simplecloud.prefixes.shared.utilities.PrefixesCoroutineDetails -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import net.luckperms.api.LuckPerms import net.minestom.server.MinecraftServer import net.minestom.server.adventure.audience.Audiences @@ -28,16 +26,15 @@ import java.nio.file.Path import java.util.function.BiPredicate import kotlin.time.Duration.Companion.seconds -class PrefixesMinestom internal constructor( +class PrefixesMinestom private constructor( directory: Path, - luckPerms: LuckPerms?, - groupPermission: BiPredicate?, + permission: BiPredicate?, private val registerCommands: Boolean, - commandPermission: BiPredicate? + luckPerms: LuckPerms? ) { private val node = EventNode.all("simplecloud-prefixes") - private val permissions = MinestomPermissions(groupPermission, commandPermission) + private val permissions = MinestomPermissions(permission) private val platform = MinestomPlatformImpl(directory, permissions.getChecker(), luckPerms) private val prefixes = Prefixes(platform) private val manager = MinestomDisplayManager(prefixes, CustomNamesMinestom.getManager()) @@ -47,7 +44,13 @@ class PrefixesMinestom internal constructor( /** Gets the [PrefixesApi] instance. */ fun getApi(): PrefixesApi = prefixes.api - fun init(): PrefixesMinestom { + fun shutdown() { + MinecraftServer.getGlobalEventHandler().removeChild(node) + prefixes.shutdown() + CustomNamesMinestom.shutdown() + } + + private fun init() { CustomNamesMinestom.init() prefixes.startup() prefixes.addListener(MinestomPrefixesListener(prefixes, manager, tablist)) @@ -55,21 +58,14 @@ class PrefixesMinestom internal constructor( PlayerListener(prefixes, manager, tablist).register(node) MinecraftServer.getGlobalEventHandler().addChild(node) - if (prefixes.config.get().general.source.equals(PrefixesConstants.CONFIG_SOURCE, ignoreCase = true) && !permissions.hasGroupPermission) { - logger.warn("Source Type is set to '${PrefixesConstants.CONFIG_SOURCE}', but no group permission check was registered!") + if (prefixes.config.get().general.source.equals(PrefixesConstants.CONFIG_SOURCE, ignoreCase = true) && !permissions.hasCheck) { + logger.warn("Source Type is set to '${PrefixesConstants.CONFIG_SOURCE}', but no permission check was registered!") } registerSync() registerLuckPermsListener() registerCommands() prefixes.api.refreshAll() - return this - } - - fun shutdown() { - MinecraftServer.getGlobalEventHandler().removeChild(node) - prefixes.shutdown() - CustomNamesMinestom.shutdown() } private fun registerSync() { @@ -109,12 +105,19 @@ class PrefixesMinestom internal constructor( } companion object { - /** - * Creates a [PrefixesMinestomBuilder]. - * - * @param directory The directory the config files are created in - */ + /** Creates a [PrefixesMinestomBuilder] config files are created in [directory]. */ @JvmStatic fun builder(directory: Path): PrefixesMinestomBuilder = PrefixesMinestomBuilder(directory) + + internal fun start( + directory: Path, + permission: BiPredicate?, + registerCommands: Boolean, + luckPerms: LuckPerms? + ): PrefixesMinestom { + val instance = PrefixesMinestom(directory, permission, registerCommands, luckPerms) + instance.init() + return instance + } } } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt index 31d5cf2..6b75e7a 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestomBuilder.kt @@ -8,50 +8,25 @@ import java.util.function.BiPredicate /** * Builds a [PrefixesMinestom] instance. */ -class PrefixesMinestomBuilder internal constructor(private val directory: Path) { - - private var luckPerms: LuckPerms? = null - private var groupPermission: BiPredicate? = null - private var registerCommands = true - private var commandPermission: BiPredicate? = null +@ConsistentCopyVisibility +data class PrefixesMinestomBuilder internal constructor( + private val directory: Path, + private val permission: BiPredicate? = null, + private val registerCommands: Boolean = true, + private val luckPerms: LuckPerms? = null +) { /** - * Sets the LuckPerms instance used when the group source is `luckperms`. + * Sets the check that decides which group a player gets and whether they may use the prefixes commands. */ - fun luckPerms(luckPerms: LuckPerms): PrefixesMinestomBuilder { - this.luckPerms = luckPerms - return this - } + fun permission(check: BiPredicate): PrefixesMinestomBuilder = copy(permission = check) - /** - * Sets the check that decides which group a player gets when the group source is `config`. - */ - fun groupPermission(check: BiPredicate): PrefixesMinestomBuilder { - this.groupPermission = check - return this - } - - /** - * Sets whether the prefixes commands are registered. - */ - fun registerCommands(enabled: Boolean): PrefixesMinestomBuilder { - this.registerCommands = enabled - return this - } + /** Sets whether the prefixes commands are registered. */ + fun registerCommands(enabled: Boolean): PrefixesMinestomBuilder = copy(registerCommands = enabled) - /** - * Sets the check that decides whether a player may use a prefixes command. - * The console is always allowed. Without a check, only players with permission level 4 are allowed. - */ - fun commandPermission(check: BiPredicate): PrefixesMinestomBuilder { - this.commandPermission = check - return this - } + /** Sets the [LuckPerms] instance, only needed when it is not available through the LuckPerms provider. */ + fun luckPerms(luckPerms: LuckPerms): PrefixesMinestomBuilder = copy(luckPerms = luckPerms) - /** - * Creates the instance and starts prefixes. - */ - fun enable(): PrefixesMinestom { - return PrefixesMinestom(directory, luckPerms, groupPermission, registerCommands, commandPermission).init() - } + /** Creates the instance and starts prefixes. */ + fun enable(): PrefixesMinestom = PrefixesMinestom.start(directory, permission, registerCommands, luckPerms) } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt index 70498d3..742c50a 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/permission/MinestomPermissions.kt @@ -8,27 +8,24 @@ import net.minestom.server.entity.Player import java.util.UUID import java.util.function.BiPredicate -class MinestomPermissions( - private val groupPermission: BiPredicate?, - private val commandPermission: BiPredicate? -) { +class MinestomPermissions(private val check: BiPredicate?) { - val hasGroupPermission: Boolean = groupPermission != null + val hasCheck: Boolean = check != null fun hasPermission(sender: CommandSender, permission: String): Boolean { if (permission.isEmpty()) return true if (sender is ConsoleSender) return true if (sender !is Player) return false - if (commandPermission != null) return commandPermission.test(sender, permission) + if (check != null) return check.test(sender, permission) return sender.permissionLevel >= 4 } fun getChecker(): PermissionChecker = PermissionChecker { id, permission -> - if (groupPermission == null) return@PermissionChecker false + if (check == null) return@PermissionChecker false val player = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) ?: return@PermissionChecker false - groupPermission.test(player, permission) + check.test(player, permission) } } diff --git a/prefixes-paper/build.gradle.kts b/prefixes-paper/build.gradle.kts index e605529..fd44af3 100644 --- a/prefixes-paper/build.gradle.kts +++ b/prefixes-paper/build.gradle.kts @@ -3,11 +3,8 @@ plugins { alias(libs.plugins.paperweight.userdev) } -val customNamesRoot = rootProject.layout.projectDirectory.dir("custom-names") -require( - customNamesRoot.dir("custom-names-api/src/main/kotlin").asFile.isDirectory && - customNamesRoot.dir("custom-names-paper/src/main/kotlin").asFile.isDirectory -) { +val customNames = rootProject.layout.projectDirectory.dir("custom-names") +require(customNames.dir("custom-names-paper/src/main/kotlin").asFile.isDirectory) { "The custom-names submodule is missing. Run `git submodule update --init --recursive`." } @@ -22,8 +19,8 @@ dependencies { sourceSets { main { kotlin { - srcDir(customNamesRoot.dir("custom-names-api/src/main/kotlin")) - srcDir(customNamesRoot.dir("custom-names-paper/src/main/kotlin")) + srcDir(customNames.dir("custom-names-api/src/main/kotlin")) + srcDir(customNames.dir("custom-names-paper/src/main/kotlin")) exclude("**/CustomNamesPlugin.kt") } } diff --git a/prefixes-paper/src/main/resources/paper-plugin.yml b/prefixes-paper/src/main/resources/paper-plugin.yml index 9eb63ce..4b8285b 100644 --- a/prefixes-paper/src/main/resources/paper-plugin.yml +++ b/prefixes-paper/src/main/resources/paper-plugin.yml @@ -1,6 +1,6 @@ name: simplecloud-prefixes version: ${version} -description: Prefixes, suffixes and colors for your SimpleCloud network +description: Prefixes, suffixes and name colors for chat, tab list and name tags. Works with LuckPerms and syncs across your SimpleCloud network api-version: 26.3 main: app.simplecloud.prefixes.paper.PrefixesPaper diff --git a/prefixes-shared/build.gradle.kts b/prefixes-shared/build.gradle.kts index 62a0bc3..b115d33 100644 --- a/prefixes-shared/build.gradle.kts +++ b/prefixes-shared/build.gradle.kts @@ -10,7 +10,6 @@ dependencies { implementation(libs.protobuf.kotlin) implementation(libs.bundles.configurate) implementation(libs.bundles.cloud.command) - testImplementation(libs.luckperms.api) } sourceSets { diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index 2c8a8f8..1a98a7e 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -88,7 +88,10 @@ class Prefixes(private val platform: PrefixesPlatform) { } private fun createMessages(): ConfigurationFactory { - val factory = ConfigurationFactory(File(platform.getDataDirectory(), "messages.yml"), MessageConfig::class.java) + val file = File(platform.getDataDirectory(), "messages.yml") + DefaultConfigInstaller.install(file.toPath(), javaClass.classLoader) + + val factory = ConfigurationFactory(file, MessageConfig::class.java) factory.loadOrCreate(MessageConfig()) return factory } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/DefaultConfigInstaller.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/DefaultConfigInstaller.kt index 7ee656b..21a3161 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/DefaultConfigInstaller.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/DefaultConfigInstaller.kt @@ -5,13 +5,12 @@ import java.nio.file.Path object DefaultConfigInstaller { - private const val CONFIG_RESOURCE = "config.yml" - fun install(target: Path, classLoader: ClassLoader) { if (Files.exists(target)) return - val resource = checkNotNull(classLoader.getResourceAsStream(CONFIG_RESOURCE)) { - "Missing bundled $CONFIG_RESOURCE" + val name = target.fileName.toString() + val resource = checkNotNull(classLoader.getResourceAsStream(name)) { + "Missing bundled $name" } Files.createDirectories(target.parent) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/MessageConfig.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/MessageConfig.kt index c6f3594..e5ee579 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/MessageConfig.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/MessageConfig.kt @@ -3,13 +3,12 @@ package app.simplecloud.prefixes.shared.config import app.simplecloud.plugin.api.shared.config.AbstractMessageConfig import app.simplecloud.plugin.api.shared.config.VersionedConfig import app.simplecloud.prefixes.shared.utilities.config.ConfigVersion -import app.simplecloud.prefixes.shared.utilities.config.DefaultConfigs import org.spongepowered.configurate.objectmapping.ConfigSerializable @ConfigSerializable data class MessageConfig( override val version: Int = ConfigVersion.VERSION, - override val variables: Map = DefaultConfigs.VARIABLES, + override val variables: Map = mapOf("prefix" to "โšก"), val command: CommandMessages = CommandMessages() ) : VersionedConfig, AbstractMessageConfig() diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt index 367e29d..4d2bb44 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt @@ -7,7 +7,6 @@ import app.simplecloud.prefixes.shared.PrefixesConstants import io.nats.client.Connection import io.nats.client.Nats import io.nats.client.Options -import org.slf4j.LoggerFactory class PrefixesSync( config: ConfigurationFactory diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/config/DefaultConfigs.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/config/DefaultConfigs.kt deleted file mode 100644 index 6897060..0000000 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/config/DefaultConfigs.kt +++ /dev/null @@ -1,7 +0,0 @@ -package app.simplecloud.prefixes.shared.utilities.config - -object DefaultConfigs { - - val VARIABLES: Map = mapOf("prefix" to "โšก") - -} diff --git a/prefixes-shared/src/main/proto/simplecloud/prefixes/v1/prefixes.proto b/prefixes-shared/src/main/proto/simplecloud/prefixes/v1/prefixes.proto index bc4c92e..e33c28c 100644 --- a/prefixes-shared/src/main/proto/simplecloud/prefixes/v1/prefixes.proto +++ b/prefixes-shared/src/main/proto/simplecloud/prefixes/v1/prefixes.proto @@ -39,4 +39,4 @@ message TablistEntryRemoveEvent { string player_id = 1; } -message TablistSyncRequest { } +message TablistSyncRequest { } \ No newline at end of file diff --git a/prefixes-shared/src/main/resources/config.yml b/prefixes-shared/src/main/resources/config.yml index fe298df..34940a4 100644 --- a/prefixes-shared/src/main/resources/config.yml +++ b/prefixes-shared/src/main/resources/config.yml @@ -6,7 +6,7 @@ version: 1 # Controls how player groups are resolved. # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ general: - # Where groups are read from. + # Where groups are loaded from. # Available: config, luckperms, or the name of a custom provider. source: config @@ -33,11 +33,10 @@ features: # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # Sync # Shares chat messages and tab list entries across the SimpleCloud network. -# Disabling sync does not disable local formatting. # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ sync: # Master switch for all cross-server communication. - enabled: true + enabled: false # Select which features are synchronized. # The corresponding feature above must also be enabled. @@ -74,8 +73,6 @@ sync: # Formatted display name # Chat message # -# Colors can be a hex code ('#DC2626' or '<#DC2626>') or a color name ('red'). -# If display-name or chat-format is empty, the default format is used. # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ groups: - name: owner diff --git a/prefixes-shared/src/main/resources/messages.yml b/prefixes-shared/src/main/resources/messages.yml new file mode 100644 index 0000000..aa576c5 --- /dev/null +++ b/prefixes-shared/src/main/resources/messages.yml @@ -0,0 +1,25 @@ +# The messages format version. Do not change this value. +version: 1 + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Variables +# Reusable variables that can be used throughout the messages. +# Usage: will be replaced with the defined value. +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +variables: + prefix: 'โšก' + +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# Command Messages +# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +command: + help: + title: ' <#0EA5E9>SimpleCloud Prefixes commands' + entry: '<#E2E8F0>' + + permission: + denied: ' <#DC2626>You do not have permission to use this command.' + + reload: + success: ' <#A3E635>SimpleCloud Prefixes was successfully reloaded.' + failed: ' <#DC2626>SimpleCloud Prefixes could not be reloaded.' From 3bc6adfc6bfb0b692ab987119346de36a9b6b739 Mon Sep 17 00:00:00 2001 From: Janis Date: Thu, 8 Oct 2026 18:25:13 +0200 Subject: [PATCH 17/22] feat: enable configuration cache --- .github/workflows/build.yml | 14 +++++------ .github/workflows/release.yml | 23 +++++++------------ build.gradle.kts | 1 - gradle.properties | 3 +++ gradle/libs.versions.toml | 2 -- .../src/main/resources/paper-plugin.yml | 4 ---- .../simplecloud/prefixes/shared/Prefixes.kt | 11 ++------- .../prefixes/shared/sync/PrefixesSubjects.kt | 7 +++--- .../prefixes/shared/sync/PrefixesSync.kt | 5 +--- 9 files changed, 23 insertions(+), 47 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81a3e23..966a055 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,12 +2,8 @@ name: Build Pull Request on: pull_request: - branches: - - main - types: - - opened - - synchronize - - reopened + branches: [main] + types: [opened, synchronize, reopened] jobs: build: @@ -20,12 +16,14 @@ jobs: with: submodules: recursive - - name: Set up JDK 25 + - name: Set up JDK uses: actions/setup-java@v6 with: distribution: 'temurin' java-version: '25' - cache: 'gradle' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v6 - name: Make gradlew executable run: chmod +x ./gradlew diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ea8c6b..3da3a76 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Create Release with ShadowJars +name: Release and Publish on: push: @@ -8,7 +8,7 @@ on: jobs: build: - name: Build ShadowJars and Create Release + name: Build and Publish runs-on: ubuntu-latest steps: @@ -18,21 +18,20 @@ jobs: ref: main submodules: recursive - - name: Set up JDK 25 + - name: Set up JDK uses: actions/setup-java@v6 with: distribution: 'temurin' java-version: '25' + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v6 + with: + cache-disabled: true + - name: Make gradlew executable run: chmod +x ./gradlew - - name: Print Gradle Wrapper Properties - run: cat gradle/wrapper/gradle-wrapper.properties - - - name: Clear Gradle Cache - run: rm -rf ~/.gradle/caches && rm -rf ~/.gradle/wrapper - - name: Build ShadowJars run: ./gradlew clean build shadowJar @@ -46,10 +45,6 @@ jobs: id: gradle_version run: echo "GRADLE_VERSION=$(./gradlew properties -q | grep "version:" | awk '{print $2}')" >> $GITHUB_ENV - - name: Get Commit Hash - id: commit_hash - run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - - name: Publish to Modrinth run: ./gradlew modrinth && ./gradlew modrinthSyncBody env: @@ -58,9 +53,7 @@ jobs: - name: Create Release run: | jars=() - # Find JAR files in any submodule's build/libs directory for jar in $(find . -type f -name "*.jar" -path "*/build/libs/*.jar" -not -path "./build/libs/*"); do - # Check if the filename contains a version number (e.g., a dash followed by numbers) if [[ $(basename "$jar") =~ -[0-9]+\.[0-9]+ ]]; then echo "Skipping $jar due to version number" else diff --git a/build.gradle.kts b/build.gradle.kts index cceed7c..7fc4f81 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,7 +34,6 @@ subprojects { testImplementation(rootProject.libs.kotlin.test) compileOnly(rootProject.libs.luckperms.api) - compileOnly(rootProject.libs.simplecloud.api) } kotlin { diff --git a/gradle.properties b/gradle.properties index 7fc6f1f..a4bd888 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,4 @@ kotlin.code.style=official +org.gradle.configuration-cache=true +org.gradle.caching=true +org.gradle.parallel=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3a9696c..a3a6add 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,6 @@ minotaur = "2.9.0" paperweight = "2.0.0-beta.24" protobuf = "0.10.0" -simplecloud-api = "0.1.0-platform.58-dev.5.1-fe01ce5" simplecloud-plugin-api = "0.0.1-platform.1778966459069-b1e45e2" paper = "26.3.build.+" @@ -33,7 +32,6 @@ slf4j = "2.0.20" kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } -simplecloud-api = { module = "app.simplecloud.api:api", version.ref = "simplecloud-api" } simplecloud-plugin-api = { module = "app.simplecloud.plugin:plugin-shared", version.ref = "simplecloud-plugin-api" } adventure-api = { module = "net.kyori:adventure-api", version.ref = "adventure" } diff --git a/prefixes-paper/src/main/resources/paper-plugin.yml b/prefixes-paper/src/main/resources/paper-plugin.yml index 4b8285b..36539b4 100644 --- a/prefixes-paper/src/main/resources/paper-plugin.yml +++ b/prefixes-paper/src/main/resources/paper-plugin.yml @@ -7,10 +7,6 @@ main: app.simplecloud.prefixes.paper.PrefixesPaper dependencies: server: - simplecloud-api: - load: BEFORE - required: false - join-classpath: true LuckPerms: load: BEFORE required: false diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index 1a98a7e..9c05b23 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -111,7 +111,7 @@ class Prefixes(private val platform: PrefixesPlatform) { if (!config.get().sync.enabled) return null if (!isSimpleCloudAvailable()) { - logger.warn("Sync is enabled in the config, but SimpleCloud was not found on this server.") + logger.warn("Sync is enabled in the config, but this Server is not running in a simplecloud network.") return null } @@ -122,12 +122,5 @@ class Prefixes(private val platform: PrefixesPlatform) { }.getOrNull() } - private fun isSimpleCloudAvailable(): Boolean { - return try { - Class.forName("app.simplecloud.api.CloudApi") - true - } catch (_: ClassNotFoundException) { - false - } - } + private fun isSimpleCloudAvailable(): Boolean = System.getenv("SIMPLECLOUD_UNIQUE_ID") != null } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt index 3dcccab..3aa99d0 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSubjects.kt @@ -1,12 +1,11 @@ package app.simplecloud.prefixes.shared.sync -import app.simplecloud.api.runtime.SimpleCloudRuntime import app.simplecloud.prefixes.shared.PrefixesConstants class PrefixesSubjects( - networkId: String, - private val origin: String = SimpleCloudRuntime.groupName().ifBlank { SimpleCloudRuntime.serverId() }, - serverId: String = SimpleCloudRuntime.serverId() + networkId: String = System.getenv().getOrDefault("SIMPLECLOUD_NETWORK_ID", "default"), + serverId: String = System.getenv().getOrDefault("SIMPLECLOUD_UNIQUE_ID", ""), + private val origin: String = System.getenv().getOrDefault("SIMPLECLOUD_GROUP", "").ifBlank { serverId } ) { private val root = "$networkId.prefixes" diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt index 4d2bb44..2563347 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt @@ -1,6 +1,5 @@ package app.simplecloud.prefixes.shared.sync -import app.simplecloud.api.CloudApi import app.simplecloud.plugin.api.shared.config.ConfigurationFactory import app.simplecloud.prefixes.shared.config.PrefixesConfig import app.simplecloud.prefixes.shared.PrefixesConstants @@ -14,8 +13,7 @@ class PrefixesSync( private val logger = PrefixesConstants.LOGGER private val connection = createNatsConnection() - private val api = CloudApi.create() - private val subjects = PrefixesSubjects(api.networkId) + private val subjects = PrefixesSubjects() val publisher = SyncPublisher(connection, subjects, config) val subscriber = SyncSubscriber(connection, subjects, config) @@ -25,7 +23,6 @@ class PrefixesSync( logger.info("Shutdown prefixes sync...") subscriber.close() connection.close() - api.close() }.onFailure { throwable -> logger.error("Failed to shutdown sync", throwable) }.onSuccess { From dbf40656ba92b02e25dd6fb388786fcf25fc2324 Mon Sep 17 00:00:00 2001 From: Janis Date: Sat, 10 Oct 2026 23:31:51 +0200 Subject: [PATCH 18/22] feat: folia (#20) * feat: folia * chore: update custom-names --- README.md | 2 +- custom-names | 2 +- gradle.properties | 1 + gradle/libs.versions.toml | 2 +- .../prefixes/minestom/PrefixesMinestom.kt | 17 ++-- .../display/MinestomDisplayManager.kt | 11 ++- .../minestom/display/MinestomTablist.kt | 85 ++++++------------- .../minestom/listener/LuckPermsListener.kt | 40 --------- .../minestom/listener/PlayerListener.kt | 11 +-- .../platform/MinestomPrefixesListener.kt | 2 +- prefixes-paper/build.gradle.kts | 11 +-- .../prefixes/paper/PrefixesPaper.kt | 15 +--- .../paper/display/PaperDisplayManager.kt | 53 +++++------- .../prefixes/paper/display/PaperTablist.kt | 81 +++++------------- .../paper/listener/LuckPermsListener.kt | 46 ---------- .../prefixes/paper/listener/PlayerListener.kt | 2 +- .../paper/platform/PaperPrefixesListener.kt | 6 +- .../src/main/resources/paper-plugin.yml | 1 + .../simplecloud/prefixes/shared/Prefixes.kt | 12 ++- .../shared/command/PrefixesCommand.kt | 34 ++------ .../prefixes/shared/config/PrefixesConfig.kt | 2 +- .../shared/data/PrefixesDataRegistry.kt | 2 - .../group/config/ConfigGroupProvider.kt | 18 ++-- .../shared/group/luckperms/LuckPermsGroup.kt | 16 ++-- .../group/luckperms/LuckPermsGroupProvider.kt | 9 +- .../group/luckperms/LuckPermsListener.kt | 27 ++++++ .../shared/sync/tablist/SyncedTablist.kt | 66 ++++++++++++++ .../shared/utilities/ComponentSerializer.kt | 8 +- 28 files changed, 240 insertions(+), 342 deletions(-) delete mode 100644 prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/LuckPermsListener.kt delete mode 100644 prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/LuckPermsListener.kt create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsListener.kt create mode 100644 prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SyncedTablist.kt diff --git a/README.md b/README.md index 2bc6a9f..fc0bdc4 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ A Paper plugin and Minestom library for prefixes, suffixes and name colors in ch - [x] **Network Sync**: Share chat and tab list across your SimpleCloud network. - [x] **MiniMessage Formats**: Fully customizable with placeholders. - [x] **Developer API**: Set prefixes per player or even per viewer, e.g. for teams or friends. -- [x] **Supported Software**: Paper plugin (and forks) or Minestom library. +- [x] **Supported Software**: Paper(and forks) and Minestom as a Library. ## Dependency diff --git a/custom-names b/custom-names index 1a4642c..6dfdd93 160000 --- a/custom-names +++ b/custom-names @@ -1 +1 @@ -Subproject commit 1a4642c476a052b2e24a8db45a84fa3d56821d40 +Subproject commit 6dfdd937754769942ab9a9312c6ea191389f9e0d diff --git a/gradle.properties b/gradle.properties index a4bd888..5f59712 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,3 +2,4 @@ kotlin.code.style=official org.gradle.configuration-cache=true org.gradle.caching=true org.gradle.parallel=true +org.gradle.tooling.parallel=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a3a6add..c645a78 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.4.20" +kotlin = "2.4.21" kotlinx-coroutines = "1.11.0" shadow = "9.6.1" diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt index 30157bd..284382e 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt @@ -1,10 +1,10 @@ package app.simplecloud.prefixes.minestom import app.simplecloud.prefixes.api.PrefixesApi +import app.simplecloud.prefixes.api.PrefixesApiProvider import app.simplecloud.prefixes.minestom.command.PrefixesMinestomSenderMapper import app.simplecloud.prefixes.minestom.display.MinestomDisplayManager import app.simplecloud.prefixes.minestom.display.MinestomTablist -import app.simplecloud.prefixes.minestom.listener.LuckPermsListener import app.simplecloud.prefixes.minestom.listener.PlayerListener import app.simplecloud.prefixes.minestom.permission.MinestomPermissions import app.simplecloud.prefixes.minestom.platform.MinestomPlatformImpl @@ -33,7 +33,7 @@ class PrefixesMinestom private constructor( luckPerms: LuckPerms? ) { - private val node = EventNode.all("simplecloud-prefixes") + private val node = EventNode.all("simplecloud-prefixes").setPriority(Int.MAX_VALUE) private val permissions = MinestomPermissions(permission) private val platform = MinestomPlatformImpl(directory, permissions.getChecker(), luckPerms) private val prefixes = Prefixes(platform) @@ -41,9 +41,6 @@ class PrefixesMinestom private constructor( private val tablist = MinestomTablist() private val logger = PrefixesConstants.LOGGER - /** Gets the [PrefixesApi] instance. */ - fun getApi(): PrefixesApi = prefixes.api - fun shutdown() { MinecraftServer.getGlobalEventHandler().removeChild(node) prefixes.shutdown() @@ -63,7 +60,6 @@ class PrefixesMinestom private constructor( } registerSync() - registerLuckPermsListener() registerCommands() prefixes.api.refreshAll() } @@ -87,11 +83,6 @@ class PrefixesMinestom private constructor( } } - private fun registerLuckPermsListener() { - val luckPerms = platform.getLuckPerms() ?: return - LuckPermsListener(luckPerms, manager).register() - } - private fun registerCommands() { if (!registerCommands) return @@ -105,6 +96,10 @@ class PrefixesMinestom private constructor( } companion object { + /** Returns the [PrefixesApi] instance, throws an [IllegalStateException] if prefixes is not enabled yet. */ + @JvmStatic + fun getApi(): PrefixesApi = PrefixesApiProvider.get() + /** Creates a [PrefixesMinestomBuilder] config files are created in [directory]. */ @JvmStatic fun builder(directory: Path): PrefixesMinestomBuilder = PrefixesMinestomBuilder(directory) diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt index 913f9e5..ded800c 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt @@ -37,7 +37,7 @@ class MinestomDisplayManager( MinecraftServer.getSchedulerManager().scheduleNextTick { if (!player.isOnline) return@scheduleNextTick registry.loadGroup(player.uuid, group) - render(player) + show(player) } } } @@ -48,7 +48,6 @@ class MinestomDisplayManager( teams.keys.filter { key -> key.target == id || key.viewer == id }.forEach { key -> if (key.viewer == id) teams.remove(key) else removeTeam(key) } - customNameManager.unregister(player) entries.remove(id) prefixes.sync?.publisher?.publishTablistRemove(id) @@ -60,14 +59,14 @@ class MinestomDisplayManager( onlinePlayers().forEach { player -> player.displayName = null } } - fun render(player: Player) { + fun show(player: Player) { if (!player.isOnline) return val data = registry.getData(player.uuid) ?: return player.displayName = if (prefixes.config.get().features.tablist) PlayerDisplayFormatter.formatTablistName(data, player.username) else null val team = createTeam(player, data) - onlinePlayers().forEach { viewer -> renderFor(player, viewer, team) } + onlinePlayers().forEach { viewer -> showTo(player, viewer, team) } updateNameTag(player, data) publish(player) @@ -81,7 +80,7 @@ class MinestomDisplayManager( fun addViewer(viewer: Player) { onlinePlayers().forEach { player -> val data = registry.getData(player.uuid) ?: return@forEach - renderFor(player, viewer, createTeam(player, data)) + showTo(player, viewer, createTeam(player, data)) } } @@ -89,7 +88,7 @@ class MinestomDisplayManager( onlinePlayers().forEach { player -> publish(player, force) } } - private fun renderFor(player: Player, viewer: Player, team: TeamsPacket?) { + private fun showTo(player: Player, viewer: Player, team: TeamsPacket?) { val viewerData = registry.getViewerData(player.uuid, viewer.uuid) if (viewerData == null) { updateTeam(player, viewer, team) diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt index 87f58e2..f38a405 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomTablist.kt @@ -1,10 +1,11 @@ package app.simplecloud.prefixes.minestom.display -import app.simplecloud.prefixes.shared.sync.tablist.SourcedTablistEntry +import app.simplecloud.prefixes.shared.sync.tablist.SyncedTablist import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import net.minestom.server.MinecraftServer import net.minestom.server.entity.GameMode import net.minestom.server.entity.Player +import net.minestom.server.network.packet.server.ServerPacket import net.minestom.server.network.packet.server.play.PlayerInfoRemovePacket import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket.Action @@ -12,54 +13,41 @@ import net.minestom.server.network.packet.server.play.TeamsPacket import net.minestom.server.utils.PacketSendingUtils import java.util.EnumSet import java.util.UUID -import java.util.concurrent.ConcurrentHashMap -class MinestomTablist { +class MinestomTablist : SyncedTablist() { - private val entries = ConcurrentHashMap() + override fun isOnline(id: UUID): Boolean = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) != null - fun update(publisherId: String, entry: TablistEntry) { - if (isOnline(entry.uniqueId)) return - - val previous = entries.put(entry.uniqueId, SourcedTablistEntry(publisherId, entry))?.entry - if (previous != entry) { - PacketSendingUtils.broadcastPlayPacket(createInfoPacket(listOf(entry))) - } - if (previous == null || previous.name != entry.name || previous.priority != entry.priority) { - if (previous != null) PacketSendingUtils.broadcastPlayPacket(MinestomPlayerTeam.removePacket(createTeam(previous))) - PacketSendingUtils.broadcastPlayPacket(createTeam(entry)) + override fun createInfoPacket(entries: List): ServerPacket { + val packetEntries = entries.map { entry -> + PlayerInfoUpdatePacket.Entry( + entry.uniqueId, + entry.name, + entry.profileProperties.map { property -> PlayerInfoUpdatePacket.Property(property.name, property.value, property.signature) }, + true, + entry.latency, + GameMode.valueOf(entry.gameMode.name), + entry.displayName, + null, + entry.listOrder, + entry.showHat + ) } - } - fun remove(id: UUID) { - val sourced = entries.remove(id) ?: return - hide(sourced.entry) + return PlayerInfoUpdatePacket(getUpdateActions(), packetEntries) } - fun remove(publisherId: String, id: UUID) { - val sourced = entries[id] ?: return - if (sourced.publisherId != publisherId) return - if (entries.remove(id, sourced)) hide(sourced.entry) - } + override fun createInfoRemovePacket(id: UUID): ServerPacket = PlayerInfoRemovePacket(id) - fun clear() { - entries.keys.toList().forEach { id -> remove(id) } - } + override fun createTeamPacket(entry: TablistEntry): ServerPacket = createTeam(entry) - fun sync(player: Player) { - val visible = entries.values.map(SourcedTablistEntry::entry).filter { entry -> !isOnline(entry.uniqueId) } - visible.chunked(PlayerInfoUpdatePacket.MAX_ENTRIES).forEach { batch -> player.sendPacket(createInfoPacket(batch)) } - visible.forEach { entry -> player.sendPacket(createTeam(entry)) } - } + override fun createTeamRemovePacket(entry: TablistEntry): ServerPacket = MinestomPlayerTeam.removePacket(createTeam(entry)) - private fun hide(entry: TablistEntry) { - PacketSendingUtils.broadcastPlayPacket(MinestomPlayerTeam.removePacket(createTeam(entry))) - if (!isOnline(entry.uniqueId)) { - PacketSendingUtils.broadcastPlayPacket(PlayerInfoRemovePacket(entry.uniqueId)) - } - } + override fun send(player: Player, packet: ServerPacket) = player.sendPacket(packet) + + override fun broadcast(packet: ServerPacket) = PacketSendingUtils.broadcastPlayPacket(packet) - private fun createTeam(entry: TablistEntry): TeamsPacket = MinestomPlayerTeam.createPacket(entry.name, entry.priority, hideNameTag = false) + private fun createTeam(entry: TablistEntry): TeamsPacket = MinestomPlayerTeam.createPacket(entry.name, entry.priority, hideNameTag = true) private fun getUpdateActions(): EnumSet = EnumSet.of( Action.ADD_PLAYER, @@ -70,25 +58,4 @@ class MinestomTablist { Action.UPDATE_LIST_ORDER, Action.UPDATE_HAT ) - - private fun createInfoPacket(tablistEntries: Collection): PlayerInfoUpdatePacket { - val packetEntries = tablistEntries.map { entry -> - PlayerInfoUpdatePacket.Entry( - entry.uniqueId, - entry.name, - entry.profileProperties.map { property -> PlayerInfoUpdatePacket.Property(property.name, property.value, property.signature) }, - true, - entry.latency, - GameMode.valueOf(entry.gameMode.name), - entry.displayName, - null, - entry.listOrder, - entry.showHat - ) - } - - return PlayerInfoUpdatePacket(getUpdateActions(), packetEntries) - } - - private fun isOnline(id: UUID): Boolean = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) != null } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/LuckPermsListener.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/LuckPermsListener.kt deleted file mode 100644 index 941643f..0000000 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/LuckPermsListener.kt +++ /dev/null @@ -1,40 +0,0 @@ -package app.simplecloud.prefixes.minestom.listener - -import app.simplecloud.prefixes.minestom.display.MinestomDisplayManager -import net.luckperms.api.LuckPerms -import net.luckperms.api.event.node.NodeMutateEvent -import net.luckperms.api.event.user.UserDataRecalculateEvent -import net.luckperms.api.model.group.Group -import net.luckperms.api.model.user.User -import net.minestom.server.MinecraftServer -import java.util.UUID - -class LuckPermsListener( - private val luckPerms: LuckPerms, - private val manager: MinestomDisplayManager -) { - - fun register() { - luckPerms.eventBus.subscribe(UserDataRecalculateEvent::class.java) { event -> - updatePlayer(event.user.uniqueId) - } - - luckPerms.eventBus.subscribe(NodeMutateEvent::class.java) { event -> - when (val target = event.target) { - is User -> updatePlayer(target.uniqueId) - is Group -> updateAllPlayers() - } - } - } - - private fun updatePlayer(id: UUID) { - val player = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) ?: return - manager.updatePlayer(player) - } - - private fun updateAllPlayers() { - MinecraftServer.getConnectionManager().onlinePlayers.forEach { player -> - manager.updatePlayer(player) - } - } -} diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt index 57ed431..9d4f23c 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/listener/PlayerListener.kt @@ -5,7 +5,6 @@ import app.simplecloud.prefixes.minestom.display.MinestomTablist import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.utilities.PlayerDisplayFormatter import net.kyori.adventure.text.Component -import net.minestom.server.MinecraftServer import net.minestom.server.event.Event import net.minestom.server.event.EventNode import net.minestom.server.event.player.PlayerChatEvent @@ -44,6 +43,8 @@ class PlayerListener( } private fun onChat(event: PlayerChatEvent) { + if (event.isCancelled) return + val features = prefixes.config.get().features if (!features.chat) return @@ -60,11 +61,7 @@ class PlayerListener( event.recipients.removeAll(viewerMessages.map { (viewer, _) -> viewer }.toSet()) event.formattedMessage = message - MinecraftServer.getSchedulerManager().scheduleNextTick { - if (event.isCancelled) return@scheduleNextTick - - viewerMessages.forEach { (viewer, viewerMessage) -> viewer.sendMessage(viewerMessage) } - prefixes.sync?.publisher?.publishChatMessage(message) - } + viewerMessages.forEach { (viewer, viewerMessage) -> viewer.sendMessage(viewerMessage) } + prefixes.sync?.publisher?.publishChatMessage(message) } } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt index 6ae671c..13b0eaa 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt @@ -37,7 +37,7 @@ class MinestomPrefixesListener( override fun onPlayerDataChange(id: UUID) { MinecraftServer.getSchedulerManager().scheduleNextTick { val player = MinecraftServer.getConnectionManager().getOnlinePlayerByUuid(id) ?: return@scheduleNextTick - manager.render(player) + manager.show(player) } } diff --git a/prefixes-paper/build.gradle.kts b/prefixes-paper/build.gradle.kts index fd44af3..740a588 100644 --- a/prefixes-paper/build.gradle.kts +++ b/prefixes-paper/build.gradle.kts @@ -3,11 +3,6 @@ plugins { alias(libs.plugins.paperweight.userdev) } -val customNames = rootProject.layout.projectDirectory.dir("custom-names") -require(customNames.dir("custom-names-paper/src/main/kotlin").asFile.isDirectory) { - "The custom-names submodule is missing. Run `git submodule update --init --recursive`." -} - dependencies { api(project(":prefixes-shared")) compileOnly(libs.paper.api) @@ -19,8 +14,8 @@ dependencies { sourceSets { main { kotlin { - srcDir(customNames.dir("custom-names-api/src/main/kotlin")) - srcDir(customNames.dir("custom-names-paper/src/main/kotlin")) + srcDir(rootProject.layout.projectDirectory.dir("custom-names").dir("custom-names-api/src/main/kotlin")) + srcDir(rootProject.layout.projectDirectory.dir("custom-names").dir("custom-names-paper/src/main/kotlin")) exclude("**/CustomNamesPlugin.kt") } } @@ -43,7 +38,7 @@ modrinth { "26.2", "26.3", ) - loaders.addAll("paper", "purpur") + loaders.addAll("paper", "purpur", "folia") changelog.set("https://docs.simplecloud.app/changelog") syncBodyFrom.set(rootProject.file("README.md").readText()) } \ No newline at end of file diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt index cb1931b..6a6ec41 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt @@ -4,13 +4,11 @@ import app.simplecloud.prefixes.api.PrefixesApi import app.simplecloud.prefixes.paper.command.PrefixesPaperSenderMapper import app.simplecloud.prefixes.paper.display.PaperDisplayManager import app.simplecloud.prefixes.paper.display.PaperTablist -import app.simplecloud.prefixes.paper.listener.LuckPermsListener import app.simplecloud.prefixes.paper.listener.PlayerListener import app.simplecloud.prefixes.paper.platform.PaperPlatformImpl import app.simplecloud.prefixes.paper.platform.PaperPrefixesListener import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.command.PrefixesCommand -import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import org.bukkit.Bukkit import org.bukkit.plugin.ServicePriority import org.bukkit.plugin.java.JavaPlugin @@ -26,8 +24,7 @@ class PrefixesPaper : JavaPlugin() { val customNameManager = CustomNameManagerImpl(this) customNameManager.registerListeners() - val platform = PaperPlatformImpl(this) - val prefixes = Prefixes(platform) + val prefixes = Prefixes(PaperPlatformImpl(this)) this.prefixes = prefixes prefixes.startup() @@ -39,7 +36,6 @@ class PrefixesPaper : JavaPlugin() { Bukkit.getPluginManager().registerEvents(PlayerListener(prefixes, manager, tablist), this) registerSync(prefixes, manager, tablist) - registerLuckPermsListener(platform, manager) prefixes.addListener(PaperPrefixesListener(this, prefixes, manager, tablist)) @@ -55,15 +51,10 @@ class PrefixesPaper : JavaPlugin() { val sync = prefixes.sync ?: return sync.subscriber.subscribeChatMessage { message -> Bukkit.getServer().sendMessage(message) } - sync.subscriber.subscribeTablist(tablist::update, tablist::remove) { Bukkit.getScheduler().runTask(this, Runnable { manager.publishAll(true) }) } + sync.subscriber.subscribeTablist(tablist::update, tablist::remove) { manager.publishAll(true) } sync.publisher.publishTablistRequest() - Bukkit.getScheduler().runTaskTimer(this, Runnable { manager.publishAll() }, 600L, 600L) - } - - private fun registerLuckPermsListener(platform: PrefixesPlatform, manager: PaperDisplayManager) { - val luckPerms = platform.getLuckPerms() ?: return - LuckPermsListener(this, luckPerms, manager).register() + Bukkit.getGlobalRegionScheduler().runAtFixedRate(this, { manager.publishAll() }, 600L, 600L) } private fun registerCommands(prefixes: Prefixes) { diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt index 71981cd..27bb437 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt @@ -38,21 +38,13 @@ class PaperDisplayManager( private val teams = ConcurrentHashMap() private val entries = ConcurrentHashMap() - fun addPlayer(player: Player) { - updatePlayer(player) - Bukkit.getScheduler().runTask(plugin, Runnable { - if (player.isOnline && !registry.isLoaded(player.uniqueId)) publish(player) - }) - } - fun updatePlayer(player: Player) { PrefixesConstants.SCOPE.launch(PrefixesCoroutineDetails("${player.name} (${player.uniqueId})", "update player")) { val group = prefixes.api.getPrimaryGroup(player.uniqueId).await() - Bukkit.getScheduler().runTask(plugin, Runnable { - if (!player.isOnline) return@Runnable + player.scheduler.run(plugin, { registry.loadGroup(player.uniqueId, group) - render(player) - }) + show(player) + }, null) } } @@ -73,14 +65,14 @@ class PaperDisplayManager( Bukkit.getOnlinePlayers().forEach { player -> player.playerListName(null) } } - fun render(player: Player) { + fun show(player: Player) { if (!player.isOnline) return val data = registry.getData(player.uniqueId) ?: return val features = prefixes.config.get().features player.playerListName(if (features.tablist) PlayerDisplayFormatter.formatTablistName(data, player.name) else null) val team = createTeam(player, data) - Bukkit.getOnlinePlayers().forEach { viewer -> renderFor(player, viewer, team) } + Bukkit.getOnlinePlayers().forEach { viewer -> showTo(player, viewer, team) } val name = customNameManager.forEntity(player) name.setName { viewer -> (registry.getViewerData(player.uniqueId, viewer) ?: data).displayName } @@ -92,17 +84,17 @@ class PaperDisplayManager( fun addViewer(viewer: Player) { Bukkit.getOnlinePlayers().forEach { player -> val data = registry.getData(player.uniqueId) ?: return@forEach - renderFor(player, viewer, createTeam(player, data)) + showTo(player, viewer, createTeam(player, data)) } } fun publishAll(force: Boolean = false) { - Bukkit.getOnlinePlayers() - .filter { player -> registry.isLoaded(player.uniqueId) } - .forEach { player -> publish(player, force) } + Bukkit.getOnlinePlayers().forEach { player -> + player.scheduler.run(plugin, { publish(player, force) }, null) + } } - private fun renderFor(player: Player, viewer: Player, team: PaperPlayerTeam?) { + private fun showTo(player: Player, viewer: Player, team: PaperPlayerTeam?) { val viewerData = registry.getViewerData(player.uniqueId, viewer.uniqueId) if (viewerData == null) { updateTeam(player, viewer, team) @@ -118,13 +110,13 @@ class PaperDisplayManager( private fun publish(player: Player, force: Boolean = false) { if (!prefixes.config.get().isTablistSynced()) return val publisher = prefixes.sync?.publisher ?: return + val data = registry.getData(player.uniqueId) ?: return - val data = registry.getData(player.uniqueId) val entry = TablistEntry( uniqueId = player.uniqueId, name = player.name, - displayName = if (data == null) Component.text(player.name) else PlayerDisplayFormatter.formatTablistName(data, player.name), - priority = data?.priority ?: 0, + displayName = PlayerDisplayFormatter.formatTablistName(data, player.name), + priority = data.priority, profileProperties = player.playerProfile.properties.map { property -> ProfileProperty(property.name, property.value, property.signature) }, latency = player.ping, gameMode = TablistGameMode.valueOf(player.gameMode.name), @@ -148,18 +140,19 @@ class PaperDisplayManager( } private fun updateTeam(player: Player, viewer: Player, team: PaperPlayerTeam?) { - val key = ViewerKey(player.uniqueId, viewer.uniqueId) - removeTeam(key) - if (team == null) return - - teams[key] = team - send(viewer, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)) + teams.compute(ViewerKey(player.uniqueId, viewer.uniqueId)) { _, current -> + if (current != null) send(viewer, ClientboundSetPlayerTeamPacket.createRemovePacket(current)) + if (team != null) send(viewer, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true)) + team + } } private fun removeTeam(key: ViewerKey) { - val team = teams.remove(key) ?: return - val viewer = Bukkit.getPlayer(key.viewer) ?: return - send(viewer, ClientboundSetPlayerTeamPacket.createRemovePacket(team)) + teams.computeIfPresent(key) { _, team -> + val viewer = Bukkit.getPlayer(key.viewer) + if (viewer != null) send(viewer, ClientboundSetPlayerTeamPacket.createRemovePacket(team)) + null + } } private fun createListNamePacket(player: Player, name: Component): ClientboundPlayerInfoUpdatePacket { diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt index f6b6ca4..64a2b3b 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperTablist.kt @@ -1,6 +1,6 @@ package app.simplecloud.prefixes.paper.display -import app.simplecloud.prefixes.shared.sync.tablist.SourcedTablistEntry +import app.simplecloud.prefixes.shared.sync.tablist.SyncedTablist import app.simplecloud.prefixes.shared.sync.tablist.TablistEntry import com.google.common.collect.ImmutableMultimap import com.mojang.authlib.GameProfile @@ -18,58 +18,13 @@ import org.bukkit.craftbukkit.entity.CraftPlayer import org.bukkit.entity.Player import java.util.EnumSet import java.util.UUID -import java.util.concurrent.ConcurrentHashMap -class PaperTablist { +class PaperTablist : SyncedTablist>() { - private val entries = ConcurrentHashMap() + override fun isOnline(id: UUID): Boolean = Bukkit.getPlayer(id) != null - fun update(publisherId: String, entry: TablistEntry) { - if (Bukkit.getPlayer(entry.uniqueId) != null) return - - val previous = entries.put(entry.uniqueId, SourcedTablistEntry(publisherId, entry))?.entry - if (previous != entry) { - broadcast(createInfoPacket(listOf(entry))) - } - if (previous == null || previous.name != entry.name || previous.priority != entry.priority) { - if (previous != null) broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(createTeam(previous))) - broadcast(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(createTeam(entry), true)) - } - } - - fun remove(id: UUID) { - val sourced = entries.remove(id) ?: return - hide(sourced.entry) - } - - fun remove(publisherId: String, id: UUID) { - val sourced = entries[id] ?: return - if (sourced.publisherId != publisherId) return - if (entries.remove(id, sourced)) hide(sourced.entry) - } - - fun clear() { - entries.keys.toList().forEach(::remove) - } - - fun sync(player: Player) { - val visible = entries.values.map(SourcedTablistEntry::entry).filter { entry -> Bukkit.getPlayer(entry.uniqueId) == null } - - visible.chunked(100).forEach { batch -> send(player, createInfoPacket(batch)) } - visible.forEach { entry -> send(player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(createTeam(entry), true)) } - } - - private fun hide(entry: TablistEntry) { - broadcast(ClientboundSetPlayerTeamPacket.createRemovePacket(createTeam(entry))) - if (Bukkit.getPlayer(entry.uniqueId) == null) { - broadcast(ClientboundPlayerInfoRemovePacket(listOf(entry.uniqueId))) - } - } - - private fun createTeam(entry: TablistEntry): PaperPlayerTeam = PaperPlayerTeam(entry.name, entry.priority, hideNameTag = true) - - private fun createInfoPacket(tablistEntries: Collection): ClientboundPlayerInfoUpdatePacket { - val packetEntries = tablistEntries.map { entry -> + override fun createInfoPacket(entries: List): Packet<*> { + val packetEntries = entries.map { entry -> ClientboundPlayerInfoUpdatePacket.Entry( entry.uniqueId, createGameProfile(entry), @@ -86,6 +41,24 @@ class PaperTablist { return ClientboundPlayerInfoUpdatePacket(getUpdateActions(), packetEntries) } + override fun createInfoRemovePacket(id: UUID): Packet<*> = ClientboundPlayerInfoRemovePacket(listOf(id)) + + override fun createTeamPacket(entry: TablistEntry): Packet<*> = + ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(createTeam(entry), true) + + override fun createTeamRemovePacket(entry: TablistEntry): Packet<*> = + ClientboundSetPlayerTeamPacket.createRemovePacket(createTeam(entry)) + + override fun send(player: Player, packet: Packet<*>) { + (player as CraftPlayer).handle.connection.send(packet) + } + + override fun broadcast(packet: Packet<*>) { + Bukkit.getOnlinePlayers().forEach { player -> send(player, packet) } + } + + private fun createTeam(entry: TablistEntry): PaperPlayerTeam = PaperPlayerTeam(entry.name, entry.priority, hideNameTag = true) + private fun getUpdateActions(): EnumSet = EnumSet.of( Action.ADD_PLAYER, Action.UPDATE_LISTED, @@ -103,12 +76,4 @@ class PaperTablist { } return GameProfile(entry.uniqueId, entry.name, PropertyMap(properties.build())) } - - private fun broadcast(packet: Packet<*>) { - Bukkit.getOnlinePlayers().forEach { player -> send(player, packet) } - } - - private fun send(player: Player, packet: Packet<*>) { - (player as CraftPlayer).handle.connection.send(packet) - } } diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/LuckPermsListener.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/LuckPermsListener.kt deleted file mode 100644 index a68c3f8..0000000 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/LuckPermsListener.kt +++ /dev/null @@ -1,46 +0,0 @@ -package app.simplecloud.prefixes.paper.listener - -import app.simplecloud.prefixes.paper.display.PaperDisplayManager -import net.luckperms.api.LuckPerms -import net.luckperms.api.event.node.NodeMutateEvent -import net.luckperms.api.event.user.UserDataRecalculateEvent -import net.luckperms.api.model.group.Group -import net.luckperms.api.model.user.User -import org.bukkit.Bukkit -import org.bukkit.plugin.Plugin -import java.util.UUID - -class LuckPermsListener( - private val plugin: Plugin, - private val luckPerms: LuckPerms, - private val manager: PaperDisplayManager -) { - - fun register() { - luckPerms.eventBus.subscribe(plugin, UserDataRecalculateEvent::class.java) { event -> - val id = event.user.uniqueId - Bukkit.getScheduler().runTask(plugin, Runnable { updatePlayer(id) }) - } - - luckPerms.eventBus.subscribe(plugin, NodeMutateEvent::class.java) { event -> - val target = event.target - Bukkit.getScheduler().runTask(plugin, Runnable { - when (target) { - is User -> updatePlayer(target.uniqueId) - is Group -> updateAllPlayers() - } - }) - } - } - - private fun updatePlayer(id: UUID) { - val player = Bukkit.getPlayer(id) ?: return - manager.updatePlayer(player) - } - - private fun updateAllPlayers() { - Bukkit.getOnlinePlayers().forEach { player -> - manager.updatePlayer(player) - } - } -} diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt index d501fd7..a25d615 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/listener/PlayerListener.kt @@ -21,7 +21,7 @@ class PlayerListener( fun onJoin(event: PlayerJoinEvent) { val player = event.player tablist.remove(player.uniqueId) - manager.addPlayer(player) + manager.updatePlayer(player) manager.addViewer(player) if (prefixes.config.get().isTablistSynced()) { diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt index b86236f..fbb3179 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt @@ -37,10 +37,8 @@ class PaperPrefixesListener( } override fun onPlayerDataChange(id: UUID) { - Bukkit.getScheduler().runTask(plugin, Runnable { - val player = Bukkit.getPlayer(id) ?: return@Runnable - manager.render(player) - }) + val player = Bukkit.getPlayer(id) ?: return + player.scheduler.run(plugin, { manager.show(player) }, null) } override fun onAllPlayersUpdate() { diff --git a/prefixes-paper/src/main/resources/paper-plugin.yml b/prefixes-paper/src/main/resources/paper-plugin.yml index 36539b4..435f5c0 100644 --- a/prefixes-paper/src/main/resources/paper-plugin.yml +++ b/prefixes-paper/src/main/resources/paper-plugin.yml @@ -2,6 +2,7 @@ name: simplecloud-prefixes version: ${version} description: Prefixes, suffixes and name colors for chat, tab list and name tags. Works with LuckPerms and syncs across your SimpleCloud network api-version: 26.3 +folia-supported: true main: app.simplecloud.prefixes.paper.PrefixesPaper diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt index 9c05b23..4d60dff 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/Prefixes.kt @@ -11,11 +11,13 @@ import app.simplecloud.prefixes.shared.data.PrefixesDataRegistry import app.simplecloud.prefixes.shared.group.GroupProviderRegistry import app.simplecloud.prefixes.shared.group.config.ConfigGroupProvider import app.simplecloud.prefixes.shared.group.luckperms.LuckPermsGroupProvider +import app.simplecloud.prefixes.shared.group.luckperms.LuckPermsListener import app.simplecloud.prefixes.shared.platform.PrefixesListener import app.simplecloud.prefixes.shared.platform.PrefixesPlatform import app.simplecloud.prefixes.shared.sync.PrefixesSync import app.simplecloud.prefixes.shared.utilities.ColorParser import kotlinx.coroutines.cancelChildren +import net.luckperms.api.event.EventSubscription import java.io.File import java.util.concurrent.CopyOnWriteArrayList @@ -23,6 +25,8 @@ class Prefixes(private val platform: PrefixesPlatform) { private val logger = PrefixesConstants.LOGGER private val listeners = CopyOnWriteArrayList() + private val subscriptions = CopyOnWriteArrayList>() + private val luckPerms = platform.getLuckPerms() val config = createConfig() val messages = createMessages() @@ -35,9 +39,14 @@ class Prefixes(private val platform: PrefixesPlatform) { fun startup() { PrefixesApiProvider.register(api) validateConfig() + + if (luckPerms != null) { + subscriptions.addAll(LuckPermsListener(luckPerms, api).register()) + } } fun shutdown() { + subscriptions.forEach { subscription -> subscription.close() } listeners.forEach(PrefixesListener::onShutdown) PrefixesApiProvider.unregister() sync?.shutdown() @@ -58,7 +67,7 @@ class Prefixes(private val platform: PrefixesPlatform) { warnIfRestartRequired(previousSync) listeners.forEach(PrefixesListener::onReload) }.onSuccess { - logger.info("Succesfully reloaded simplecloud prefixes") + logger.info("Successfully reloaded simplecloud prefixes") }.onFailure { throwable -> logger.error("Failed to reload simplecloud prefixes", throwable) }.isSuccess @@ -99,7 +108,6 @@ class Prefixes(private val platform: PrefixesPlatform) { private fun createGroupRegistry(): GroupProviderRegistry { val registry = GroupProviderRegistry(config, ConfigGroupProvider(config, platform.getPermissionChecker())) - val luckPerms = platform.getLuckPerms() if (luckPerms != null) { registry.register(LuckPermsGroupProvider(luckPerms)) } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt index 01c973a..34e9930 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/command/PrefixesCommand.kt @@ -4,7 +4,6 @@ import app.simplecloud.prefixes.shared.Prefixes import app.simplecloud.prefixes.shared.utilities.PrefixesPermissions import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder import org.incendo.cloud.CommandManager -import org.incendo.cloud.kotlin.coroutines.extension.suspendingHandler class PrefixesCommand( private val manager: CommandManager, @@ -14,28 +13,14 @@ class PrefixesCommand( private val messages get() = prefixes.messages.get() fun register() { - registerRoot() registerHelp() registerReload() } - private fun registerRoot() { - manager.command( - manager.commandBuilder("scprefix") - .handler { - sendHelp(it.sender()) - } - ) - } - private fun registerHelp() { - manager.command( - manager.commandBuilder("scprefix") - .literal("help") - .handler { - sendHelp(it.sender()) - } - ) + val root = manager.commandBuilder("scprefix") + manager.command(root.handler { sendHelp(it.sender()) }) + manager.command(root.literal("help").handler { sendHelp(it.sender()) }) } private fun registerReload() { @@ -43,14 +28,9 @@ class PrefixesCommand( manager.commandBuilder("scprefix") .literal("reload") .permission(PrefixesPermissions.RELOAD) - .suspendingHandler { - val sender = it.sender() - val reloaded = prefixes.reload() - if (reloaded) { - sender.sendMessage(messages.msg(messages.command.reload.success)) - } else { - sender.sendMessage(messages.msg(messages.command.reload.failed)) - } + .handler { + val message = if (prefixes.reload()) messages.command.reload.success else messages.command.reload.failed + it.sender().sendMessage(messages.msg(message)) } ) } @@ -68,4 +48,4 @@ class PrefixesCommand( sender.sendMessage(messages.msg(messages.command.help.entry, Placeholder.unparsed("command", "/scprefix reload"))) } } -} \ No newline at end of file +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt index d98c804..ba47fe9 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/config/PrefixesConfig.kt @@ -34,7 +34,7 @@ data class FeaturesConfig( @ConfigSerializable data class SyncConfig( - val enabled: Boolean = true, + val enabled: Boolean = false, val channels: SyncChannels = SyncChannels(), val sources: List = listOf(PrefixesConstants.CURRENT_SYNC_SOURCE) ) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt index b9d0fb5..21ac740 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/data/PrefixesDataRegistry.kt @@ -19,8 +19,6 @@ class PrefixesDataRegistry(private val platform: PrefixesPlatform) { private val overrides = ConcurrentHashMap() private val viewerOverrides = ConcurrentHashMap() - fun isLoaded(id: UUID): Boolean = groups.containsKey(id) - fun getData(id: UUID): PrefixesPlayerData? { val group = groups[id] ?: return null return createData(id, group.merge(overrides[id])) diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt index 4b9f553..eaf8629 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/config/ConfigGroupProvider.kt @@ -20,23 +20,16 @@ class ConfigGroupProvider( private val logger = PrefixesConstants.LOGGER - override fun getName(): String = "config" + override fun getName(): String = PrefixesConstants.CONFIG_SOURCE override fun getGroups(): CompletableFuture> { return CompletableFuture.completedFuture(loadGroups()) } override fun getGroup(id: UUID): CompletableFuture { - return PrefixesConstants.SCOPE.future { - val groups = loadGroups() - val config = configFactory.get() - - val group = groups.firstOrNull { - !it.name.equals(config.general.defaultGroup, ignoreCase = true) && it.hasPermission(id) - } - - group ?: groups.firstOrNull { it.name.equals(config.general.defaultGroup, ignoreCase = true) } - } + val groups = loadGroups() + val group = groups.firstOrNull { !isDefault(it) && it.hasPermission(id) } ?: groups.firstOrNull(::isDefault) + return CompletableFuture.completedFuture(group) } override fun addGroup(group: PrefixesGroup): CompletableFuture { @@ -64,6 +57,9 @@ class ConfigGroupProvider( .sortedByDescending { it.priority } } + private fun isDefault(group: PrefixesGroup): Boolean = + group.name.equals(configFactory.get().general.defaultGroup, ignoreCase = true) + private fun createConfigGroup(group: PrefixesGroup) = ConfigGroup( name = group.name, priority = group.priority, diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt index ab09df0..431fa08 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroup.kt @@ -11,6 +11,7 @@ import net.kyori.adventure.text.format.NamedTextColor import net.kyori.adventure.text.format.TextColor import net.luckperms.api.LuckPerms import net.luckperms.api.model.group.Group +import net.luckperms.api.model.user.User import java.util.UUID import java.util.concurrent.CompletableFuture @@ -25,16 +26,21 @@ class LuckPermsGroup( override val prefix: Component = miniMessage.deserialize(group.cachedData.metaData.prefix ?: "") override val suffix: Component = miniMessage.deserialize(group.cachedData.metaData.suffix ?: "") override val color: TextColor = parseColor() - override val displayName: String = getMetaValue("display-name") ?: PrefixesConstants.DEFAULT_DISPLAY_NAME - override val chatFormat: String = getMetaValue("chat-format") ?: PrefixesConstants.DEFAULT_CHAT_FORMAT + override val displayName: String = getMetaValue("display-name") ?: "" + override val chatFormat: String = getMetaValue("chat-format") ?: "" override fun containsPlayer(id: UUID): CompletableFuture { - return PrefixesConstants.SCOPE.future { - val user = luckPerms.userManager.loadUser(id).await() - user.primaryGroup == name || user.getInheritedGroups(user.queryOptions).any { it.name == name } + val user = luckPerms.userManager.getUser(id) + if (user != null) { + return CompletableFuture.completedFuture(isMember(user)) } + + return PrefixesConstants.SCOPE.future { isMember(luckPerms.userManager.loadUser(id).await()) } } + private fun isMember(user: User): Boolean = + user.primaryGroup == name || user.getInheritedGroups(user.queryOptions).any { it.name == name } + private fun parseColor(): TextColor { val value = getMetaValue("color") ?: return NamedTextColor.WHITE return ColorParser.parse(value) ?: NamedTextColor.WHITE diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt index c0addf6..b96de93 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsGroupProvider.kt @@ -8,6 +8,7 @@ import app.simplecloud.prefixes.shared.PrefixesConstants import kotlinx.coroutines.future.await import kotlinx.coroutines.future.future import net.luckperms.api.LuckPerms +import net.luckperms.api.model.data.NodeMap import net.luckperms.api.model.group.Group import net.luckperms.api.model.user.User import net.luckperms.api.node.types.MetaNode @@ -78,9 +79,13 @@ class LuckPermsGroupProvider( } data.add(MetaNode.builder("color", ColorParser.serialize(source.color)).build()) + addMeta(data, "display-name", source.displayName) + addMeta(data, "chat-format", source.chatFormat) + } - data.add(MetaNode.builder("display-name", source.displayName).build()) - data.add(MetaNode.builder("chat-format", source.chatFormat).build()) + private fun addMeta(data: NodeMap, key: String, value: String) { + if (value.isBlank()) return + data.add(MetaNode.builder(key, value).build()) } private fun resolveGroup(user: User): PrefixesGroup? { diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsListener.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsListener.kt new file mode 100644 index 0000000..6c1d25c --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/group/luckperms/LuckPermsListener.kt @@ -0,0 +1,27 @@ +package app.simplecloud.prefixes.shared.group.luckperms + +import app.simplecloud.prefixes.api.PrefixesApi +import net.luckperms.api.LuckPerms +import net.luckperms.api.event.EventSubscription +import net.luckperms.api.event.node.NodeMutateEvent +import net.luckperms.api.event.user.UserDataRecalculateEvent +import net.luckperms.api.model.group.Group +import net.luckperms.api.model.user.User + +class LuckPermsListener( + private val luckPerms: LuckPerms, + private val api: PrefixesApi +) { + + fun register(): List> = listOf( + luckPerms.eventBus.subscribe(UserDataRecalculateEvent::class.java) { event -> api.refreshPlayer(event.user.uniqueId) }, + luckPerms.eventBus.subscribe(NodeMutateEvent::class.java, ::onNodeMutate) + ) + + private fun onNodeMutate(event: NodeMutateEvent) { + when (val target = event.target) { + is User -> api.refreshPlayer(target.uniqueId) + is Group -> api.refreshAll() + } + } +} diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SyncedTablist.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SyncedTablist.kt new file mode 100644 index 0000000..533c8e5 --- /dev/null +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SyncedTablist.kt @@ -0,0 +1,66 @@ +package app.simplecloud.prefixes.shared.sync.tablist + +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap + +abstract class SyncedTablist { + + private val entries = ConcurrentHashMap() + + fun update(publisherId: String, entry: TablistEntry) { + if (isOnline(entry.uniqueId)) return + + val previous = entries.put(entry.uniqueId, SourcedTablistEntry(publisherId, entry))?.entry + if (previous != entry) { + broadcast(createInfoPacket(listOf(entry))) + } + if (previous == null || previous.name != entry.name || previous.priority != entry.priority) { + if (previous != null) broadcast(createTeamRemovePacket(previous)) + broadcast(createTeamPacket(entry)) + } + } + + fun remove(id: UUID) { + val sourced = entries.remove(id) ?: return + hide(sourced.entry) + } + + fun remove(publisherId: String, id: UUID) { + val sourced = entries[id] ?: return + if (sourced.publisherId != publisherId) return + if (entries.remove(id, sourced)) hide(sourced.entry) + } + + fun clear() { + entries.keys.toList().forEach { id -> remove(id) } + } + + fun sync(player: P) { + val visible = entries.values.map(SourcedTablistEntry::entry).filter { entry -> !isOnline(entry.uniqueId) } + + visible.chunked(100).forEach { batch -> send(player, createInfoPacket(batch)) } + visible.forEach { entry -> send(player, createTeamPacket(entry)) } + } + + protected abstract fun isOnline(id: UUID): Boolean + + protected abstract fun createInfoPacket(entries: List): K + + protected abstract fun createInfoRemovePacket(id: UUID): K + + protected abstract fun createTeamPacket(entry: TablistEntry): K + + protected abstract fun createTeamRemovePacket(entry: TablistEntry): K + + protected abstract fun send(player: P, packet: K) + + protected abstract fun broadcast(packet: K) + + private fun hide(entry: TablistEntry) { + broadcast(createTeamRemovePacket(entry)) + if (!isOnline(entry.uniqueId)) { + broadcast(createInfoRemovePacket(entry.uniqueId)) + } + } + +} \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ComponentSerializer.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ComponentSerializer.kt index f72035a..23196fe 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ComponentSerializer.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/utilities/ComponentSerializer.kt @@ -7,11 +7,7 @@ object ComponentSerializer { private val serializer = GsonComponentSerializer.gson() - fun serialize(component: Component): String { - return serializer.serialize(component) - } + fun serialize(component: Component): String = serializer.serialize(component) - fun deserialize(json: String): Component { - return serializer.deserialize(json) - } + fun deserialize(json: String): Component = serializer.deserialize(json) } From e5dcc1ba8ecd78eb628c2120c9dda759fa13c199 Mon Sep 17 00:00:00 2001 From: Janis Date: Sun, 11 Oct 2026 01:30:21 +0200 Subject: [PATCH 19/22] build: reduce jar size --- build.gradle.kts | 5 +++++ gradle/libs.versions.toml | 2 +- prefixes-api/build.gradle.kts | 6 +++++- prefixes-minestom/build.gradle.kts | 4 ++-- prefixes-paper/build.gradle.kts | 2 -- prefixes-shared/build.gradle.kts | 20 ++++++++++++++++---- 6 files changed, 29 insertions(+), 10 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7fc4f81..70e7f17 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,6 +22,10 @@ allprojects { } } +tasks.shadowJar { + enabled = false +} + subprojects { apply { plugin("org.jetbrains.kotlin.jvm") @@ -57,6 +61,7 @@ subprojects { mergeServiceFiles() duplicatesStrategy = DuplicatesStrategy.INCLUDE archiveFileName.set("${project.name}.jar") + exclude("google/protobuf/**", "src/google/protobuf/**") } tasks.processResources { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c645a78..dc78423 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,7 +54,7 @@ configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref configurate-kotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurate" } protoc = { module = "com.google.protobuf:protoc", version.ref = "protoc" } -protobuf-kotlin = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "protobuf-kotlin" } +protobuf-kotlin-lite = { module = "com.google.protobuf:protobuf-kotlin-lite", version.ref = "protobuf-kotlin" } jnats = { module = "io.nats:jnats", version.ref = "jnats" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } diff --git a/prefixes-api/build.gradle.kts b/prefixes-api/build.gradle.kts index 91b61f6..3a23142 100644 --- a/prefixes-api/build.gradle.kts +++ b/prefixes-api/build.gradle.kts @@ -1,3 +1,7 @@ dependencies { api(libs.bundles.adventure) -} \ No newline at end of file +} + +tasks.shadowJar { + enabled = false +} diff --git a/prefixes-minestom/build.gradle.kts b/prefixes-minestom/build.gradle.kts index 8af7b68..d477e9a 100644 --- a/prefixes-minestom/build.gradle.kts +++ b/prefixes-minestom/build.gradle.kts @@ -1,7 +1,7 @@ dependencies { api(project(":prefixes-shared")) - implementation(libs.minestom) - implementation(libs.slf4j.api) + compileOnly(libs.minestom) + compileOnly(libs.slf4j.api) implementation(libs.cloud.command.minestom) } diff --git a/prefixes-paper/build.gradle.kts b/prefixes-paper/build.gradle.kts index 740a588..d776fc6 100644 --- a/prefixes-paper/build.gradle.kts +++ b/prefixes-paper/build.gradle.kts @@ -24,8 +24,6 @@ sourceSets { tasks.shadowJar { relocate("com.google.protobuf", "app.simplecloud.prefixes.libs.protobuf") relocate("space.chunks.customname", "app.simplecloud.prefixes.libs.customname") - relocate("xyz.jpenilla.reflectionremapper", "app.simplecloud.prefixes.libs.reflectionremapper") - exclude("org/bouncycastle/jcajce/io/DigestUpdatingOutputStream.class") } modrinth { diff --git a/prefixes-shared/build.gradle.kts b/prefixes-shared/build.gradle.kts index b115d33..e79bebc 100644 --- a/prefixes-shared/build.gradle.kts +++ b/prefixes-shared/build.gradle.kts @@ -5,9 +5,12 @@ plugins { dependencies { api(project(":prefixes-api")) api(libs.simplecloud.plugin.api) - implementation(libs.jnats) + implementation(libs.jnats) { + // only needed for NKey + exclude(group = "org.bouncycastle") + } implementation(libs.slf4j.api) - implementation(libs.protobuf.kotlin) + implementation(libs.protobuf.kotlin.lite) implementation(libs.bundles.configurate) implementation(libs.bundles.cloud.command) } @@ -34,7 +37,12 @@ protobuf { generateProtoTasks { all().forEach { it.builtins { - create("kotlin") + named("java") { + option("lite") + } + create("kotlin") { + option("lite") + } } } } @@ -42,6 +50,10 @@ protobuf { configurations.all { resolutionStrategy { - force("com.google.protobuf:protobuf-java:4.36.2") + force("com.google.protobuf:protobuf-javalite:4.36.2") } } + +tasks.shadowJar { + enabled = false +} From 54e518907d3e590b97e1de28890d5aa6c9afa9ef Mon Sep 17 00:00:00 2001 From: Janis Date: Sun, 11 Oct 2026 02:55:47 +0200 Subject: [PATCH 20/22] chore: bump version to 1.1.0 --- README.md | 2 +- build.gradle.kts | 6 +----- prefixes-api/build.gradle.kts | 6 +----- prefixes-shared/build.gradle.kts | 6 +----- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index fc0bdc4..0fbea5f 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Clone the repository with its pinned Custom Names source before building: ```shell git clone --recurse-submodules https://github.com/simplecloudapp/prefixes-plugin.git cd prefixes-plugin -bash ./gradlew build +./gradlew build ``` ## License diff --git a/build.gradle.kts b/build.gradle.kts index 70e7f17..e7f65cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { allprojects { group = "app.simplecloud.plugin" - version = "1.1.0-beta.3" + version = "1.1.0" repositories { mavenCentral() @@ -22,10 +22,6 @@ allprojects { } } -tasks.shadowJar { - enabled = false -} - subprojects { apply { plugin("org.jetbrains.kotlin.jvm") diff --git a/prefixes-api/build.gradle.kts b/prefixes-api/build.gradle.kts index 3a23142..91b61f6 100644 --- a/prefixes-api/build.gradle.kts +++ b/prefixes-api/build.gradle.kts @@ -1,7 +1,3 @@ dependencies { api(libs.bundles.adventure) -} - -tasks.shadowJar { - enabled = false -} +} \ No newline at end of file diff --git a/prefixes-shared/build.gradle.kts b/prefixes-shared/build.gradle.kts index e79bebc..9db4d92 100644 --- a/prefixes-shared/build.gradle.kts +++ b/prefixes-shared/build.gradle.kts @@ -52,8 +52,4 @@ configurations.all { resolutionStrategy { force("com.google.protobuf:protobuf-javalite:4.36.2") } -} - -tasks.shadowJar { - enabled = false -} +} \ No newline at end of file From 8f6e64448272700bc3eca01c288cbe36763997fd Mon Sep 17 00:00:00 2001 From: Janis Date: Sun, 11 Oct 2026 13:54:07 +0200 Subject: [PATCH 21/22] fix: stale synced tablist entries --- .../prefixes/minestom/PrefixesMinestom.kt | 3 ++- .../minestom/display/MinestomDisplayManager.kt | 18 +++++++++++++----- .../platform/MinestomPrefixesListener.kt | 1 + .../prefixes/paper/PrefixesPaper.kt | 8 ++++++-- .../paper/display/PaperDisplayManager.kt | 18 +++++++++++++----- .../paper/platform/PaperPrefixesListener.kt | 1 + .../prefixes/shared/sync/PrefixesSync.kt | 7 ++++++- .../prefixes/shared/sync/SyncPublisher.kt | 2 +- .../shared/sync/tablist/SourcedTablistEntry.kt | 5 ++++- .../shared/sync/tablist/SyncedTablist.kt | 10 +++++++++- 10 files changed, 56 insertions(+), 17 deletions(-) diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt index 284382e..65634d9 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/PrefixesMinestom.kt @@ -71,7 +71,7 @@ class PrefixesMinestom private constructor( sync.subscriber.subscribeTablist( onUpdate = tablist::update, onRemove = { publisherId, id -> tablist.remove(publisherId, id) }, - onRequest = { MinecraftServer.getSchedulerManager().scheduleNextTick { manager.publishAll(true) } } + onRequest = { MinecraftServer.getSchedulerManager().scheduleNextTick { manager.publishAll() } } ) sync.publisher.publishTablistRequest() @@ -79,6 +79,7 @@ class PrefixesMinestom private constructor( while (isActive) { delay(30.seconds) MinecraftServer.getSchedulerManager().scheduleNextTick { manager.publishAll() } + tablist.expire() } } } diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt index ded800c..f2ace28 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/display/MinestomDisplayManager.kt @@ -49,12 +49,11 @@ class MinestomDisplayManager( if (key.viewer == id) teams.remove(key) else removeTeam(key) } - entries.remove(id) - prefixes.sync?.publisher?.publishTablistRemove(id) + unpublish(id) } fun clear() { - entries.clear() + unpublishAll() teams.keys.toList().forEach(::removeTeam) onlinePlayers().forEach { player -> player.displayName = null } } @@ -84,8 +83,12 @@ class MinestomDisplayManager( } } - fun publishAll(force: Boolean = false) { - onlinePlayers().forEach { player -> publish(player, force) } + fun publishAll() { + onlinePlayers().forEach { player -> publish(player, true) } + } + + fun unpublishAll() { + entries.keys.forEach(::unpublish) } private fun showTo(player: Player, viewer: Player, team: TeamsPacket?) { @@ -131,6 +134,11 @@ class MinestomDisplayManager( } } + private fun unpublish(id: UUID) { + if (entries.remove(id) == null) return + prefixes.sync?.publisher?.publishTablistRemove(id) + } + private fun createTeam(player: Player, data: PrefixesPlayerData): TeamsPacket? { val features = prefixes.config.get().features return when { diff --git a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt index 13b0eaa..bad7428 100644 --- a/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt +++ b/prefixes-minestom/src/main/kotlin/app/simplecloud/prefixes/minestom/platform/MinestomPrefixesListener.kt @@ -17,6 +17,7 @@ class MinestomPrefixesListener( onAllPlayersUpdate() if (!prefixes.config.get().isTablistSynced()) { + manager.unpublishAll() tablist.clear() return } diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt index 6a6ec41..804a983 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/PrefixesPaper.kt @@ -15,6 +15,7 @@ import org.bukkit.plugin.java.JavaPlugin import org.incendo.cloud.execution.ExecutionCoordinator import org.incendo.cloud.paper.PaperCommandManager import space.chunks.customname.paper.CustomNameManagerImpl +import java.util.concurrent.TimeUnit class PrefixesPaper : JavaPlugin() { @@ -51,10 +52,13 @@ class PrefixesPaper : JavaPlugin() { val sync = prefixes.sync ?: return sync.subscriber.subscribeChatMessage { message -> Bukkit.getServer().sendMessage(message) } - sync.subscriber.subscribeTablist(tablist::update, tablist::remove) { manager.publishAll(true) } + sync.subscriber.subscribeTablist(tablist::update, tablist::remove) { manager.publishAll() } sync.publisher.publishTablistRequest() - Bukkit.getGlobalRegionScheduler().runAtFixedRate(this, { manager.publishAll() }, 600L, 600L) + Bukkit.getAsyncScheduler().runAtFixedRate(this, { + manager.publishAll() + tablist.expire() + }, 30L, 30L, TimeUnit.SECONDS) } private fun registerCommands(prefixes: Prefixes) { diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt index 27bb437..9c470a5 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/display/PaperDisplayManager.kt @@ -55,12 +55,11 @@ class PaperDisplayManager( if (key.viewer == id) teams.remove(key) else removeTeam(key) } - entries.remove(id) - prefixes.sync?.publisher?.publishTablistRemove(id) + unpublish(id) } fun clear() { - entries.clear() + unpublishAll() teams.keys.toList().forEach(::removeTeam) Bukkit.getOnlinePlayers().forEach { player -> player.playerListName(null) } } @@ -88,12 +87,16 @@ class PaperDisplayManager( } } - fun publishAll(force: Boolean = false) { + fun publishAll() { Bukkit.getOnlinePlayers().forEach { player -> - player.scheduler.run(plugin, { publish(player, force) }, null) + player.scheduler.run(plugin, { publish(player, true) }, null) } } + fun unpublishAll() { + entries.keys.forEach(::unpublish) + } + private fun showTo(player: Player, viewer: Player, team: PaperPlayerTeam?) { val viewerData = registry.getViewerData(player.uniqueId, viewer.uniqueId) if (viewerData == null) { @@ -130,6 +133,11 @@ class PaperDisplayManager( } } + private fun unpublish(id: UUID) { + if (entries.remove(id) == null) return + prefixes.sync?.publisher?.publishTablistRemove(id) + } + private fun createTeam(player: Player, data: PrefixesPlayerData): PaperPlayerTeam? { val features = prefixes.config.get().features return when { diff --git a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt index fbb3179..134f7c5 100644 --- a/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt +++ b/prefixes-paper/src/main/kotlin/app/simplecloud/prefixes/paper/platform/PaperPrefixesListener.kt @@ -19,6 +19,7 @@ class PaperPrefixesListener( onAllPlayersUpdate() if (!prefixes.config.get().isTablistSynced()) { + manager.unpublishAll() tablist.clear() return } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt index 2563347..f745715 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/PrefixesSync.kt @@ -6,6 +6,7 @@ import app.simplecloud.prefixes.shared.PrefixesConstants import io.nats.client.Connection import io.nats.client.Nats import io.nats.client.Options +import java.time.Duration class PrefixesSync( config: ConfigurationFactory @@ -21,6 +22,7 @@ class PrefixesSync( fun shutdown() { runCatching { logger.info("Shutdown prefixes sync...") + connection.flush(Duration.ofSeconds(1)) subscriber.close() connection.close() }.onFailure { throwable -> @@ -31,12 +33,15 @@ class PrefixesSync( } private fun createNatsConnection(): Connection { - return Nats.connect( + val nats = Nats.connect( Options.builder() .server(System.getenv().getOrDefault("SIMPLECLOUD_NATS_URL", "wss://nats.simplecloud.app:443")) .userInfo(System.getenv().getOrDefault("SIMPLECLOUD_NETWORK_ID", "default"), System.getenv().getOrDefault("SIMPLECLOUD_NETWORK_SECRET", "")) .maxReconnects(-1) .build() ) + + logger.info("Connected to NATS server (${nats.connectedUrl})") + return nats } } diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt index 0bdc454..3d5ff92 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/SyncPublisher.kt @@ -31,7 +31,7 @@ class SyncPublisher( TablistEntryMapper.toDefinition(entry) } - fun publishTablistRemove(uniqueId: UUID) = publish(PrefixesConfig::isTablistSynced, PrefixesSubjects.TABLIST_REMOVE) { + fun publishTablistRemove(uniqueId: UUID) = publish({ true }, PrefixesSubjects.TABLIST_REMOVE) { TablistEntryRemoveEvent.newBuilder() .setPlayerId(uniqueId.toString()) .build() diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SourcedTablistEntry.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SourcedTablistEntry.kt index aaf2a07..aaab20a 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SourcedTablistEntry.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SourcedTablistEntry.kt @@ -1,6 +1,9 @@ package app.simplecloud.prefixes.shared.sync.tablist +import kotlin.time.TimeMark + data class SourcedTablistEntry( val publisherId: String, - val entry: TablistEntry + val entry: TablistEntry, + val updatedAt: TimeMark ) \ No newline at end of file diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SyncedTablist.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SyncedTablist.kt index 533c8e5..b65c8a6 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SyncedTablist.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/sync/tablist/SyncedTablist.kt @@ -2,6 +2,8 @@ package app.simplecloud.prefixes.shared.sync.tablist import java.util.UUID import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration.Companion.seconds +import kotlin.time.TimeSource abstract class SyncedTablist { @@ -10,7 +12,7 @@ abstract class SyncedTablist { fun update(publisherId: String, entry: TablistEntry) { if (isOnline(entry.uniqueId)) return - val previous = entries.put(entry.uniqueId, SourcedTablistEntry(publisherId, entry))?.entry + val previous = entries.put(entry.uniqueId, SourcedTablistEntry(publisherId, entry, TimeSource.Monotonic.markNow()))?.entry if (previous != entry) { broadcast(createInfoPacket(listOf(entry))) } @@ -35,6 +37,12 @@ abstract class SyncedTablist { entries.keys.toList().forEach { id -> remove(id) } } + fun expire() { + entries.entries + .filter { (_, sourced) -> sourced.updatedAt.elapsedNow() > 90.seconds } + .forEach { (id, sourced) -> if (entries.remove(id, sourced)) hide(sourced.entry) } + } + fun sync(player: P) { val visible = entries.values.map(SourcedTablistEntry::entry).filter { entry -> !isOnline(entry.uniqueId) } From e415e566240be3bbfdc62d1143c7a37366140459 Mon Sep 17 00:00:00 2001 From: Janis Date: Sun, 11 Oct 2026 14:16:13 +0200 Subject: [PATCH 22/22] build: check for custom-names submodule --- build.gradle.kts | 4 ++++ .../shared/platform/PrefixesListener.kt | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index e7f65cf..28cd48d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,10 @@ plugins { alias(libs.plugins.shadow) } +check(file("custom-names/custom-names-api/src/main/kotlin").isDirectory) { + "The custom-names submodule is missing, run 'git submodule update --init' first" +} + allprojects { group = "app.simplecloud.plugin" version = "1.1.0" diff --git a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt index d03a596..98d9246 100644 --- a/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt +++ b/prefixes-shared/src/main/kotlin/app/simplecloud/prefixes/shared/platform/PrefixesListener.kt @@ -2,15 +2,33 @@ package app.simplecloud.prefixes.shared.platform import java.util.UUID +/** + * Represents a listener for prefixes events on a platform. + */ interface PrefixesListener { + /** + * Called after the config and messages were reloaded. + */ fun onReload() + /** + * Called when prefixes shuts down. + */ fun onShutdown() + /** + * Called when the group of a player has to be reloaded. + */ fun onPlayerUpdate(id: UUID) + /** + * Called when manually set values of a player changed. + */ fun onPlayerDataChange(id: UUID) + /** + * Called when the groups of all players have to be reloaded. + */ fun onAllPlayersUpdate() }