418 lines
20 KiB
Kotlin
418 lines
20 KiB
Kotlin
package net.hareworks.ghostdisplays.command
|
|
|
|
import java.time.format.DateTimeFormatter
|
|
import java.util.Locale
|
|
import java.util.UUID
|
|
import net.hareworks.ghostdisplays.display.DisplayManager
|
|
import net.hareworks.ghostdisplays.display.DisplayManager.DisplayOperationException
|
|
import net.hareworks.ghostdisplays.display.DisplayKind
|
|
import net.hareworks.ghostdisplays.display.EditSessionManager
|
|
import net.hareworks.kommand_lib.KommandLib
|
|
import net.hareworks.kommand_lib.kommand
|
|
import net.hareworks.permits_lib.bukkit.MutationSession
|
|
import org.bukkit.Bukkit
|
|
import org.bukkit.Location
|
|
import org.bukkit.Material
|
|
import org.bukkit.block.data.BlockData
|
|
import org.bukkit.command.CommandSender
|
|
import org.bukkit.entity.Player
|
|
import org.bukkit.inventory.ItemStack
|
|
import org.bukkit.permissions.PermissionDefault
|
|
import org.bukkit.plugin.java.JavaPlugin
|
|
|
|
object CommandRegistrar {
|
|
fun register(
|
|
plugin: JavaPlugin,
|
|
manager: DisplayManager,
|
|
editSessions: EditSessionManager,
|
|
permissionSession: MutationSession
|
|
): KommandLib =
|
|
kommand(plugin) {
|
|
permissions {
|
|
namespace = "ghostdisplays"
|
|
defaultValue = PermissionDefault.OP
|
|
wildcard = true
|
|
session(permissionSession)
|
|
}
|
|
|
|
command("ghostdisplay", listOf("gdisplay", "gdisp")) {
|
|
description = "Manage GhostDisplays entities"
|
|
permission = "ghostdisplays.command"
|
|
|
|
executes { sender.showUsage() }
|
|
|
|
literal("help") {
|
|
executes { sender.showUsage() }
|
|
}
|
|
|
|
literal("create") {
|
|
literal("text") {
|
|
string("id") {
|
|
executes {
|
|
val player = sender.requirePlayer() ?: return@executes
|
|
val id = argument<String>("id")
|
|
val location = player.anchorLocation()
|
|
try {
|
|
val display = manager.createTextDisplay(id, location, "", player)
|
|
editSessions.begin(player, display.id)
|
|
sender.success("Text display '${display.id}' created at ${describe(location)}. Type text in chat (or 'cancel') to set content.")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to create text display.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
literal("block") {
|
|
string("id") {
|
|
string("state") {
|
|
executes {
|
|
val player = sender.requirePlayer() ?: return@executes
|
|
val id = argument<String>("id")
|
|
val state = argument<String>("state")
|
|
try {
|
|
val blockData = parseBlockData(state)
|
|
val display = manager.createBlockDisplay(id, player.anchorLocation(), blockData, player)
|
|
sender.success("Block display '${display.id}' created with $state at ${describe(display.location)}.")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to create block display.")
|
|
} catch (ex: IllegalArgumentException) {
|
|
sender.failure("Invalid block data '$state': ${ex.message}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
literal("item") {
|
|
string("id") {
|
|
string("material") {
|
|
executes {
|
|
val player = sender.requirePlayer() ?: return@executes
|
|
val id = argument<String>("id")
|
|
val materialToken = argument<String>("material")
|
|
val material = Material.matchMaterial(materialToken.uppercase(Locale.ROOT))
|
|
?: return@executes sender.failure("Unknown material '$materialToken'.")
|
|
if (!material.isItem) {
|
|
return@executes sender.failure("$materialToken cannot be used as an item display.")
|
|
}
|
|
try {
|
|
val item = ItemStack(material)
|
|
val display = manager.createItemDisplay(id, player.anchorLocation(), item, player)
|
|
sender.success("Item display '${display.id}' created with ${material.name.lowercase()} at ${describe(display.location)}.")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to create item display.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
literal("delete") {
|
|
string("id") {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
try {
|
|
manager.delete(id)
|
|
sender.success("Display '$id' removed.")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to delete '$id'.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
literal("list") {
|
|
executes {
|
|
val entries = manager.listDisplays()
|
|
if (entries.isEmpty()) {
|
|
sender.info("No displays registered.")
|
|
return@executes
|
|
}
|
|
sender.info("Displays (${entries.size}):")
|
|
entries.forEach {
|
|
sender.info(" - ${it.id} [${it.kind}] @ ${describe(it.location)} viewers=${it.controller.viewerIds().size}")
|
|
}
|
|
}
|
|
}
|
|
|
|
literal("info") {
|
|
string("id") {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
val display = manager.findDisplay(id)
|
|
if (display == null) {
|
|
sender.failure("Display '$id' not found.")
|
|
return@executes
|
|
}
|
|
sender.info("Display '${display.id}' (${display.kind})")
|
|
sender.info(" Location: ${describe(display.location)}")
|
|
sender.info(" Created: ${DATE_FORMAT.format(display.createdAt)} by ${display.createdBy?.let { Bukkit.getOfflinePlayer(it).name ?: it } ?: "unknown"}")
|
|
val viewers = display.controller.viewerIds()
|
|
sender.info(" Viewers (${viewers.size}): ${resolveNames(viewers).ifEmpty { "none" }}")
|
|
val audiences = display.audienceBindings()
|
|
sender.info(" Audiences (${audiences.size}): ${if (audiences.isEmpty()) "none" else audiences.joinToString { it.description }}")
|
|
sender.info(" Content: ${manager.describeContent(display)}")
|
|
}
|
|
}
|
|
}
|
|
|
|
literal("viewer") {
|
|
literal("add") {
|
|
string("id") {
|
|
players("targets") {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
val targets = argument<List<Player>>("targets")
|
|
try {
|
|
manager.showToPlayers(id, targets)
|
|
sender.success("Showing '$id' to ${targets.size} player(s).")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to update viewers.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
literal("remove") {
|
|
string("id") {
|
|
players("targets") {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
val targets = argument<List<Player>>("targets")
|
|
try {
|
|
manager.hideFromPlayers(id, targets)
|
|
sender.success("Hid '$id' from ${targets.size} player(s).")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to update viewers.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
literal("clear") {
|
|
string("id") {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
try {
|
|
manager.clearViewers(id)
|
|
sender.success("Cleared active viewers for '$id'.")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to clear viewers.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
literal("text") {
|
|
literal("edit") {
|
|
string("id") {
|
|
executes {
|
|
val player = sender.requirePlayer() ?: return@executes
|
|
val id = argument<String>("id")
|
|
val display = manager.findDisplay(id)
|
|
if (display == null || display.kind != DisplayKind.TEXT) {
|
|
sender.failure("Text display '$id' not found.")
|
|
return@executes
|
|
}
|
|
editSessions.begin(player, display.id)
|
|
sender.info("Enter new MiniMessage text in chat for '${display.id}'. Type 'cancel' to abort.")
|
|
}
|
|
}
|
|
}
|
|
literal("set") {
|
|
string("id") {
|
|
string("content") {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
val token = argument<String>("content")
|
|
val raw = token.replace('_', ' ').replace("\\n", "\n")
|
|
try {
|
|
manager.updateText(id, raw)
|
|
sender.success("Updated text for '$id'. Use /ghostdisplay text edit $id for multi-line content.")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to update text.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
literal("cancel") {
|
|
executes {
|
|
val player = sender.requirePlayer() ?: return@executes
|
|
if (editSessions.cancel(player)) {
|
|
sender.success("Editing session cancelled.")
|
|
} else {
|
|
sender.failure("You are not editing any display.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
literal("block") {
|
|
literal("set") {
|
|
string("id") {
|
|
string("state") {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
val state = argument<String>("state")
|
|
try {
|
|
val blockData = parseBlockData(state)
|
|
manager.updateBlock(id, blockData)
|
|
sender.success("Block data for '$id' updated to $state.")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to update block display.")
|
|
} catch (ex: IllegalArgumentException) {
|
|
sender.failure("Invalid block data '$state': ${ex.message}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
literal("item") {
|
|
literal("set") {
|
|
string("id") {
|
|
string("material") {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
val materialToken = argument<String>("material")
|
|
val material = Material.matchMaterial(materialToken.uppercase(Locale.ROOT))
|
|
?: return@executes sender.failure("Unknown material '$materialToken'.")
|
|
if (!material.isItem) {
|
|
return@executes sender.failure("$materialToken cannot be used as an item display.")
|
|
}
|
|
try {
|
|
manager.updateItem(id, ItemStack(material))
|
|
sender.success("Item for '$id' set to ${material.name.lowercase()}.")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to update item display.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
literal("audience") {
|
|
literal("permission") {
|
|
literal("add") {
|
|
string("id") {
|
|
string("permission") {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
val perm = argument<String>("permission")
|
|
try {
|
|
manager.addPermissionAudience(id, perm)
|
|
sender.success("Permission audience '$perm' added to '$id'.")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to add permission audience.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
literal("remove") {
|
|
string("id") {
|
|
string("permission") {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
val perm = argument<String>("permission")
|
|
val removed = runCatching { manager.removePermissionAudience(id, perm) }
|
|
.onFailure { sender.failure(it.message ?: "Failed to remove permission audience.") }
|
|
.getOrNull()
|
|
if (removed == true) {
|
|
sender.success("Permission audience '$perm' removed from '$id'.")
|
|
} else if (removed == false) {
|
|
sender.failure("Permission audience '$perm' not found for '$id'.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
literal("near") {
|
|
literal("set") {
|
|
string("id") {
|
|
float("radius", min = 1.0) {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
val radius = argument<Double>("radius")
|
|
try {
|
|
manager.setNearAudience(id, radius)
|
|
sender.success("Radius audience for '$id' set to ${String.format("%.1f", radius)} block(s).")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to update radius audience.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
literal("clear") {
|
|
string("id") {
|
|
executes {
|
|
val id = argument<String>("id")
|
|
try {
|
|
manager.clearAudiences(id)
|
|
sender.success("Audiences cleared for '$id'.")
|
|
} catch (ex: DisplayOperationException) {
|
|
sender.failure(ex.message ?: "Failed to clear audiences.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun CommandSender.showUsage() {
|
|
info("GhostDisplays commands:")
|
|
info(" /ghostdisplay create <text|block|item> ...")
|
|
info(" /ghostdisplay text edit <id> - edit text via chat")
|
|
info(" /ghostdisplay viewer <add|remove|clear> ...")
|
|
info(" /ghostdisplay audience <permission|near|clear> ...")
|
|
info(" /ghostdisplay list | info <id> | delete <id>")
|
|
}
|
|
|
|
private fun Player.anchorLocation(): Location =
|
|
eyeLocation.clone().add(direction.normalize().multiply(1.5))
|
|
|
|
private fun parseBlockData(state: String): BlockData =
|
|
Bukkit.createBlockData(state)
|
|
|
|
private fun describe(location: Location): String =
|
|
"${location.world?.name ?: "unknown"} @ ${location.blockX},${location.blockY},${location.blockZ}"
|
|
|
|
private fun CommandSender.requirePlayer(): Player? {
|
|
val player = this as? Player
|
|
if (player == null) {
|
|
failure("This command can only be used by players.")
|
|
}
|
|
return player
|
|
}
|
|
|
|
private fun CommandSender.info(message: String) {
|
|
sendMessage("§7$message")
|
|
}
|
|
|
|
private fun CommandSender.success(message: String) {
|
|
sendMessage("§a$message")
|
|
}
|
|
|
|
private fun CommandSender.failure(message: String) {
|
|
sendMessage("§c$message")
|
|
}
|
|
|
|
private fun resolveNames(ids: Collection<UUID>): String {
|
|
if (ids.isEmpty()) return ""
|
|
return ids.joinToString { Bukkit.getOfflinePlayer(it).name ?: it.toString() }
|
|
}
|
|
|
|
private val DATE_FORMAT: DateTimeFormatter =
|
|
DateTimeFormatter.ISO_INSTANT
|