diff --git a/README.md b/README.md index 05fef1e..1e04d58 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 64b703c..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.3 +version=2.1.4 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..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 @@ -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(data.frozenAt, 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,6 @@ object FreezeService { fun isFrozen(uuid: UUID): Boolean { return frozenPlayers.getIfPresent(uuid) != null } -} \ No newline at end of file + + fun getFrozenEntries(): Map = frozenPlayers.asMap().toMap() +}