Skip to content

Commit

Permalink
Fix bugs for GUI
Browse files Browse the repository at this point in the history
  • Loading branch information
Gk0Wk committed Aug 22, 2023
1 parent e0fe88d commit c270813
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 61 deletions.
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down Expand Up @@ -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() }
Expand Down
12 changes: 12 additions & 0 deletions src/main/kotlin/city/newnan/violet/gui/GuiView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package city.newnan.violet.gui

enum class UpdateType {
Init, Refresh, Back, Show,
}

enum class CloseType {
Back, Next, Hide,
}

typealias UpdateHandler<GuiType> = (UpdateType, GuiType, PlayerGuiSession) -> Boolean
typealias CloseHandler<GuiType> = (CloseType, GuiType, PlayerGuiSession) -> Unit
113 changes: 62 additions & 51 deletions src/main/kotlin/city/newnan/violet/gui/PlayerGuiSession.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Triple<BaseGui, ((type: UpdateType) -> Boolean)?, ((type: CloseType) -> Unit)?>>()
val history = ArrayDeque<Triple<BaseGui, UpdateHandler<BaseGui>?, CloseHandler<BaseGui>?>>()
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 <GuiType : BaseGui> open(gui: GuiType, onUpdate: UpdateHandler<GuiType>? = null, onClose: CloseHandler<GuiType>? = 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<BaseGui>?, onClose as CloseHandler<BaseGui>?))
// 原则: 不能在 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)
}
}
}

Expand All @@ -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)
}
}

Expand All @@ -90,25 +92,34 @@ 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
}

/**
* 获取用户的下一个聊天框输入
* @param handler 获取到输入后的回调函数,返回`true`则结束获取输入,返回`false`则继续获取输入并处理
* @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 {
Expand Down
20 changes: 11 additions & 9 deletions src/main/kotlin/city/newnan/violet/item/Skull.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}
Expand All @@ -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)

0 comments on commit c270813

Please sign in to comment.