diff --git a/build.gradle.kts b/build.gradle.kts index a3be860..85017c4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -124,6 +124,11 @@ spigot { defaults = "op" } + create("portals.modify.publish") { + description = "Allows changing the publicity state of portals" + defaults = "op" + } + create("portals.modify.*") { description = "Wildcard portal modification" defaults = "op" @@ -132,7 +137,8 @@ spigot { "portals.modify.edit" to true, "portals.modify.target" to true, "portals.modify.allow" to true, - "portals.modify.other" to true + "portals.modify.other" to true, + "portals.modify.publish" to true ) } diff --git a/src/main/kotlin/MultiSortedList.kt b/src/main/kotlin/MultiSortedList.kt index 2c3063b..4991e05 100644 --- a/src/main/kotlin/MultiSortedList.kt +++ b/src/main/kotlin/MultiSortedList.kt @@ -112,24 +112,32 @@ class MultiSortedList constructor( * Get all contiguous elements that match a comparison * @return Iterable object of all matching elements, or null if none exist */ - fun getAll(comparator: Comparator, comparison: (E) -> Int): Iterable? { - var index = search(comparator, comparison) - if (index < 0) return null + fun getAll(comparator: Comparator, findBase: (E) -> Int, compare: (E) -> Int): Iterable? { + var index = search(comparator, findBase) + if (index < 0) { + index = -(index + 1) + + if (index >= size || compare(get(index, comparator)) != 0) + return null + } + + // This should help with accessing entries in sequential collections (e.g. linked lists) + val iterator = (extraLists[comparator] ?: this).subList(index, size).iterator() val result = LinkedList() - - var element: E - do { - element = get(index++, comparator) - if (comparison(element) != 0) + while (iterator.hasNext()) { + val element = iterator.next() + if (compare(element) != 0) break result += element - } while (index < size) + } return result } + fun getAll(comparator: Comparator, comparison: (E) -> Int) = getAll(comparator, comparison, comparison) + fun findValueOrNull(comparator: Comparator, comparison: (E) -> Int): E? { val index = search(comparator, comparison) diff --git a/src/main/kotlin/PortalCommand.kt b/src/main/kotlin/PortalCommand.kt index 55731e2..28a36e2 100644 --- a/src/main/kotlin/PortalCommand.kt +++ b/src/main/kotlin/PortalCommand.kt @@ -57,7 +57,8 @@ class PortalCommand( permissionTpOther: Permission, permissionInfo: Permission, permissionInfoOther: Permission, - permissionEdit: Permission + permissionEdit: Permission, + permissionPublish: Permission ): CommandExecutor, TabCompleter { // Arg parse node for targeting a portal owned by the sender private val senderPortalParseNode: ArgNode = @@ -138,6 +139,10 @@ class PortalCommand( .branch(PermissionParseBranch(permissionEdit, false, constantParseNode("edit"), senderPortalParseNode, constantParseNode("pitch"), PARSE_NODE_DECIMAL)) // portals edit [name] pitch [number] .branch(PermissionParseBranch(permissionEdit, false, constantParseNode("edit"), senderPortalParseNode, constantParseNode("yaw"))) // portals edit [name] yaw .branch(PermissionParseBranch(permissionEdit, false, constantParseNode("edit"), senderPortalParseNode, constantParseNode("pitch"))) // portals edit [name] pitch + .branch(PermissionParseBranch(permissionPublish, false, constantParseNode("publish"), senderPortalParseNode)) // portals publish [name] + .branch(PermissionParseBranch(permissionPublish, true, constantParseNode("publish"), PARSE_NODE_PLAYER, otherPortalParseNode)) // portals publish [player] [name] + .branch(PermissionParseBranch(permissionPublish, false, constantParseNode("unpublish"), senderPortalParseNode)) // portals unpublish [name] + .branch(PermissionParseBranch(permissionPublish, true, constantParseNode("unpublish"), PARSE_NODE_PLAYER, otherPortalParseNode)) // portals unpublish [player] [name] override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { when (val result = portalParse.getMatch(args, sender)) { @@ -263,7 +268,7 @@ class PortalCommand( } "tp" -> { - (result.match.last() as Portal).teleportPlayerTo(sender as Player) + portalManager.teleportTo(sender as Player, result.match.last() as Portal) null } @@ -287,11 +292,11 @@ class PortalCommand( "edit" -> { val isExplicit = result.match.last() is Double val last = if (isExplicit) result.match.last() as Double else null - val portal = result.match[result.match.size - 3] as Portal + val portal = result.match[1] as Portal sender as Player - when(result.match[result.match.lastIndex - (if (isExplicit) 1 else 0)] as String) { + when(result.match[2] as String) { "yaw" -> { portal.yaw = if(isExplicit) last!!.toFloat() else sender.location.yaw RESULT_SUCCESS_EDIT_YAW.format(portal.yaw) @@ -304,6 +309,18 @@ class PortalCommand( } } + "publish", "unpublish" -> { + val publish = result.match.first() == "publish" + val portal = result.match.last() as Portal + + if (portal.public == publish) { + "Nothing changed" + } else { + portal.public = publish + String.format(Locale.ROOT, "Portal \"%s\" is now %s", portal.name, if(publish) "public" else "private") + } + } + else -> RESULT_ERROR_UNKNOWN } diff --git a/src/main/kotlin/PortalManager.kt b/src/main/kotlin/PortalManager.kt index e3df870..105ede6 100644 --- a/src/main/kotlin/PortalManager.kt +++ b/src/main/kotlin/PortalManager.kt @@ -5,12 +5,14 @@ import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.HandlerList import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerLoginEvent import org.bukkit.event.player.PlayerMoveEvent import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.plugin.Plugin -import java.lang.Long.max import java.util.* import java.util.logging.Logger +import kotlin.math.max +import kotlin.math.min private const val PATH_DATA_PLAYERS = "players" private const val PATH_DATA_WORLDS = "worlds" @@ -281,17 +283,25 @@ class PortalManager(private val data: ConfigurationSection, private val config: portals.getAll(COMPARATOR_PORTAL_OWNER_NAME) { owner.uniqueId.compareTo(it.owner.uniqueId) } fun getPortalsByPartialName(owner: OfflinePlayer, namePart: String) = - portals.getAll(COMPARATOR_PORTAL_OWNER_NAME) { - compareValues( - it.owner::getUniqueId to owner::getUniqueId, - { it.name.substring(0, namePart.length.coerceAtMost(it.name.length)) } to { namePart } - ) - } + portals.getAll(COMPARATOR_PORTAL_OWNER_NAME, + { + compareValues( + it.owner::getUniqueId to owner::getUniqueId, + { it.name } to { namePart } + ) + }, + { + compareValues( + it.owner::getUniqueId to owner::getUniqueId, + { it.name.substring(0 until min(it.name.length, namePart.length)) } to { namePart } + ) + }, + ) fun getPortalsAt(location: Location) = portals.getAll(COMPARATOR_PORTAL_LOCATION_OWNER, location.portalComparison(worlds::getIndex)) - fun teleportPlayerTo(player: Player, portal: Portal) { + fun enterPortal(player: Player, portal: Portal) { val result = portal.enterPortal(player, this::getPortal) if (result is PortalResult.SUCCESS) triggerCooldown(player, result.link) @@ -300,6 +310,11 @@ class PortalManager(private val data: ConfigurationSection, private val config: .warning("${player.name} failed to enter portal ${portal.name} (${portal.owner.playerName}; ${portal.world.name}; ${portal.x}, ${portal.y}, ${portal.z})") } + fun teleportTo(player: Player, portal: Portal) { + portal.teleportPlayerTo(player) + triggerCooldown(player, portal) + } + private fun popCooldowns(player: OfflinePlayer, moveTo: Location) { val time = System.currentTimeMillis() while (cooldowns.isNotEmpty()) { @@ -323,6 +338,16 @@ class PortalManager(private val data: ConfigurationSection, private val config: touchPortalCooldown[player.uniqueId] = portal } + fun getAccessiblePortals(player: Player, location: Location = player.location) = + getPortalsAt(location)?.filter { + it.checkEnter(player, this::getPortal) is PortalResult.SUCCESS + } + + fun getAccessiblePortal(player: Player, location: Location = player.location): Portal? { + val found = getAccessiblePortals(player, location) + return found?.firstOrNull { it.owner.uniqueId == player.uniqueId } ?: found?.firstOrNull() + } + @EventHandler fun onPlayerMove(moveEvent: PlayerMoveEvent) { val to = moveEvent.to @@ -331,15 +356,18 @@ class PortalManager(private val data: ConfigurationSection, private val config: // If we're ignoring player movements for this player, just return immediately if (isOnCooldown(moveEvent.player, to)) return - val found = getPortalsAt(to) + enterPortal(moveEvent.player, getAccessiblePortal(moveEvent.player, to) ?: return) + } + } - val triggered = found?.firstOrNull { - it.owner.uniqueId == moveEvent.player.uniqueId && it.checkEnter(moveEvent.player, this::getPortal) is PortalResult.SUCCESS + @EventHandler + fun onPlayerJoin(loginEvent: PlayerLoginEvent) { + val portal = getAccessiblePortal(loginEvent.player) + + if (portal != null) { + synchronized(touchPortalCooldown) { + triggerCooldown(loginEvent.player, portal) } - ?: found?.firstOrNull { it.checkEnter(moveEvent.player, this::getPortal) is PortalResult.SUCCESS } - - if (triggered != null) - teleportPlayerTo(moveEvent.player, triggered) } } diff --git a/src/main/kotlin/PortalsPlugin.kt b/src/main/kotlin/PortalsPlugin.kt index 69604c9..1303ff3 100644 --- a/src/main/kotlin/PortalsPlugin.kt +++ b/src/main/kotlin/PortalsPlugin.kt @@ -27,7 +27,8 @@ class PortalsPlugin: JavaPlugin() { description.permissions.first { it.name == "portals.tp.other" }, description.permissions.first { it.name == "portals.info" }, description.permissions.first { it.name == "portals.info.other" }, - description.permissions.first { it.name == "portals.modify.edit" } + description.permissions.first { it.name == "portals.modify.edit" }, + description.permissions.first { it.name == "portals.modify.publish" } ) val pluginCommand = getCommand("portals")!!