diff --git a/build.gradle.kts b/build.gradle.kts index 81aa325..b9362dd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,6 +25,7 @@ repositories { maven("https://libraries.minecraft.net/") maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") maven("https://repo.songoda.com/repository/public/") + maven("https://nexus.velocitypowered.com/repository/maven-public/") mavenCentral() } @@ -52,13 +53,13 @@ dependencies { "com.fasterxml.jackson.dataformat:jackson-dataformat-properties:2.15.2", "com.jasonclawson:jackson-dataformat-hocon:1.1.0", "com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2", + "com.mojang:authlib:1.5.26", ).forEach { compileOnly(it); testImplementation(it) } // Test testImplementation(kotlin("test")) - compileOnly("org.spigotmc:spigot:1.16.5") } tasks.test { useJUnitPlatform() } diff --git a/src/main/kotlin/city/newnan/violet/gui/GuiView.kt b/src/main/kotlin/city/newnan/violet/gui/GuiView.kt new file mode 100644 index 0000000..0974195 --- /dev/null +++ b/src/main/kotlin/city/newnan/violet/gui/GuiView.kt @@ -0,0 +1,12 @@ +package city.newnan.violet.gui + +enum class UpdateType { + Init, Refresh, Back, Show, +} + +enum class CloseType { + Back, Next, Hide, +} + +typealias UpdateHandler = (UpdateType, GuiType, PlayerGuiSession) -> Boolean +typealias CloseHandler = (CloseType, GuiType, PlayerGuiSession) -> Unit \ No newline at end of file diff --git a/src/main/kotlin/city/newnan/violet/gui/PlayerGuiSession.kt b/src/main/kotlin/city/newnan/violet/gui/PlayerGuiSession.kt index eb5a49f..238192f 100644 --- a/src/main/kotlin/city/newnan/violet/gui/PlayerGuiSession.kt +++ b/src/main/kotlin/city/newnan/violet/gui/PlayerGuiSession.kt @@ -4,71 +4,73 @@ import dev.triumphteam.gui.guis.BaseGui import me.lucko.helper.Schedulers import org.bukkit.entity.Player -enum class UpdateType { - Init, Refresh, Back, Show, -} - -enum class CloseType { - Back, Next, Hide, -} - class PlayerGuiSession(val player: Player) { var chatInputHandlers: ((input: String) -> Boolean)? = null - val history = ArrayDeque Boolean)?, ((type: CloseType) -> Unit)?>>() + val history = ArrayDeque?, CloseHandler?>>() val length get() = history.size val current - get() = history.lastOrNull() + get() = history.lastOrNull()?.first @Synchronized - fun open(gui: BaseGui, onUpdate: ((type: UpdateType) -> Boolean)? = null, onClose: ((type: CloseType) -> Unit)?) { + fun open(gui: GuiType, onUpdate: UpdateHandler? = null, onClose: CloseHandler? = null) { if (!player.isOnline) { clear() return } - history.addLast(Triple(gui, onUpdate, onClose)) - gui.setCloseGuiAction { gui.open(it.player) } - history.lastOrNull()?.run { - third?.invoke(CloseType.Next) - first.close(player, false) + history.lastOrNull()?.also { (gui, _, close) -> + close?.invoke(CloseType.Next, gui, this) + } + history.addLast(Triple(gui, onUpdate as UpdateHandler?, onClose as CloseHandler?)) + // 原则: 不能在 close 的同一帧 open + gui.setCloseGuiAction { + if (current == gui) { + history.removeLastOrNull()?.third?.invoke(CloseType.Back, gui, this) + history.lastOrNull()?.also { (gui, update, _) -> + Schedulers.sync().runLater({ + if (update?.invoke(UpdateType.Back, gui, this) == true) gui.update() + gui.open(player) + }, 1L) + } + } } - Schedulers.sync().runLater({ - val refresh = onUpdate?.invoke(UpdateType.Init) ?: false - if (refresh) gui.update() + Schedulers.sync().run { + if (onUpdate?.invoke(UpdateType.Init, gui, this) == true) gui.update() gui.open(player) - }, 1) + } } @Synchronized - fun back(step: Int = 1) { - if (length <= 0) return - for (i in 1..if (player.isOnline) step else length) { - val last = history.removeLastOrNull() ?: return - last.first.close(player, false) - last.third?.invoke(CloseType.Back) - } - history.lastOrNull()?.also { - Schedulers.sync().runLater({ - val refresh = it.second?.invoke(UpdateType.Back) ?: false - if (refresh) it.first.update() - it.first.open(player) - }, 1) + fun back(step: Int = 1, show: Boolean = true) { + if (!player.isOnline) { + clear() + return + } + for (i in 1..step) { + val (gui, _, close) = history.removeLastOrNull() ?: return + close?.invoke(CloseType.Back, gui, this) + if (history.isEmpty()) gui.close(player) + } + history.lastOrNull()?.also { (gui, update, _) -> + Schedulers.sync().run { + if (update?.invoke(UpdateType.Back, gui, this) == true) gui.update() + if (show) gui.open(player) + } } } @Synchronized - fun refresh() { + fun refresh(show: Boolean = true) { if (!player.isOnline) { clear() return } - current?.run { - Schedulers.sync().runLater({ - val refresh = second?.invoke(UpdateType.Refresh) ?: false - if (refresh) first.update() - first.open(player) - }, 1) + history.lastOrNull()?.also { (gui, update, _) -> + Schedulers.sync().run { + if (update?.invoke(UpdateType.Refresh, gui, this) == true) gui.update() + if (show) gui.open(player) + } } } @@ -78,9 +80,9 @@ class PlayerGuiSession(val player: Player) { clear() return } - current?.run { - third?.invoke(CloseType.Hide) - first.close(player, false) + history.lastOrNull()?.also { (gui, _, close) -> + close?.invoke(CloseType.Hide, gui, this) + gui.close(player, false) } } @@ -90,16 +92,24 @@ class PlayerGuiSession(val player: Player) { clear() return } - current?.run { - Schedulers.sync().runLater({ - val refresh = second?.invoke(UpdateType.Show) ?: false - if (refresh) first.update() - first.open(player) - }, 1) + history.lastOrNull()?.also { (gui, update, _) -> + Schedulers.sync().run { + if (update?.invoke(UpdateType.Show, gui, this) == true) gui.update() + gui.open(player) + } } } - fun clear() = back(length) + fun clear() { + current?.inventory?.viewers?.forEach { viewer -> + viewer.closeInventory() + } + while (history.isNotEmpty()) { + val (gui, _, close) = history.removeLast() + close?.invoke(CloseType.Hide, gui, this) + } + chatInputHandlers = null + } /** * 获取用户的下一个聊天框输入 @@ -107,8 +117,9 @@ class PlayerGuiSession(val player: Player) { * @return 如果先前已经有其他输入请求,则不会开始获取输入,而返回 false,反之则返回true,开始等待输入 */ @Synchronized - fun chatInput(handler: (input: String) -> Boolean): Boolean { + fun chatInput(hide: Boolean = true, handler: (input: String) -> Boolean): Boolean { return if (chatInputHandlers == null) { + if (hide) hide() chatInputHandlers = handler true } else { diff --git a/src/main/kotlin/city/newnan/violet/item/Skull.kt b/src/main/kotlin/city/newnan/violet/item/Skull.kt index 4bf86e5..deb9f5f 100644 --- a/src/main/kotlin/city/newnan/violet/item/Skull.kt +++ b/src/main/kotlin/city/newnan/violet/item/Skull.kt @@ -1,6 +1,8 @@ package city.newnan.violet.item -import org.bukkit.Bukkit +import city.newnan.violet.Reflection +import com.mojang.authlib.GameProfile +import com.mojang.authlib.properties.Property import org.bukkit.Material import org.bukkit.OfflinePlayer import org.bukkit.inventory.ItemStack @@ -21,14 +23,14 @@ fun OfflinePlayer.getSkull(amount: Int = 1): ItemStack = * @return 一个拥有材质的头颅 * @throws Exception 任何异常 */ -fun URL.getSkull(amount: Int): ItemStack { - // 创建一个头 +fun URL.toSkull(amount: Int = 1): ItemStack { val item = ItemStack(Material.PLAYER_HEAD, amount) item.itemMeta = (item.itemMeta as SkullMeta).also { - // 创建一个随机的UUID虚拟玩家,并赋予对应的材质 - it.ownerProfile = Bukkit.getServer().createPlayerProfile(UUID.randomUUID(), null).also { profile -> - profile.textures.skin = this - } + val profile = GameProfile(UUID.randomUUID(), null) + profile.properties.put("textures", Property("textures", + Base64.getEncoder().encodeToString("{textures:{SKIN:{url:\"$this\"}}}".toByteArray())) + ) + Reflection.getDeclaredField(it.javaClass, "profile")?.set(it, profile) } return item } @@ -38,6 +40,6 @@ fun URL.getSkull(amount: Int): ItemStack { * @return 一个拥有材质的头颅 * @throws Exception 任何异常 */ -fun String.getSkull(amount: Int): ItemStack = +fun String.toSkull(amount: Int = 1): ItemStack = URL(if (!startsWith("http://") && !startsWith("https://")) - "http://textures.minecraft.net/texture/$this" else this).getSkull(amount) + "http://textures.minecraft.net/texture/$this" else this).toSkull(amount)