From 67f2901c2642f19f3d4d37ab0f980b9f56a34a0b Mon Sep 17 00:00:00 2001 From: Gabriel Tofvesson Date: Mon, 27 Sep 2021 01:13:45 +0200 Subject: [PATCH] Implement command handling --- src/main/kotlin/PortalCommand.kt | 232 ++++++++++++++++++++++++++++--- src/main/kotlin/PortalsPlugin.kt | 15 ++ 2 files changed, 231 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/PortalCommand.kt b/src/main/kotlin/PortalCommand.kt index 07967fa..83aef20 100644 --- a/src/main/kotlin/PortalCommand.kt +++ b/src/main/kotlin/PortalCommand.kt @@ -1,33 +1,233 @@ +import net.md_5.bungee.api.chat.TextComponent +import org.bukkit.OfflinePlayer import org.bukkit.command.Command import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender +import org.bukkit.command.TabCompleter +import org.bukkit.entity.Player import org.bukkit.permissions.Permission +import java.util.* + +private const val RESULT_ERROR_NOPORTAL = "Given portal doesn't exist" +private const val RESULT_ERROR_NOINVITE = "Invitation to given portal does not exist" +private const val RESULT_ERROR_PORTALEXISTS = "Portal with this name already exists" +private const val RESULT_ERROR_NOTINVITED = "%s is not invited to portal" +private const val RESULT_ERROR_SELFREF = "Cannot link portal to itself" +private const val RESULT_ERROR_NOPORTALS = "%s has no portals" +private const val RESULT_ERROR_INVITED = "%s is already invited" +private const val RESULT_ERROR_BANOWNER = "Cannot ban portal owner" +private const val RESULT_ERROR_UNKNOWN = "An unknown error occurred" + +private const val RESULT_SUCCESS_NEWPORTAL = "Created portal \"%s\"" +private const val RESULT_SUCCESS_DELPORTAL = "Removed portal \"%s\"" +private const val RESULT_SUCCESS_DELPORTALOTHER = "Removed portal \"%s\", owned by %s" +private const val RESULT_SUCCESS_BAN = "%s has been banned from using portal \"%s\"" +private const val RESULT_SUCCESS_BANOTHER = "%s has been banned from using portal \"%s\", owned by %s" +private const val RESULT_SUCCESS_LINK = "Linked portal \"%s\" to portal \"%s\"" +private const val RESULT_SUCCESS_UNLINK = "Unlinked portal \"%s\"" +private const val RESULT_SUCCESS_INVITE = "Invited %s to portal \"%s\"" +private const val RESULT_SUCCESS_INVITEOTHER = "Invited %s to portal \"%s\", owned by %s" +private const val RESULT_SUCCESS_CANCEL = "Cancelled invitation of %s to portal \"%s\"" +private const val RESULT_SUCCESS_ACCEPT = "Accepted invite from %s to portal \"%s\"" +private const val RESULT_SUCCESS_DECLINE = "Declined invite from %s to portal \"%s\"" + +private const val RESULT_INFO_LIST = "List of portals owned by %s:" + + +private val OfflinePlayer.playerName: String + get() = name ?: "" + class PortalCommand( + private val portalManager: PortalManager, permissionCreate: Permission, permissionRemove: Permission, permissionRemoveOther: Permission, permissionInvite: Permission, permissionInviteOther: Permission, permissionListOther: Permission -): CommandExecutor { +): CommandExecutor, TabCompleter { + // Arg parse node for targeting a portal owned by the sender + private val senderPortalParseNode: ArgNode = + { parsed: List<*>, current: String, sender: CommandSender -> + val portal = portalManager.getPortal(sender as OfflinePlayer, current) + if (portal == null) NodeParseResult.FailResult(RESULT_ERROR_NOPORTAL) + else NodeParseResult.SuccessResult(portal) + } to { parsed: List<*>, sender: CommandSender, current: String -> + portalManager.getPortalsByPartialName(sender as OfflinePlayer, current)?.mapTo(ArrayList(), Portal::name) + } + + // Arg parse node for targeting a portal owned by a named player + private val otherPortalParseNode: ArgNode = + { parsed: List<*>, current: String, _: CommandSender -> + val portal = portalManager.getPortal(parsed.last() as OfflinePlayer, current) + if (portal == null) NodeParseResult.FailResult(RESULT_ERROR_NOPORTAL) + else NodeParseResult.SuccessResult(portal) + } to { parsed: List<*>, _: CommandSender, current: String -> + portalManager.getPortalsByPartialName(parsed.last() as OfflinePlayer, current)?.mapTo(ArrayList(), Portal::name) + } + + private val recipientInviteParseNode: ArgNode = + parse@{ parsed: List<*>, current: String, sender: CommandSender -> + NodeParseResult.SuccessResult( + portalManager.getInvitationsForPlayerFromPlayer(sender as OfflinePlayer, parsed.last() as OfflinePlayer) + ?.firstOrNull { portalManager.getPortal(it.portalID)?.name?.equals(current) == true } + ?: return@parse NodeParseResult.FailResult(RESULT_ERROR_NOINVITE) + ) + } to { parsed: List<*>, sender: CommandSender, current: String -> + portalManager.getInvitationsForPlayerFromPlayer(sender as OfflinePlayer, parsed.last() as OfflinePlayer) + ?.mapNotNull { + val name = portalManager.getPortal(it.portalID)?.name ?: return@mapNotNull null + if (name.startsWith(current)) name + else null + } + } + + private val senderInviteParseNode: ArgNode = + parse@{ parsed: List<*>, current: String, sender: CommandSender -> + NodeParseResult.SuccessResult( + portalManager.getInvitationsForPortal( + portalManager.getPortal(sender as OfflinePlayer, current) + ?: return@parse NodeParseResult.FailResult(RESULT_ERROR_NOPORTAL) + ) + ?.firstOrNull { it.recipient.uniqueId == (parsed.last() as OfflinePlayer).uniqueId } + ?: return@parse NodeParseResult.FailResult(RESULT_ERROR_NOINVITE) + ) + } to { parsed: List<*>, sender: CommandSender, current: String -> + portalManager.getInvitationsForPlayerFromPlayer(parsed.last() as OfflinePlayer, sender as OfflinePlayer) + ?.mapNotNull { + val name = portalManager.getPortal(it.portalID)?.name ?: return@mapNotNull null + if (name.startsWith(current)) name + else null + } + } + private val portalParse = ParseTree() - .branch(PermissionParseBranch(permissionCreate, constantParseNode("create"), PARSE_NODE_STRING, PARSE_NODE_STRING)) // portal create [name] [linkName] - .branch(PermissionParseBranch(permissionCreate, constantParseNode("create"), PARSE_NODE_STRING)) // portal create [name] - .branch(PermissionParseBranch(permissionRemove, constantParseNode("remove"), PARSE_NODE_STRING)) // portal remove [name] - .branch(PermissionParseBranch(permissionRemoveOther, constantParseNode("remove"), PARSE_NODE_STRING, PARSE_NODE_PLAYER)) // portal remove [name] [player] - .branch(PermissionParseBranch(permissionInvite, constantParseNode("invite"), PARSE_NODE_STRING, PARSE_NODE_PLAYER)) // portal invite [name] [player] - .branch(PermissionParseBranch(permissionInviteOther, constantParseNode("invite"), PARSE_NODE_PLAYER, PARSE_NODE_STRING, PARSE_NODE_PLAYER)) // portal invite [owner] [name] [player] - .branch(constantParseNode("invite"), constantParseNode("cancel"), PARSE_NODE_PLAYER, PARSE_NODE_STRING) // portal invite cancel [player] [name] - .branch(constantParseNode("invite"), constantParseNode("accept"), PARSE_NODE_PLAYER, PARSE_NODE_STRING) // portal invite accept [player] [name] - .branch(constantParseNode("invite"), constantParseNode("decline"), PARSE_NODE_PLAYER, PARSE_NODE_STRING) // portal invite decline [player] [name] - .branch(constantParseNode("uninvite"), PARSE_NODE_PLAYER, PARSE_NODE_STRING) // portal uninvite [player] [name] - .branch(constantParseNode("link"), PARSE_NODE_STRING, PARSE_NODE_STRING) // portal link [name] [linkName] - .branch(constantParseNode("unlink"), PARSE_NODE_STRING) // portal unlink [name] - .branch(constantParseNode("list")) // portal list - .branch(PermissionParseBranch(permissionListOther, constantParseNode("list"), PARSE_NODE_PLAYER)) // portal list [player] + .branch(PermissionParseBranch(permissionCreate, false, constantParseNode("create"), PARSE_NODE_STRING, senderPortalParseNode)) // portals create [name] [linkName] + .branch(PermissionParseBranch(permissionCreate, false, constantParseNode("create"), PARSE_NODE_STRING)) // portals create [name] + .branch(PermissionParseBranch(permissionRemove, false, constantParseNode("remove"), PARSE_NODE_STRING)) // portals remove [name] + .branch(PermissionParseBranch(permissionRemoveOther, constantParseNode("remove"), PARSE_NODE_PLAYER, otherPortalParseNode)) // portals remove [player] [name] + .branch(PlayerParseBranch(constantParseNode("uninvite"), senderPortalParseNode, PARSE_NODE_PLAYER)) // portals uninvite [name] [player] + .branch(PermissionParseBranch(permissionInviteOther, false, constantParseNode("uninvite"), PARSE_NODE_PLAYER, otherPortalParseNode, PARSE_NODE_PLAYER)) // portals uninvite [owner] [name] [player] + .branch(constantParseNode("link"), senderPortalParseNode, senderPortalParseNode) // portals link [name] [linkName] + .branch(constantParseNode("unlink"), senderPortalParseNode) // portals unlink [name] + .branch(constantParseNode("list")) // portals list + .branch(PermissionParseBranch(permissionListOther, constantParseNode("list"), PARSE_NODE_PLAYER)) // portals list [player] + .branch(PermissionParseBranch(permissionInvite, constantParseNode("invite"), senderPortalParseNode, PARSE_NODE_PLAYER)) // portals invite [name] [player] + .branch(PermissionParseBranch(permissionInviteOther, constantParseNode("invite"), PARSE_NODE_PLAYER, otherPortalParseNode, PARSE_NODE_PLAYER)) // portals invite [owner] [name] [player] + .branch(constantParseNode("invite"), constantParseNode("cancel"), PARSE_NODE_PLAYER, senderInviteParseNode) // portals invite cancel [player] [name] + .branch(constantParseNode("invite"), constantParseNode("accept"), PARSE_NODE_PLAYER, recipientInviteParseNode) // portals invite accept [player] [name] + .branch(constantParseNode("invite"), constantParseNode("decline"), PARSE_NODE_PLAYER, recipientInviteParseNode) // portals invite decline [player] [name] override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { - TODO("Not yet implemented") + when (val result = portalParse.getMatch(args, sender)) { + is ParseResult.FailResult -> sender.spigot().sendMessage(TextComponent(result.reason)) + is ParseResult.SuccessResult -> + sender.spigot().sendMessage(TextComponent(when(result.match[0] as String) { + "create" -> { + val portal = portalManager.makePortal(sender as Player, result.match[1] as String, sender.location, if (result.match.size == 2) null else result.match[2] as Portal) + if (portal == null) RESULT_ERROR_PORTALEXISTS else RESULT_SUCCESS_NEWPORTAL.format(Locale.ROOT, result.match[1] as String) + } + + "remove" -> { + val portal = result.match.last() as Portal + portalManager.removePortal(portal) + if (result.match.size == 2) RESULT_SUCCESS_DELPORTAL.format(Locale.ROOT, portal.name) + else RESULT_SUCCESS_DELPORTALOTHER.format(Locale.ROOT, portal.name, portal.owner.playerName) + } + + "uninvite" -> { + val toRemove = result.match.last() as OfflinePlayer + val portal = result.match[result.match.size - 2] as Portal + + if (toRemove.uniqueId == portal.owner.uniqueId) RESULT_ERROR_BANOWNER + else if (!portal.public) { + portal.accessExclusions.remove(toRemove) + RESULT_SUCCESS_BAN.format(toRemove.playerName, portal.name) + } else { + portal.accessExclusions.add(toRemove) + RESULT_SUCCESS_BANOTHER.format(toRemove.playerName, portal.name, portal.owner.playerName) + } + } + + "link" -> { + val portal = result.match[result.match.size - 2] as Portal + val link = result.match.last() as Portal + + if (portal.link(link)) RESULT_SUCCESS_LINK.format(portal.name, link.name) + else RESULT_ERROR_SELFREF + } + + "unlink" -> { + val portal = result.match.last() as Portal + portal.unlink() + RESULT_SUCCESS_UNLINK.format(portal.name) + } + + "list" -> { + val owner = sender as? OfflinePlayer ?: result.match.last() as OfflinePlayer + val portals = portalManager.getPortals(owner) + + if (portals == null || !portals.iterator().hasNext()) RESULT_ERROR_NOPORTALS + else { + val builder = StringBuilder(RESULT_INFO_LIST) + var counter = 0 + for (portal in portals) + builder.append(counter++).append(". ").append(portal.name) + builder.toString() + } + } + + "invite" -> + when(result.match[1]) { + is Portal, is OfflinePlayer -> { + val recipient = result.match.last() as OfflinePlayer + val portal = result.match[result.match.size - 2] as Portal + + if (recipient.uniqueId == portal.owner.uniqueId || !portalManager.invitePlayer(recipient, portal)) RESULT_ERROR_INVITED + else if (sender !is OfflinePlayer || portal.owner.uniqueId != (sender as OfflinePlayer).uniqueId) + RESULT_SUCCESS_INVITEOTHER.format(recipient.playerName, portal.name, portal.owner.playerName) + else RESULT_SUCCESS_INVITE.format(recipient.playerName, portal.name) + } + + "cancel" -> { + val invite = result.match.last() as Invite + val portal = portalManager.getPortal(invite.portalID) + + if (portal == null || !portalManager.cancelInvite(invite)) RESULT_ERROR_UNKNOWN + else RESULT_SUCCESS_CANCEL.format(invite.recipient.playerName, portal.name) + } + + "accept" -> { + val invite = result.match.last() as Invite + val portal = portalManager.getPortal(invite.portalID) + + if (portal == null || !portalManager.acceptInvite(invite)) RESULT_ERROR_UNKNOWN + else RESULT_SUCCESS_ACCEPT.format(portal.owner.playerName, portal.name) + } + + "decline" -> { + val invite = result.match.last() as Invite + val portal = portalManager.getPortal(invite.portalID) + + if (portal == null || !portalManager.declineInvite(invite)) RESULT_ERROR_UNKNOWN + else RESULT_SUCCESS_DECLINE.format(portal.owner.playerName, portal.name) + } + + else -> RESULT_ERROR_UNKNOWN + } + + else -> RESULT_ERROR_UNKNOWN + })) + } + + return true } + + override fun onTabComplete( + sender: CommandSender, + command: Command, + alias: String, + args: Array + ) = portalParse.getSuggestions(args, sender).toMutableList() } \ No newline at end of file diff --git a/src/main/kotlin/PortalsPlugin.kt b/src/main/kotlin/PortalsPlugin.kt index 93e7835..c82a81e 100644 --- a/src/main/kotlin/PortalsPlugin.kt +++ b/src/main/kotlin/PortalsPlugin.kt @@ -13,6 +13,21 @@ class PortalsPlugin: JavaPlugin() { reloadConfig() portalManager.onEnable(this) + + + val command = PortalCommand( + portalManager, + description.permissions.first { it.name == "portals.create" }, + description.permissions.first { it.name == "portals.modify.remove" }, + description.permissions.first { it.name == "portals.modify.other" }, + description.permissions.first { it.name == "portals.invite" }, + description.permissions.first { it.name == "portals.invite.other" }, + description.permissions.first { it.name == "portals.list.other" }, + ) + + val pluginCommand = getCommand("portals")!! + pluginCommand.tabCompleter = command + pluginCommand.setExecutor(command) } override fun reloadConfig() {