From e0054ea18e9d7f252809a065cd73555a726cd40d Mon Sep 17 00:00:00 2001 From: Nicholas Stonecipher Date: Mon, 15 Jun 2020 19:24:38 -0400 Subject: [PATCH] Add `//signsearch` --- README.md | 5 +- src/main/kotlin/Find.kt | 38 ++--------- src/main/kotlin/RedstoneTools.kt | 47 ++++++++++++- src/main/kotlin/SignSearch.kt | 109 +++++++++++++++++++++++++++++++ src/main/kotlin/Util.kt | 13 ++++ 5 files changed, 176 insertions(+), 36 deletions(-) create mode 100644 src/main/kotlin/SignSearch.kt create mode 100644 src/main/kotlin/Util.kt diff --git a/README.md b/README.md index 41d8c57..083f2b8 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,12 @@ common in redstoning. NOTE: The order of arguments does not matter apart from `count` and `spacing`. -## `//find` +## `//find [mask]` Search a selected region for a specific WorldEdit material mask. +## `//signsearch|//ss [regex]` +Search a selected region for signs that match a provided regex. + ## `/container` Gives the player a chest, barrel, hopper, or furnace with the proper amount of items to provide a signal strength of specified power. diff --git a/src/main/kotlin/Find.kt b/src/main/kotlin/Find.kt index 1a6bfc7..96f9367 100644 --- a/src/main/kotlin/Find.kt +++ b/src/main/kotlin/Find.kt @@ -15,28 +15,19 @@ import com.sk89q.worldedit.function.operation.Operations import com.sk89q.worldedit.function.visitor.RegionVisitor import com.sk89q.worldedit.math.BlockVector3 import com.sk89q.worldedit.util.formatting.component.InvalidComponentException -import com.sk89q.worldedit.util.formatting.component.PaginationBox -import com.sk89q.worldedit.util.formatting.text.Component -import com.sk89q.worldedit.util.formatting.text.TextComponent -import com.sk89q.worldedit.util.formatting.text.event.ClickEvent -import com.sk89q.worldedit.util.formatting.text.event.HoverEvent -import com.sk89q.worldedit.util.formatting.text.format.TextColor -import org.bukkit.ChatColor import org.bukkit.entity.Player import java.util.* import kotlin.collections.HashMap import kotlin.math.ceil val findResults = HashMap>() -const val MAKE_SELECTION_FIRST = "Make a region selection first." @CommandAlias("/find") @Description("Find some shid in selecton") @CommandPermission("redstonetools.find") class Find(private val worldEdit: WorldEdit) : BaseCommand() { - @Default - @CommandCompletion("@find_mask") + @CommandCompletion("@we_mask") @Syntax("[material]") fun find( player: Player, @@ -45,7 +36,7 @@ class Find(private val worldEdit: WorldEdit) : BaseCommand() { doFind(BukkitAdapter.adapt(player), arg) } - @Subcommand("page") + @Subcommand("-p") @CommandCompletion("@find_page") @Syntax("[number]") fun page( @@ -53,7 +44,7 @@ class Find(private val worldEdit: WorldEdit) : BaseCommand() { page: Int ) { val locations = findResults[player.uniqueId] ?: throw RedstoneToolsException(MAKE_SELECTION_FIRST) - val paginationBox = FindPaginationBox(locations, "Results", "//find page %page%") + val paginationBox = LocationsPaginationBox(locations, "Find Results", "//find -p %page%") val component = try { paginationBox.create(page) } catch (e: InvalidComponentException) { @@ -87,32 +78,11 @@ class Find(private val worldEdit: WorldEdit) : BaseCommand() { } } -class FindPaginationBox(private val locations: MutableList, title: String, command: String) : - PaginationBox("${ChatColor.LIGHT_PURPLE}$title", command) { - override fun getComponent(number: Int): Component { - if (number > locations.size) throw IllegalArgumentException("Invalid location index.") - return TextComponent.of("${number}: ${locations[number]}") - .color(TextColor.LIGHT_PURPLE) - .clickEvent(ClickEvent.runCommand("/tp ${locations[number].x} ${locations[number].y} ${locations[number].z}")) - .hoverEvent(HoverEvent.showText(TextComponent.of("Click to teleport"))) - } - - override fun getComponentsSize(): Int = locations.size -} - class FindPageCompletionHandler : CommandCompletions.CommandCompletionHandler { override fun getCompletions(context: BukkitCommandCompletionContext): Collection { val player = context.sender as Player val locations = findResults[player.uniqueId] ?: return emptyList() - return (1..ceil(locations.size / 8f).toInt()).map { it.toString() }.toList() + return (1..ceil(locations.size / 7f).toInt()).map { it.toString() }.toList() } } - -class FindCompletionHandler(worldEdit: WorldEdit) : - CommandCompletions.CommandCompletionHandler { - private val maskFactory = MaskFactory(worldEdit) - override fun getCompletions(context: BukkitCommandCompletionContext): Collection { - return maskFactory.getSuggestions(context.input) - } -} \ No newline at end of file diff --git a/src/main/kotlin/RedstoneTools.kt b/src/main/kotlin/RedstoneTools.kt index 45b576a..3851b09 100644 --- a/src/main/kotlin/RedstoneTools.kt +++ b/src/main/kotlin/RedstoneTools.kt @@ -1,12 +1,23 @@ package redstonetools import co.aikar.commands.* +import com.sk89q.worldedit.WorldEdit import com.sk89q.worldedit.bukkit.WorldEditPlugin +import com.sk89q.worldedit.extension.factory.MaskFactory +import com.sk89q.worldedit.math.BlockVector3 +import com.sk89q.worldedit.util.formatting.component.PaginationBox +import com.sk89q.worldedit.util.formatting.text.Component +import com.sk89q.worldedit.util.formatting.text.TextComponent +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent +import com.sk89q.worldedit.util.formatting.text.format.TextColor import org.bukkit.ChatColor import org.bukkit.Material import org.bukkit.plugin.java.JavaPlugin import java.util.logging.Level +const val MAKE_SELECTION_FIRST = "Make a region selection first." + class RedstoneTools : JavaPlugin() { private fun handleCommandException( @@ -37,14 +48,16 @@ class RedstoneTools : JavaPlugin() { server.pluginManager.registerEvents(WorldEditHelper(this, worldEdit), this) PaperCommandManager(this).apply { commandCompletions.registerCompletion("slabs", SlabCompletionHandler()) - commandCompletions.registerCompletion("find_mask", FindCompletionHandler(worldEdit)) + commandCompletions.registerCompletion("we_mask", MaskCompletionHandler(worldEdit)) commandCompletions.registerCompletion("find_page", FindPageCompletionHandler()) + commandCompletions.registerCompletion("search_page", SearchPageCompletionHandler()) registerThing("Signal strength", { SignalStrength.of(it) }, SignalStrength.values) registerThing("Container", { SignalContainer.of(it) }, SignalContainer.values) setDefaultExceptionHandler(::handleCommandException, false) registerCommands( RStack(worldEdit), Find(worldEdit), + SignSearch(worldEdit), Container(), Slab() ) @@ -108,3 +121,35 @@ class SignalContainer(val material: Material) { } } } + +class MaskCompletionHandler(worldEdit: WorldEdit) : + CommandCompletions.CommandCompletionHandler { + private val maskFactory = MaskFactory(worldEdit) + override fun getCompletions(context: BukkitCommandCompletionContext): Collection { + return maskFactory.getSuggestions(context.input) + } +} + +class LocationsPaginationBox(private val locations: MutableList, title: String, command: String) : + PaginationBox("${ChatColor.LIGHT_PURPLE}$title", command) { + + init { + setComponentsPerPage(7) + } + + override fun getComponent(number: Int): Component { + if (number > locations.size) throw IllegalArgumentException("Invalid location index.") + return TextComponent.of("${number+1}: ${locations[number]}") + .color(TextColor.LIGHT_PURPLE) + .clickEvent(ClickEvent.runCommand("/tp ${locations[number].x} ${locations[number].y} ${locations[number].z}")) + .hoverEvent(HoverEvent.showText(TextComponent.of("Click to teleport"))) + } + + override fun getComponentsSize(): Int = locations.size + + override fun create(page: Int): Component { + super.getContents().append(TextComponent.of("Total Results: ${locations.size}").color(TextColor.GRAY)) + .append(TextComponent.newline()) + return super.create(page) + } +} diff --git a/src/main/kotlin/SignSearch.kt b/src/main/kotlin/SignSearch.kt new file mode 100644 index 0000000..d1c392e --- /dev/null +++ b/src/main/kotlin/SignSearch.kt @@ -0,0 +1,109 @@ +package redstonetools + +import co.aikar.commands.BaseCommand +import co.aikar.commands.BukkitCommandCompletionContext +import co.aikar.commands.CommandCompletions +import co.aikar.commands.annotation.* +import com.sk89q.jnbt.StringTag +import com.sk89q.worldedit.IncompleteRegionException +import com.sk89q.worldedit.WorldEdit +import com.sk89q.worldedit.bukkit.BukkitAdapter +import com.sk89q.worldedit.function.RegionFunction +import com.sk89q.worldedit.function.RegionMaskingFilter +import com.sk89q.worldedit.function.mask.BlockCategoryMask +import com.sk89q.worldedit.function.operation.Operations +import com.sk89q.worldedit.function.visitor.RegionVisitor +import com.sk89q.worldedit.math.BlockVector3 +import com.sk89q.worldedit.util.formatting.component.InvalidComponentException +import com.sk89q.worldedit.util.formatting.text.TextComponent +import com.sk89q.worldedit.util.formatting.text.serializer.gson.GsonComponentSerializer +import com.sk89q.worldedit.world.block.BlockCategories +import org.bukkit.entity.Player +import java.util.* +import kotlin.collections.HashMap +import kotlin.math.ceil + +val searchResults = HashMap>() + +@CommandAlias("/signsearch|/ss") +@Description("Search for text of signs within a selection using a regular expression") +@CommandPermission("redstonetools.signsearch") +class SignSearch(private val worldEdit: WorldEdit) : BaseCommand() { + @Default + @Syntax("[expression]") + fun search( + player: Player, + arg: String + ) { + doSearch(BukkitAdapter.adapt(player), arg) + } + + @Subcommand("-p") + @CommandCompletion("@search_page") + @Syntax("[number]") + fun page( + player: Player, + page: Int + ) { + val locations = searchResults[player.uniqueId] ?: throw RedstoneToolsException(MAKE_SELECTION_FIRST) + val paginationBox = LocationsPaginationBox(locations, "Search Results", "//signsearch -p %page%") + val component = try { + paginationBox.create(page) + } catch (e: InvalidComponentException) { + throw RedstoneToolsException("Invalid page number.") + } + BukkitAdapter.adapt(player).print(component) + } + + private fun doSearch(player: WEPlayer, arg: String) { + val pattern = try { + Regex(arg) + } catch (e: Exception) { + throw RedstoneToolsException("Illegal pattern: " + e.message) + } + val session = worldEdit.sessionManager.get(player) + val selection = try { + session.getSelection(session.selectionWorld) + } catch (e: IncompleteRegionException) { + throw RedstoneToolsException(MAKE_SELECTION_FIRST) + } + val matches = mutableListOf() + val blockMask = BlockCategoryMask(session.selectionWorld, BlockCategories.SIGNS) + val regionFunction = RegionFunction { position -> + val baseBlock = session.selectionWorld.getFullBlock(position) + if (baseBlock.hasNbtData()) { + val compoundTag = baseBlock.nbtData!! + val content = buildString { + for (i in 1..4) { + val textTag = compoundTag.value["Text$i"] as StringTag + val component = GsonComponentSerializer.INSTANCE.deserialize(textTag.value) as TextComponent + append(component.getAllContent()) + } + } + if (content.contains(pattern)) { + matches.add(position) + } + } + false + } + val regionMaskingFilter = RegionMaskingFilter(blockMask, regionFunction) + val regionVisitor = RegionVisitor(selection, regionMaskingFilter) + Operations.complete(regionVisitor) + if (matches.isNotEmpty()) { + searchResults[player.uniqueId] = matches + page(BukkitAdapter.adapt(player), 1) + } else { + searchResults.remove(player.uniqueId) + player.printInfo(TextComponent.of("No results found.")) + } + } +} + +class SearchPageCompletionHandler : + CommandCompletions.CommandCompletionHandler { + override fun getCompletions(context: BukkitCommandCompletionContext): Collection { + val player = context.sender as Player + val locations = searchResults[player.uniqueId] ?: return emptyList() + return (1..ceil(locations.size / 7f).toInt()).map { it.toString() }.toList() + } +} diff --git a/src/main/kotlin/Util.kt b/src/main/kotlin/Util.kt new file mode 100644 index 0000000..293f923 --- /dev/null +++ b/src/main/kotlin/Util.kt @@ -0,0 +1,13 @@ +package redstonetools + +import com.sk89q.worldedit.util.formatting.text.TextComponent + +fun TextComponent.getAllContent(): String { + return if (this.children().isEmpty()) { + this.content() + } else { + this.children().filterIsInstance().joinToString(separator = "") { textComponent -> + textComponent.getAllContent() + } + } +}