From b0040e5741846b301cba0f9bf558e05fc923f0f1 Mon Sep 17 00:00:00 2001 From: Timonso Date: Fri, 19 Jun 2026 14:09:38 +0200 Subject: [PATCH 1/6] feat: implement freeze list command and enhance freeze functionality --- README.md | 4 + gradle.properties | 2 +- .../moderation/tools/PaperCommandManager.kt | 1 + .../tools/commands/FreezeCommand.kt | 3 +- .../tools/commands/FreezeListCommand.kt | 105 ++++++++++++++++++ .../moderation/tools/service/FreezeService.kt | 31 +++++- 6 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/dev/slne/surf/moderation/tools/commands/FreezeListCommand.kt diff --git a/README.md b/README.md index 05fef1e..9d188cd 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ - **`/unfreeze `** โ˜€๏ธ Unfreeze a player. - Permission: `surf.moderation.tools.command.unfreeze` + +- **`/`freezelist``** + ๐Ÿ“‹ View a list of currently frozen players. + - Permission: `surf.moderation.tools.command.freeze` - **`/pingPlayer `** ๐Ÿ“ Ping a player with an auditory indicator and an optional message. diff --git a/gradle.properties b/gradle.properties index 1d2f063..704330f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ kotlin.code.style=official kotlin.stdlib.default.dependency=false org.gradle.parallel=true -version=2.1.2-SNAPSHOT \ No newline at end of file +version=2.1.3-SNAPSHOT \ No newline at end of file diff --git a/src/main/kotlin/dev/slne/surf/moderation/tools/PaperCommandManager.kt b/src/main/kotlin/dev/slne/surf/moderation/tools/PaperCommandManager.kt index 3f06fb3..ac28ee8 100644 --- a/src/main/kotlin/dev/slne/surf/moderation/tools/PaperCommandManager.kt +++ b/src/main/kotlin/dev/slne/surf/moderation/tools/PaperCommandManager.kt @@ -11,5 +11,6 @@ object PaperCommandManager { unfreezeCommand() stopInteractionCommand() pingPlayerCommand() + freezeListCommand() } } \ No newline at end of file diff --git a/src/main/kotlin/dev/slne/surf/moderation/tools/commands/FreezeCommand.kt b/src/main/kotlin/dev/slne/surf/moderation/tools/commands/FreezeCommand.kt index d85e1fd..37a6b03 100644 --- a/src/main/kotlin/dev/slne/surf/moderation/tools/commands/FreezeCommand.kt +++ b/src/main/kotlin/dev/slne/surf/moderation/tools/commands/FreezeCommand.kt @@ -36,7 +36,8 @@ fun freezeCommand() = commandAPICommand("freeze") { return@anyExecutorSuspend } - FreezeService.freeze(targetUuid, duration) + val frozenBy = (sender as? Player)?.uniqueId + FreezeService.freeze(targetUuid, duration, frozenBy, sender.name) withContext(plugin.entityDispatcher(targetPlayer)) { val location = targetPlayer.location diff --git a/src/main/kotlin/dev/slne/surf/moderation/tools/commands/FreezeListCommand.kt b/src/main/kotlin/dev/slne/surf/moderation/tools/commands/FreezeListCommand.kt new file mode 100644 index 0000000..7dd14e2 --- /dev/null +++ b/src/main/kotlin/dev/slne/surf/moderation/tools/commands/FreezeListCommand.kt @@ -0,0 +1,105 @@ +package dev.slne.surf.moderation.tools.commands + +import com.github.shynixn.mccoroutine.folia.launch +import dev.jorel.commandapi.kotlindsl.anyExecutor +import dev.jorel.commandapi.kotlindsl.commandTree +import dev.jorel.commandapi.kotlindsl.getValue +import dev.jorel.commandapi.kotlindsl.integerArgument +import dev.slne.surf.api.core.font.toSmallCaps +import dev.slne.surf.api.core.messages.CommonComponents +import dev.slne.surf.api.core.messages.adventure.buildText +import dev.slne.surf.api.core.messages.adventure.clickRunsCommand +import dev.slne.surf.api.core.messages.adventure.sendText +import dev.slne.surf.api.core.messages.pagination.Pagination +import dev.slne.surf.moderation.tools.plugin +import dev.slne.surf.moderation.tools.service.FreezeService +import dev.slne.surf.moderation.tools.util.PermissionRegistry +import net.kyori.adventure.text.format.TextDecoration +import org.bukkit.Bukkit +import java.time.Duration +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.util.* +import kotlin.time.toKotlinDuration + +private val FREEZE_TIME_FORMATTER: DateTimeFormatter = + DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss").withZone(ZoneId.systemDefault()) + +fun freezeListCommand() = commandTree("freezelist") { + withPermission(PermissionRegistry.COMMAND_FREEZE) + + integerArgument("page", optional = true) { + anyExecutor { executor, args -> + val page: Int? by args + + plugin.launch { + val frozenPlayers = FreezeService.getFrozenEntries() + .entries + .sortedBy { it.value.expiresAt } + + if (frozenPlayers.isEmpty()) { + executor.sendText { + appendErrorPrefix() + error("Es sind aktuell keine Spieler eingefroren.") + } + return@launch + } + + val pagination = + Pagination> { + title { + primary("Eingefrorene Spieler".toSmallCaps(), TextDecoration.BOLD) + spacer(" (${frozenPlayers.size})") + } + rowRenderer { entry, _ -> + val uuid = entry.key + val data = entry.value + val targetName = Bukkit.getOfflinePlayer(uuid).name ?: uuid.toString() + val remaining = Duration.between(Instant.now(), data.expiresAt) + .coerceAtLeast(Duration.ZERO) + + listOf( + buildText { + append(CommonComponents.EM_DASH) + appendSpace() + append { + variableValue(targetName) + hoverEvent(buildText { + info("Eingefroren von: ") + variableValue(data.frozenByName) + appendNewline() + info("Eingefroren am: ") + variableValue(FREEZE_TIME_FORMATTER.format(data.frozenAt)) + appendNewline() + info("Lรคuft ab am: ") + variableValue(FREEZE_TIME_FORMATTER.format(data.expiresAt)) + appendNewline() + info("Verbleibend: ") + appendTime(remaining.toKotlinDuration()) + }) + } + appendSpace() + append { + spacer("[") + error("Entfrieren") + spacer("]") + hoverEvent(buildText { + info("Klicke, um ") + variableValue(targetName) + info(" aufzutauen.") + }) + clickRunsCommand("/unfreeze $targetName") + } + } + ) + } + } + + executor.sendText { + append(pagination.renderComponent(frozenPlayers, page ?: 1)) + } + } + } + } +} diff --git a/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt b/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt index 3dd7927..e957f12 100644 --- a/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt +++ b/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt @@ -3,16 +3,31 @@ package dev.slne.surf.moderation.tools.service import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.Expiry import java.time.Duration +import java.time.Instant import java.util.* object FreezeService { + + data class FreezeData( + val frozenBy: UUID?, + val frozenByName: String, + val frozenAt: Instant, + val expiresAt: Instant, + ) + private val frozenPlayers = Caffeine.newBuilder() .maximumSize(10_000) - .expireAfter(Expiry.writing { _, durationMs -> Duration.ofMillis(durationMs) }) - .build() + .expireAfter(Expiry.writing { _, data -> + Duration.between(Instant.now(), data.expiresAt).coerceAtLeast(Duration.ZERO) + }) + .build() - fun freeze(uuid: UUID, durationMs: Long) { - frozenPlayers.put(uuid, durationMs) + fun freeze(uuid: UUID, durationMs: Long, frozenBy: UUID?, frozenByName: String) { + val now = Instant.now() + frozenPlayers.put( + uuid, + FreezeData(frozenBy, frozenByName, now, now.plusMillis(durationMs)) + ) } fun unfreeze(uuid: UUID) { @@ -22,4 +37,10 @@ object FreezeService { fun isFrozen(uuid: UUID): Boolean { return frozenPlayers.getIfPresent(uuid) != null } -} \ No newline at end of file + + fun getFreezeData(uuid: UUID): FreezeData? = frozenPlayers.getIfPresent(uuid) + + fun getFrozenPlayers(): Collection = frozenPlayers.asMap().keys + + fun getFrozenEntries(): Map = frozenPlayers.asMap().toMap() +} From 2f4b42b55102b6c273afc0625d45ee6023130053 Mon Sep 17 00:00:00 2001 From: Timonso Date: Fri, 19 Jun 2026 14:12:55 +0200 Subject: [PATCH 2/6] refactor: remove unused methods from FreezeService --- .../dev/slne/surf/moderation/tools/service/FreezeService.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt b/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt index e957f12..a489b25 100644 --- a/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt +++ b/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt @@ -38,9 +38,5 @@ object FreezeService { return frozenPlayers.getIfPresent(uuid) != null } - fun getFreezeData(uuid: UUID): FreezeData? = frozenPlayers.getIfPresent(uuid) - - fun getFrozenPlayers(): Collection = frozenPlayers.asMap().keys - fun getFrozenEntries(): Map = frozenPlayers.asMap().toMap() } From ce19f4a63928ba7eea13c8f418e524f8b6d39ab3 Mon Sep 17 00:00:00 2001 From: Timonso <181272711+Timonso-1@users.noreply.github.com> Date: Fri, 19 Jun 2026 14:19:35 +0200 Subject: [PATCH 3/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9d188cd..1e04d58 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ โ˜€๏ธ Unfreeze a player. - Permission: `surf.moderation.tools.command.unfreeze` -- **`/`freezelist``** - ๐Ÿ“‹ View a list of currently frozen players. - - Permission: `surf.moderation.tools.command.freeze` +- **`/freezelist`** + ๐Ÿ“‹ View a list of currently frozen players. + - Permission: `surf.moderation.tools.command.freeze` - **`/pingPlayer `** ๐Ÿ“ Ping a player with an auditory indicator and an optional message. From 241b84d96c5713eb40b75542c54f8899710a9780 Mon Sep 17 00:00:00 2001 From: Timonso Date: Fri, 19 Jun 2026 18:42:45 +0200 Subject: [PATCH 4/6] fix: correct duration calculation for freeze expiration --- .../dev/slne/surf/moderation/tools/service/FreezeService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt b/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt index a489b25..22087f9 100644 --- a/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt +++ b/src/main/kotlin/dev/slne/surf/moderation/tools/service/FreezeService.kt @@ -18,7 +18,7 @@ object FreezeService { private val frozenPlayers = Caffeine.newBuilder() .maximumSize(10_000) .expireAfter(Expiry.writing { _, data -> - Duration.between(Instant.now(), data.expiresAt).coerceAtLeast(Duration.ZERO) + Duration.between(data.frozenAt, data.expiresAt).coerceAtLeast(Duration.ZERO) }) .build() From 592b19f70bb6d21fdc7afbec74a15ae292055ea7 Mon Sep 17 00:00:00 2001 From: Timonso <181272711+Timonso-1@users.noreply.github.com> Date: Sat, 20 Jun 2026 06:50:29 +0200 Subject: [PATCH 5/6] Update gradle.properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 704330f..384501f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ kotlin.code.style=official kotlin.stdlib.default.dependency=false org.gradle.parallel=true -version=2.1.3-SNAPSHOT \ No newline at end of file +version=2.1.4-SNAPSHOT From 0e17daa8e43ce1755be09fb59731cf58a6100beb Mon Sep 17 00:00:00 2001 From: Timonso <181272711+Timonso-1@users.noreply.github.com> Date: Sat, 20 Jun 2026 06:51:53 +0200 Subject: [PATCH 6/6] Update version from 2.1.4-SNAPSHOT to 2.1.4 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 384501f..d2fa762 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ kotlin.code.style=official kotlin.stdlib.default.dependency=false org.gradle.parallel=true -version=2.1.4-SNAPSHOT +version=2.1.4