From 2ebce56d4fa44fb12ec15a436feea6c43f7ecd8b Mon Sep 17 00:00:00 2001 From: ThatGravyBoat Date: Wed, 9 Oct 2024 08:14:16 -0230 Subject: Improvement: Add Suggestion Provider and guild support in tab complete (#2637) Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com> --- .../features/commands/TabCompleteConfig.java | 12 +- .../java/at/hannibal2/skyhanni/data/GuildAPI.kt | 6 +- .../skyhanni/features/commands/PartyCommands.kt | 29 ----- .../commands/suggestions/SuggestionEntry.kt | 31 ++++++ .../commands/suggestions/SuggestionProvider.kt | 83 ++++++++++++++ .../commands/tabcomplete/PlayerTabComplete.kt | 122 +++++++++++++-------- .../features/commands/tabcomplete/TabComplete.kt | 18 ++- 7 files changed, 208 insertions(+), 93 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/features/commands/suggestions/SuggestionEntry.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/features/commands/suggestions/SuggestionProvider.kt (limited to 'src') diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/commands/TabCompleteConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/commands/TabCompleteConfig.java index 790224761..388cec87d 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/commands/TabCompleteConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/commands/TabCompleteConfig.java @@ -36,22 +36,22 @@ public class TabCompleteConfig { public boolean party = true; @Expose - @ConfigOption(name = "VIP Visits", desc = "Tab-complete the visit to special users with cake souls on it.") + @ConfigOption(name = "Guild", desc = "Tab-complete Guild Members.") @ConfigEditorBoolean @FeatureToggle - public boolean vipVisits = true; + public boolean guild = false; @Expose - @ConfigOption(name = "/gfs Sack", desc = "Tab-complete §e/gfs §7sack items.") + @ConfigOption(name = "VIP Visits", desc = "Tab-complete the visit to special users with cake souls on it.") @ConfigEditorBoolean @FeatureToggle - public boolean gfsSack = true; + public boolean vipVisits = true; @Expose - @ConfigOption(name = "Party Commands", desc = "Tab-complete commonly used party commands.") + @ConfigOption(name = "/gfs Sack", desc = "Tab-complete §e/gfs §7sack items.") @ConfigEditorBoolean @FeatureToggle - public boolean partyCommands = true; + public boolean gfsSack = true; @Expose @ConfigOption(name = "View Recipe", desc = "Tab-complete item IDs in the the Hypixel command §e/viewrecipe§7. Only items with recipes are tab completed.") diff --git a/src/main/java/at/hannibal2/skyhanni/data/GuildAPI.kt b/src/main/java/at/hannibal2/skyhanni/data/GuildAPI.kt index 541eb323c..1e98b61e4 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/GuildAPI.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/GuildAPI.kt @@ -38,7 +38,7 @@ object GuildAPI { } } - fun isInGuild(name: String) = ProfileStorageData.playerSpecific?.guildMembers?.let { - name in it - } ?: false + fun isInGuild(name: String) = name in getAllMembers() + + fun getAllMembers() = ProfileStorageData.playerSpecific?.guildMembers ?: emptyList() } diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/PartyCommands.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/PartyCommands.kt index e7b3333dd..3cbd43a78 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/commands/PartyCommands.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/PartyCommands.kt @@ -2,7 +2,6 @@ package at.hannibal2.skyhanni.features.commands import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator -import at.hannibal2.skyhanni.data.FriendAPI import at.hannibal2.skyhanni.data.PartyAPI import at.hannibal2.skyhanni.data.PartyAPI.partyLeader import at.hannibal2.skyhanni.data.PartyAPI.transferVoluntaryPattern @@ -11,7 +10,6 @@ import at.hannibal2.skyhanni.events.MessageSendToServerEvent import at.hannibal2.skyhanni.features.misc.limbo.LimboTimeTracker import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils -import at.hannibal2.skyhanni.utils.EntityUtils import at.hannibal2.skyhanni.utils.HypixelCommands import at.hannibal2.skyhanni.utils.LorenzUtils import at.hannibal2.skyhanni.utils.RegexUtils.matches @@ -115,25 +113,9 @@ object PartyCommands { if (command == "pk" || command == "pt" || command == "pp" && config.shortCommands) { return PartyAPI.partyMembers } - - if (command == "p" || command == "party") { - val friends = if (config.tabComplete.friends) { - FriendAPI.getAllFriends().filter { it.bestFriend || !config.tabComplete.onlyBestFriends }.map { it.name } - } else { - emptyList() - } - val allOnLobby = EntityUtils.getPlayerEntities().map { it.name } - return friends + getPartyCommands() + allOnLobby - } return null } - private fun getPartyCommands(): List { - return if (config.tabComplete.partyCommands && PartyAPI.partyMembers.isNotEmpty()) { - otherPartyCommands - } else emptyList() - } - @SubscribeEvent fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { event.move(5, "commands.usePartyTransferAlias", "commands.shortCommands") @@ -157,14 +139,3 @@ object PartyCommands { ) } } - -private val otherPartyCommands = listOf( - "Disband", - "KickOffline", - "Leave", - "List", - "Mute", - "Private", - "Warp", - "Settings", -) diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/suggestions/SuggestionEntry.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/suggestions/SuggestionEntry.kt new file mode 100644 index 000000000..74fcb0ef0 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/suggestions/SuggestionEntry.kt @@ -0,0 +1,31 @@ +package at.hannibal2.skyhanni.features.commands.suggestions + +interface SuggestionEntry { + + val suggestions: List + + fun isEntryFor(argument: String): Boolean { + return this.suggestions.any { it.equals(argument, ignoreCase = true) } + } +} + +data class LiteralSuggestionEntry(override val suggestions: List) : SuggestionEntry + +data class CompositeSuggestionEntry(val entries: List) : SuggestionEntry { + override val suggestions: List get() = entries.flatMap { it.suggestions } + override fun isEntryFor(argument: String): Boolean = entries.any { it.isEntryFor(argument) } +} + +data class ParentSuggestionEntry(val parent: SuggestionEntry, val children: List) : SuggestionEntry { + override val suggestions: List get() = parent.suggestions + override fun isEntryFor(argument: String): Boolean = parent.isEntryFor(argument) +} + +data class LazySuggestionEntry(val supplier: MutableList.() -> Unit) : SuggestionEntry { + override val suggestions: List get() = mutableListOf().apply { supplier() } +} + +data class ConditionalSuggestionEntry(val condition: () -> Boolean, val entry: SuggestionEntry) : SuggestionEntry { + override val suggestions: List get() = if (condition()) entry.suggestions else emptyList() + override fun isEntryFor(argument: String): Boolean = entry.isEntryFor(argument) +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/suggestions/SuggestionProvider.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/suggestions/SuggestionProvider.kt new file mode 100644 index 000000000..eda4c704c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/suggestions/SuggestionProvider.kt @@ -0,0 +1,83 @@ +package at.hannibal2.skyhanni.features.commands.suggestions + +class SuggestionProvider { + + private val entry = mutableListOf() + + fun add(entry: SuggestionEntry) { + this.entry.add(entry) + } + + private fun List.getEntryForPath(path: List): SuggestionEntry? { + val entry = this.firstOrNull { it.isEntryFor(path.first()) } + val remainingPath = path.drop(1) + if (remainingPath.isNotEmpty()) { + if (entry is ParentSuggestionEntry) { + return entry.children.getEntryForPath(remainingPath) + } + return null + } else if (entry is ParentSuggestionEntry) { + return CompositeSuggestionEntry(entry.children) + } + return null + } + + fun getSuggestions(command: String): List { + val arguments = command.lowercase().split(" ") + val last = arguments.lastOrNull() ?: "" + val suggestions = mutableListOf() + if (arguments.size != 1) { + entry.getEntryForPath(arguments.dropLast(1))?.suggestions?.let { suggestions.addAll(it) } + } else { + entry.forEach { suggestions.addAll(it.suggestions) } + } + return suggestions.filter { it.startsWith(last, ignoreCase = true) } + } + + companion object { + + fun build(builder: Builder.() -> Unit): SuggestionProvider { + val b = Builder() + b.builder() + return b.build() + } + } +} + +class Builder { + private val entries = mutableListOf() + + fun add(entry: SuggestionEntry) { + entries.add(entry) + } + + fun conditional(condition: () -> Boolean, builder: Builder.() -> Unit) { + val childBuilder = Builder() + childBuilder.builder() + add(ConditionalSuggestionEntry(condition, CompositeSuggestionEntry(childBuilder.entries))) + } + + fun literal(vararg literals: String) { + add(LiteralSuggestionEntry(literals.toList())) + } + + fun lazy(supplier: () -> List) { + add(LazySuggestionEntry { addAll(supplier()) }) + } + + fun group(vararg children: SuggestionEntry) { + add(CompositeSuggestionEntry(children.toList())) + } + + fun parent(vararg literals: String, children: Builder.() -> Unit) { + val childBuilder = Builder() + childBuilder.children() + add(ParentSuggestionEntry(LiteralSuggestionEntry(literals.toList()), childBuilder.entries)) + } + + fun build(): SuggestionProvider { + val provider = SuggestionProvider() + entries.forEach { provider.add(it) } + return provider + } +} diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/PlayerTabComplete.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/PlayerTabComplete.kt index 070ab478e..95acec9a6 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/PlayerTabComplete.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/PlayerTabComplete.kt @@ -3,9 +3,12 @@ package at.hannibal2.skyhanni.features.commands.tabcomplete import at.hannibal2.skyhanni.SkyHanniMod import at.hannibal2.skyhanni.config.ConfigUpdaterMigrator import at.hannibal2.skyhanni.data.FriendAPI +import at.hannibal2.skyhanni.data.GuildAPI import at.hannibal2.skyhanni.data.PartyAPI import at.hannibal2.skyhanni.data.jsonobjects.repo.VipVisitsJson import at.hannibal2.skyhanni.events.RepositoryReloadEvent +import at.hannibal2.skyhanni.features.commands.suggestions.LazySuggestionEntry +import at.hannibal2.skyhanni.features.commands.suggestions.SuggestionProvider import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.EntityUtils import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -15,68 +18,97 @@ object PlayerTabComplete { private val config get() = SkyHanniMod.feature.misc.commands.tabComplete private var vipVisits = listOf() - private val ignoredCommandCategories = mapOf( - "f" to listOf(PlayerCategory.FRIENDS), - "friend" to listOf(PlayerCategory.FRIENDS), - "msg" to listOf(), - "w" to listOf(), - "tell" to listOf(), - "boop" to listOf(), + private val friendsEntry = lazyEntry { FriendAPI.getAllFriends().map { it.name } } + private val partyMembersEntry = lazyEntry { PartyAPI.partyMembers } + private val guildMembersEntry = lazyEntry { GuildAPI.getAllMembers() } + private val vipVisitsEntry = lazyEntry { vipVisits } + private val islandPlayersEntry = lazyEntry { EntityUtils.getPlayerEntities().map { it.name } } + + private val suggestions = SuggestionProvider.build { + parent("f", "friend") { + parent("accept", "add", "deny") { add(getExcluding(PlayerCategory.FRIENDS)) } + parent("best") { add(friendsEntry) } + parent("remove", "nickname") { add(friendsEntry) } + parent("list") { literal("best") } + literal("help", "notifications", "removeall", "requests") + } - "visit" to listOf(), - "invite" to listOf(), - "ah" to listOf(), + parent("g", "guild") { + parent("invite") { add(getExcluding(PlayerCategory.GUILD)) } + parent("kick", "transfer", "setrank", "promote", "demote") { add(guildMembersEntry) } + parent("mute", "unmute") { + add(guildMembersEntry) + literal("everyone") + } + parent("member") { add(guildMembersEntry) } + literal( + "top", "toggle", "tagcolor", "tag", "slow", "settings", "rename", "quest", "permissions", "party", "onlinemode", + "online", "officerchat", "notifications", "mypermissions", "motd", "menu", "members", "log", "leave", "info", "history", + "help", "discord", "disband", "create", "chat", "accept", + ) + } - "pv" to listOf(), // NEU's Profile Viewer - "shmarkplayer" to listOf(), // SkyHanni's Mark Player + parent("p", "party") { + parent("accept", "invite") { add(getExcluding(PlayerCategory.PARTY)) } + conditional({ PartyAPI.partyMembers.isNotEmpty() }) { + parent("kick", "demote", "promote", "transfer") { add(partyMembersEntry) } + literal("chat", "disband", "kickoffline", "leave", "list", "mute", "poll", "private", "settings", "warp") + } + } - "trade" to listOf(PlayerCategory.FRIENDS, PlayerCategory.PARTY), - ) + parent("w", "msg", "tell", "boop") { add(getExcluding()) } - @SubscribeEvent - fun onRepoReload(event: RepositoryReloadEvent) { - val data = event.getConstant("VipVisits") - vipVisits = data.vipVisits - } + parent("visit") { + add(getExcluding()) + conditional({ config.vipVisits }) { + add(vipVisitsEntry) + } + } - @SubscribeEvent - fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { - event.move(2, "misc.tabCompleteCommands", "commands.tabComplete") + parent("invite") { add(getExcluding()) } + parent("ah") { add(getExcluding()) } + + parent("pv") { add(getExcluding()) } + parent("shmarkplayer") { add(getExcluding()) } + + parent("trade") { add(islandPlayersEntry) } } enum class PlayerCategory { FRIENDS, ISLAND_PLAYERS, PARTY, + GUILD, } - fun handleTabComplete(command: String): List? { - val ignoredCategories = ignoredCommandCategories[command] ?: return null - - return buildList { + private fun getExcluding(vararg categories: PlayerCategory) = LazySuggestionEntry { + if (config.friends && PlayerCategory.FRIENDS !in categories) { + addAll(FriendAPI.getAllFriends().filter { it.bestFriend || !config.onlyBestFriends }.map { it.name }) + } + if (config.islandPlayers && PlayerCategory.ISLAND_PLAYERS !in categories) { + addAll(EntityUtils.getPlayerEntities().map { it.name }) + } + if (config.party && PlayerCategory.PARTY !in categories) { + addAll(PartyAPI.partyMembers) + } + if (config.guild && PlayerCategory.GUILD !in categories) { + addAll(GuildAPI.getAllMembers()) + } + } - if (config.friends && PlayerCategory.FRIENDS !in ignoredCategories) { - FriendAPI.getAllFriends().filter { it.bestFriend || !config.onlyBestFriends }.forEach { add(it.name) } - } + private fun lazyEntry(getter: () -> List) = LazySuggestionEntry { addAll(getter()) } - if (config.islandPlayers && PlayerCategory.ISLAND_PLAYERS !in ignoredCategories) { - for (entity in EntityUtils.getPlayerEntities()) { - add(entity.name) - } - } + fun handleTabComplete(command: String): List? = suggestions.getSuggestions(command).takeIf { it.isNotEmpty() }?.distinct() - if (config.party && PlayerCategory.PARTY !in ignoredCategories) { - for (member in PartyAPI.partyMembers) { - add(member) - } - } + @SubscribeEvent + fun onRepoReload(event: RepositoryReloadEvent) { + val data = event.getConstant("VipVisits") + vipVisits = data.vipVisits + } - if (config.vipVisits && command == "visit") { - for (visit in vipVisits) { - add(visit) - } - } - } + @SubscribeEvent + fun onConfigFix(event: ConfigUpdaterMigrator.ConfigFixEvent) { + event.move(2, "misc.tabCompleteCommands", "commands.tabComplete") } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt index 602c4a3b9..aa66b6937 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/commands/tabcomplete/TabComplete.kt @@ -14,20 +14,18 @@ object TabComplete { @SubscribeEvent fun handleTabComplete(event: TabCompletionEvent) { val splits = event.leftOfCursor.split(" ") - if (splits.size > 1) { - var command = splits.first().lowercase() - if (command.startsWith("/")) { - command = command.substring(1) - customTabComplete(command)?.let { - event.addSuggestions(it) - } - } + if (splits.size <= 1) return + var command = splits.first().lowercase() + if (!command.startsWith("/")) return + command = command.substring(1) + customTabComplete(event.leftOfCursor.substring(1), command)?.let { + event.addSuggestions(it) } } - private fun customTabComplete(command: String): List? { + private fun customTabComplete(fullCommand: String, command: String): List? { GetFromSacksTabComplete.handleTabComplete(command)?.let { return it } - PlayerTabComplete.handleTabComplete(command)?.let { return it } + PlayerTabComplete.handleTabComplete(fullCommand)?.let { return it } CollectionTracker.handleTabComplete(command)?.let { return it } PartyCommands.customTabComplete(command)?.let { return it } ViewRecipeCommand.customTabComplete(command)?.let { return it } -- cgit