新增模式选项,修改编辑方式。

This commit is contained in:
BERADQ 2024-11-01 22:06:44 +08:00
parent 1e461c0c18
commit 15b7a3cb39
9 changed files with 287 additions and 59 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.gradle
.idea
build
.zed

View File

@ -6,6 +6,8 @@ import taboolib.common.platform.function.info
object AdyBubbles : Plugin() {
val adyApi = Adyeshach.api()
val hologramHandler = adyApi.getHologramHandler()
val entityFinder = adyApi.getEntityFinder()
override fun onEnable() {
info("Successfully running AdyBubbles!")
}

View File

@ -0,0 +1,27 @@
package io.github.beradq.adybubbles
class Bubbles(val items: MutableList<String>, val mode: ChatPopupMode = ChatPopupMode.LOOP) {
val state = BubbleState(0)
operator fun get(index: Int): String {
return items[index]
}
class BubbleState(
var index: Int,
)
fun next(): String? {
if (items.isEmpty()) return null
val item = items[state.index]
state.index++
if (state.index >= items.size) {
state.index = 0
}
return item
}
fun random(): String {
return items.random()
}
}

View File

@ -2,8 +2,11 @@ package io.github.beradq.adybubbles
import ink.ptms.adyeshach.core.AdyeshachHologram
import io.github.beradq.adybubbles.AdyBubbles.adyApi
import io.github.beradq.adybubbles.AdyBubbles.entityFinder
import io.github.beradq.adybubbles.AdyBubbles.hologramHandler
import taboolib.common.LifeCycle
import taboolib.common.platform.Awake
import taboolib.common.platform.function.submit
object BubblesBundle {
@ -12,14 +15,26 @@ object BubblesBundle {
return map[key]
}
fun popup(npcId: String, text: String) {
val adyEntity = adyApi.getEntityFinder().getEntityFromUniqueId(npcId) ?: error("Entity not found: $npcId")
val hologramList = map.getOrPut(npcId) { mutableListOf() }
val hologram = adyApi.getHologramHandler().createHologram(
fun popup(npcId: String, text: String): AdyeshachHologram {
val adyEntity = entityFinder.getEntityFromUniqueId(npcId) ?: error("Entity not found: $npcId")
val hologramList = map.getOrPut(npcId) {
mutableListOf()
}
val hologram = hologramHandler.createHologram(
adyEntity.getLocation().add(0.0, adyEntity.entitySize.height, 0.0), listOf(text)
)
hologramList.add(hologram)
updateLocation(npcId)
return hologram
}
fun popupTick(npcId: String, text: String, lifetime: Long) {
val hologram = popup(npcId, text)
val handle = submit(delay = lifetime) {
hologram.remove()
map[text]?.remove(hologram)
this.cancel()
}
}
fun clear(npcId: String) {
@ -39,7 +54,7 @@ object BubblesBundle {
}
fun updateLocation(npcId: String) {
val adyEntity = adyApi.getEntityFinder().getEntityFromUniqueId(npcId) ?: error("Entity not found: $npcId")
val adyEntity = entityFinder.getEntityFromUniqueId(npcId) ?: error("Entity not found: $npcId")
val hologramList = map.getOrPut(npcId) { mutableListOf() }
hologramList.reversed().withIndex().forEach {
it.value.teleport(

View File

@ -1,39 +1,136 @@
package io.github.beradq.adybubbles
import ink.ptms.adyeshach.taboolib.module.chat.RawMessage
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import taboolib.common.platform.command.CommandBody
import taboolib.common.platform.command.CommandHeader
import taboolib.common.platform.command.subCommand
import taboolib.platform.util.nextChatInTick
import io.github.beradq.adybubbles.AdyBubbles.entityFinder
import taboolib.common.platform.ProxyCommandSender
import taboolib.common.platform.ProxyPlayer
import taboolib.common.platform.command.CommandContext
import taboolib.common.platform.function.adaptCommandSender
import taboolib.common.platform.function.adaptPlayer
import taboolib.module.chat.component
private fun clearMessageScreen(player: ProxyCommandSender) {
for (i in 1..40) player.sendMessage(" ")
}
fun sendEditMessage(player: ProxyCommandSender, uuid: String, title: String) {
clearMessageScreen(player)
val bubbles = TraitBubblesChat.getBubbles(uuid) ?: Bubbles(mutableListOf(), ChatPopupMode.LOOP)
player.sendMessage("当前正在编辑: $title 的弹出泡泡")
player.sendMessage(" ")
player.sendMessage(" ")
player.sendMessage(" ")
player.sendMessage("弹出模式(点击切换):")
when (bubbles.mode) {
ChatPopupMode.LOOP ->
"[\\[循环\\]](b;u) [\\[随机\\]](cmd=/bubbles-chat editdata $uuid mode random) [\\[单次\\]](cmd=/bubbles-chat editdata $uuid mode once)"
.component().sendTo(player)
ChatPopupMode.RANDOM ->
"[\\[循环\\]](cmd=/bubbles-chat editdata $uuid mode loop) [\\[随机\\]](b;u) [\\[单次\\]](cmd=/bubbles-chat editdata $uuid mode once)"
.component().sendTo(player)
ChatPopupMode.ONCE ->
"[\\[循环\\]](cmd=/bubbles-chat editdata $uuid mode loop) [\\[随机\\]](cmd=/bubbles-chat editdata $uuid mode random) [\\[单次\\]](b;u)"
.component().sendTo(player)
}
player.sendMessage(" ")
"[\\[弹出内容\\](点击编辑)](cmd=/bubbles-chat editdata $uuid items)".component().sendTo(player)
player.sendMessage(" ")
player.sendMessage(" ")
player.sendMessage(" ")
player.sendMessage("请展开聊天栏编辑")
player.sendMessage(" ")
player.sendMessage(" ")
player.sendMessage(" ")
}
@CommandHeader("bubbles-chat")
object BubblesChatCommand {
@CommandBody
val edit = subCommand {
dynamic("NPC_ID") {
suggestion<CommandSender>(uncheck = true) { sender, _ ->
AdyBubbles.adyApi.getEntityFinder().getEntities().map { it.id }
val editdata = subCommand {
dynamic("UUID") {
suggestion<CommandSender>(uncheck = true) { _, _ ->
entityFinder.getEntities().map { it.uniqueId }
}
dynamic("FIELD") {
suggestion<CommandSender>(uncheck = false) { _, _ ->
listOf("items", "mode")
}
execute<CommandSender> { sender, context, _ ->
val npcId = context["NPC_ID"]
val npcList = AdyBubbles.adyApi.getEntityFinder().getEntitiesFromId(npcId)
if (npcList.size == 1) {
TraitBubblesChat.edit(sender as Player, npcList.first())
} else {
sender.sendMessage("找到了${npcList.size}个NPC:")
npcList.withIndex().forEach {
sender.sendMessage("${it.index + 1}: ${it.value.uniqueId}")
val field = context["FIELD"]
if (field != "items") {
sender.sendMessage("缺少参数")
return@execute
}
sender.sendMessage("请通过聊天输入序号指定NPC")
(sender as Player).nextChatInTick(20 * 10, {
val npc = npcList[it.toInt() - 1]
TraitBubblesChat.edit(sender, npc)
}, {
sender.sendMessage("超时")
})
val uuid = context["UUID"]
val npc = entityFinder.getEntityFromUniqueId(uuid) ?: throw Exception("Entity not found")
TraitBubblesChat.edit(sender as Player, npc)
sendEditMessage(adaptCommandSender(sender), uuid, npc.id)
}
dynamic("VALUE") {
execute<CommandSender> { sender, context, _ ->
val field = context["FIELD"]
val value = context["VALUE"]
val uuid = context["UUID"]
val npc = entityFinder.getEntityFromUniqueId(uuid) ?: throw Exception("Entity not found")
when (field) {
"mode" -> {
val mode = ChatPopupMode.fromString(value)
TraitBubblesChat.setMode(uuid, mode)
sendEditMessage(adaptCommandSender(sender), uuid, npc.id)
}
else -> {
sender.sendMessage("Unknown field: $field")
}
}
}
}
}
}
}
@CommandBody
val edit = subCommand {
dynamic("NPCID") {
suggestion<CommandSender>(uncheck = true) { _, _ ->
entityFinder.getEntities().map { it.id }
}
execute<CommandSender> { sender, context, _ ->
val npcs = entityFinder.getEntitiesFromId(context["NPCID"])
if (npcs.size == 1) {
val uuid = npcs[0].uniqueId
sendEditMessage(adaptPlayer(sender as Player), uuid, npcs[0].id)
} else {
sender.sendMessage("找到了${npcs.size}个NPC:")
for (npc in npcs) {
"[${npc.uniqueId}](cmd=/bubbles-chat edit2 ${npc.uniqueId})".component().sendTo(
adaptCommandSender(sender)
)
}
}
}
}
}
@CommandBody
val edit2 = subCommand {
dynamic("UUID") {
suggestion<CommandSender>(uncheck = true) { _, _ ->
entityFinder.getEntities().map { it.uniqueId }
}
execute<CommandSender> { sender, context, _ ->
val uuid = context["UUID"]
val npc = entityFinder.getEntityFromUniqueId(uuid)!!
sendEditMessage(adaptPlayer(sender as Player), uuid, npc.id)
}
}
}

View File

@ -10,7 +10,7 @@ object BubblesCommand {
@CommandBody
val popup = subCommand {
dynamic("NPC_ID") {
suggestion<CommandSender>(uncheck = true) { sender, _ ->
suggestion<CommandSender>(uncheck = true) { _, _ ->
AdyBubbles.adyApi.getEntityFinder().getEntities().map { it.id }
}
dynamic("TEXT") {
@ -33,6 +33,29 @@ object BubblesCommand {
})
}
}
int("LIFE") {
execute<CommandSender> { sender, context, _ ->
val npcId = context["NPC_ID"]
val npcList = AdyBubbles.adyApi.getEntityFinder().getEntitiesFromId(npcId)
val text = context["TEXT"]
val lifetime = context.int("LIFE")
if (npcList.size == 1) {
BubblesBundle.popupTick(npcList.first().uniqueId, text, lifetime.toLong())
} else {
sender.sendMessage("找到了${npcList.size}个NPC:")
npcList.withIndex().forEach {
sender.sendMessage("${it.index + 1}: ${it.value.uniqueId}")
}
sender.sendMessage("请通过聊天输入序号指定NPC")
(sender as Player).nextChatInTick(20 * 10, {
val npcUniqueId = npcList[it.toInt() - 1].uniqueId
BubblesBundle.popupTick(npcUniqueId, text, lifetime.toLong())
}, {
sender.sendMessage("超时")
})
}
}
}
}
}
}
@ -41,7 +64,7 @@ object BubblesCommand {
@CommandBody
val clear = subCommand {
dynamic("NPC_ID") {
suggestion<CommandSender>(uncheck = true) { sender, _ ->
suggestion<CommandSender>(uncheck = true) { _, _ ->
AdyBubbles.adyApi.getEntityFinder().getEntities().map { it.id }
}
execute<CommandSender> { sender, context, _ ->

View File

@ -0,0 +1,27 @@
package io.github.beradq.adybubbles
enum class ChatPopupMode {
LOOP, // 持续循环
ONCE, // 顺序单次
RANDOM; // 持续随机
override fun toString(): String {
return when (this) {
LOOP -> "loop"
ONCE -> "once"
RANDOM -> "random"
}
}
companion object {
@JvmStatic
fun fromString(str: String): ChatPopupMode {
return when (str) {
"loop" -> LOOP
"once" -> ONCE
"random" -> RANDOM
else -> throw IllegalArgumentException("Invalid chat popup mode: $str")
}
}
}
}

View File

@ -15,38 +15,60 @@ import taboolib.module.chat.uncolored
import java.util.concurrent.CompletableFuture
object TraitBubblesChat : Trait() {
private val bubbleLines = mutableMapOf<String, List<String>>()
private val bubbleState = mutableMapOf<String, Int>()
private val bubbles = mutableMapOf<String, Bubbles>()
private val visibleEntity = mutableListOf<String>()
private var handle: PlatformExecutor.PlatformTask? = null
fun getBubbles(uuid: String): Bubbles? {
val conf = data.getConfigurationSection(uuid) ?: return null
val items = conf.getStringList("items")
val mode = conf.getString("mode")?.let { ChatPopupMode.fromString(it) } ?: ChatPopupMode.LOOP
return Bubbles(items.toMutableList(), mode)
}
fun setBubbles(uuid: String, bubbles: Bubbles) {
data[uuid] = mapOf(
"items" to bubbles.items,
"mode" to bubbles.mode.toString()
)
uuid pipe this::update
}
override fun edit(player: Player, entityInstance: EntityInstance): CompletableFuture<Void> {
val future = CompletableFuture<Void>()
val content = bubbleLines.getOrPut(entityInstance.uniqueId) { mutableListOf() }
val uuid = entityInstance.uniqueId
val content = bubbles.getOrPut(uuid) { Bubbles(mutableListOf()) }.items
player.inputBook(content) {
future.complete(null)
(future::complete.bind(null))()
if (it.all { s -> s.isBlank() }) {
data[entityInstance.uniqueId] = null
bubbleState.remove(entityInstance.uniqueId)
bubbleLines.remove(entityInstance.uniqueId)
BubblesBundle.clear(entityInstance.uniqueId)
data[uuid] = null
uuid also bubbles::remove also BubblesBundle::clear
} else {
data[entityInstance.uniqueId] = it.uncolored()
if (!data.contains(uuid)) data[uuid] = mapOf("items" to it.uncolored())
else data.getConfigurationSection(uuid)!!["items"] = it.uncolored()
}
update(entityInstance.uniqueId)
uuid pipe this::update
}
return future
}
private fun update(entityInstanceUniqueId: String) {
if (!data.contains(entityInstanceUniqueId)) return
val lines = data.getStringList(entityInstanceUniqueId)
bubbleLines[entityInstanceUniqueId] = lines
fun setMode(uuid: String, mode: ChatPopupMode) {
if (!data.contains(uuid)) data[uuid] = mapOf("mode" to mode.toString())
else data.getConfigurationSection(uuid)!!["mode"] = mode.toString()
uuid pipe this::update
}
private fun fistUpdate(entityInstanceUniqueId: String) {
if (bubbleLines.containsKey(entityInstanceUniqueId)) return
update(entityInstanceUniqueId)
private fun update(uuid: String) {
if (!data.contains(uuid)) return
val innerData = data.getConfigurationSection(uuid)!!
val items = if (innerData.contains("items")) innerData.getStringList("items") else emptyList()
val mode = ChatPopupMode.fromString(innerData.getString("mode") ?: "loop")
bubbles[uuid] = Bubbles(items.toMutableList(), mode)
}
private fun fistUpdate(uuid: String) {
if (bubbles.containsKey(uuid)) return
update(uuid)
}
@ -62,30 +84,26 @@ object TraitBubblesChat : Trait() {
}
@SubscribeEvent(priority = EventPriority.MONITOR, ignoreCancelled = true)
// 仅在可视时渲染聊天气泡
private fun onVisible(e: AdyeshachEntityVisibleEvent) {
if (e.entity.isPublic()) {
fistUpdate(e.entity.uniqueId)
val uuid = e.entity.uniqueId
fistUpdate(uuid)
if (e.visible) {
if (visibleEntity.contains(e.entity.uniqueId)) return
visibleEntity.add(e.entity.uniqueId)
if (visibleEntity.contains(uuid)) return
visibleEntity.add(uuid)
} else {
visibleEntity.remove(e.entity.uniqueId)
BubblesBundle.clear(e.entity.uniqueId)
uuid also visibleEntity::remove also BubblesBundle::clear
}
}
}
private fun nextBubble(entityInstanceUniqueId: String) {
val bubbleLine = bubbleLines[entityInstanceUniqueId] ?: return
if (bubbleLine.isEmpty()) return
var state = bubbleState[entityInstanceUniqueId] ?: 0
if (state >= bubbleLine.size) {
state = 0
}
val currentLine = bubbleLine[state]
BubblesBundle.popup(entityInstanceUniqueId, currentLine)
BubblesBundle.limit(entityInstanceUniqueId, Config.bubblesConfig.chat.limit)
bubbleState[entityInstanceUniqueId] = state
private fun nextBubble(uuid: String) {
val bubble = bubbles[uuid]?.next() ?: return
if (bubble.isEmpty()) return
BubblesBundle.popup(uuid, bubble)
BubblesBundle.limit(uuid, Config.bubblesConfig.chat.limit)
}
override fun id(): String {

View File

@ -0,0 +1,18 @@
package io.github.beradq.adybubbles
inline infix fun <T, R> T.pipe(func: (T) -> R): R {
return func(this)
}
infix fun <T, R, S> T.pipe(func: Pair<(T) -> R, (T) -> S>): Pair<R, S> {
return func.first(this) to func.second(this)
}
inline infix fun <T, R> T.also(func: (T) -> R): T {
func(this)
return this
}
infix fun <F : (T) -> R, T, R> F.bind(value: T): () -> R {
return { this(value) }
}