大规模重构修改弹出机制,优化性能。

新的配置项目: rest-delay。
时间相关皆可设置随机时间。
优化游戏内聊天泡泡的编辑体验。
无破坏性修改。
This commit is contained in:
BERADQ 2024-11-06 23:02:11 +08:00
parent f763c8d53c
commit 03137e6115
9 changed files with 347 additions and 116 deletions

View File

@ -1,5 +1,7 @@
# AdyBubbles
版本: 2.5.0-SNAPSHOT
## 指令
- `bubbles popup <NPC_ID> <TEXT> [LIFE]` - 用于弹出泡泡
@ -7,13 +9,36 @@
- `bubbles-chat edit <NPC_ID>` - 用于编辑NPC顺序弹出的泡泡
- `bubbles-chat play <NPC_UUID>` - 用于播放NPC模式为ONCE弹出的泡泡
## 编辑NPC顺序弹出的泡泡
## 游戏内编辑NPC的对话泡泡 (推荐)
使用指令 `bubbles-chat edit <NPC_ID>`
使用指令 bubbles-chat edit <NPC_ID> 并根据提示操作。
根据提示操作
## 通过文件编辑NPC的对话泡泡 (不推荐)
## 配置
本插件的对话泡泡以 Adyeshach 的 Trait 形式存在,详见 特征。
Adyeshach 的特征保存为 yaml 存在于 `~/Adyeshach/npc/traits` 下。
本插件的对话泡泡特征文件名为 bubbles-chat。
则需要编辑 `~/Adyeshach/npc/traits/bubbles-chat.yml` 内的内容。
Example:
```yaml
44cb049b-c385-4654-b52f-cdc5a09dfb66: # NPC UUID
mode: loop # 弹出模式。
items: # 对话内容队列,每一个元素为一条。
- 你好
- 再见
period: 60 # 泡泡弹出间隔时间,可以是随机区间(单位:Tick) 可选参数,不填则跟随全局设置
rest-delay: 0 # 泡泡弹出每轮后的等待时长(区间同理) 可选参数,不填则跟随全局设置
# 模式:
# loop 只要NPC在玩家视野内就循环队列播放
# random 只要NPC在玩家视野内就一直随机队列播放
# once 仅当指令触发时播放一次
```
## 全局配置
Example:
@ -21,9 +46,12 @@ Example:
offset: 0.5 # 泡泡的偏移量(向上)
line-height: 0.5 # 泡泡的间距(向上)
chat:
period: 60 # 泡泡弹出间隔时间
period: 60 # 泡泡弹出间隔时间,可以是随机区间(单位:Tick)
# period: [60, 100] # 三秒到五秒内随机时长
rest-delay: 0 # 泡泡弹出每轮后的等待时长(区间同理)
# rest-delay: [0, 100] # 泡泡弹出一轮后等待随机时长后再开启新一轮
limit: 2 # 泡泡弹出数量限制(超出会自动清楚最早的泡泡)
lifetime: 20 # 泡泡存在的时间限制(超出会自动清除泡泡)
lifetime: 20 # 泡泡时间限制(超出会自动清除泡泡)
```
## 构建发行版本

View File

@ -1,5 +1,5 @@
import io.izzel.taboolib.gradle.BUKKIT
import io.izzel.taboolib.gradle.BUKKIT_ALL
import io.izzel.taboolib.gradle.NMS_UTIL
import io.izzel.taboolib.gradle.UNIVERSAL
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@ -12,7 +12,18 @@ plugins {
taboolib {
env {
// 安装模块
install(UNIVERSAL, BUKKIT_ALL)
install(UNIVERSAL, BUKKIT_ALL, NMS_UTIL)
}
description {
name("AdyBubbles")
desc("为Ady实体显示气泡")
contributors {
name("俗手")
}
dependencies {
name("Adyeshach")
}
load("POSTWORLD")
}
version { taboolib = "6.1.2-beta10" }
}

View File

@ -1,5 +1,5 @@
group=io.github.beradq.adybubbles
version=2.0.0-SNAPSHOT
version=2.5.0-SNAPSHOT
kotlin.incremental=true
kotlin.incremental.java=true
kotlin.caching.enabled=true

View File

@ -1,14 +1,35 @@
package io.github.beradq.adybubbles
import ink.ptms.adyeshach.core.Adyeshach
import ink.ptms.adyeshach.core.AdyeshachAPI
import taboolib.common.platform.Plugin
import taboolib.common.platform.function.info
// ___ __ ____ __ __ __
// / | ____/ /_ __/ __ )__ __/ /_ / /_ / /__ _____
// / /| |/ __ / / / / __ / / / / __ \/ __ \/ / _ \/ ___/
// / ___ / /_/ / /_/ / /_/ / /_/ / /_/ / /_/ / / __(__ )
// /_/ |_\__,_/\__, /_____/\__,_/_.___/_.___/_/\___/____/
// /____/
// 作者: 俗手
object AdyBubbles : Plugin() {
val adyApi = Adyeshach.api()
val adyApi: AdyeshachAPI = Adyeshach.api()
val hologramHandler = adyApi.getHologramHandler()
val entityFinder = adyApi.getEntityFinder()
override fun onEnable() {
info("Successfully running AdyBubbles!")
info("AdyBubbles 已启用!")
}
override fun onActive() {
info("\u001B[36m" + " ___ __ ____ __ __ __ " + "\u001B[0m")
info("\u001B[36m" + " / | ____/ /_ __/ __ )__ __/ /_ / /_ / /__ _____" + "\u001B[0m")
info("\u001B[36m" + " / /| |/ __ / / / / __ / / / / __ \\/ __ \\/ / _ \\/ ___/" + "\u001B[0m")
info("\u001B[36m" + " / ___ / /_/ / /_/ / /_/ / /_/ / /_/ / /_/ / / __(__ ) " + "\u001B[0m")
info("\u001B[36m" + "/_/ |_\\__,_/\\__, /_____/\\__,_/_.___/_.___/_/\\___/____/ " + "\u001B[0m")
info("\u001B[36m" + " /____/ " + "\u001B[0m")
info("\u001B[34m" + "AdyBubbles" + "\u001B[32m" + " 已加载!" + "\u001B[0m")
}
}

View File

@ -1,37 +1,78 @@
package io.github.beradq.adybubbles
class Bubbles(val items: MutableList<String>, val mode: ChatPopupMode = ChatPopupMode.LOOP) : Cloneable {
class Bubbles(val items: MutableList<String>, val config: BubbleConfig) : Cloneable {
private val state = BubbleState(0)
private var restDelay = config.restDelayRange.random()
private var period = config.period.random()
operator fun get(index: Int): String {
return items[index]
}
class BubbleState(
data class BubbleConfig(
val mode: ChatPopupMode,
val _restDelayRange: LongRange? = null,
val _period: LongRange? = null
) {
val period: LongRange get() = _period ?: Config.bubblesConfig.chat.period
val restDelayRange: LongRange get() = _restDelayRange ?: Config.bubblesConfig.chat.restDelay
}
data class BubbleState(
var index: Int,
var timeCount: Long = 0
) : Cloneable
fun next(): String? {
if (config.mode == ChatPopupMode.RANDOM) return items.random()
if (items.isEmpty()) return null
val item = items[state.index]
state.index++
if (state.index >= items.size && mode == ChatPopupMode.LOOP) {
if (state.index >= items.size && config.mode == ChatPopupMode.LOOP) {
state.index = 0
}
return item
}
fun resetState() {
state.index = 0
fun tick(uuid: String) {
if (state.timeCount == -1L) return
state.timeCount += 1
if (state.timeCount == period) {
popup(uuid, next() ?: return)
}
if (state.timeCount >= period) {
if (state.index != 0 || config.mode != ChatPopupMode.LOOP) {
state.timeCount = if (config.mode == ChatPopupMode.ONCE) -1 else 0
period = config.period.random()
}
if (state.timeCount >= period + restDelay) {
state.timeCount = 0
period = config.period.random()
restDelay = config.restDelayRange.random()
}
}
}
fun random(): String {
return items.random()
private fun popup(uuid: String, text: String) {
if (Config.bubblesConfig.chat.lifetime != null) {
BubblesBundle.popupTick(uuid, text, Config.bubblesConfig.chat.lifetime!!)
} else {
BubblesBundle.popup(uuid, text)
}
BubblesBundle.limit(uuid, Config.bubblesConfig.chat.limit)
}
fun resetState() {
state.index = 0
state.timeCount = 0
restDelay = config.restDelayRange.random()
period = config.period.random()
}
public override fun clone(): Bubbles {
val bubbles = Bubbles(items.toMutableList(), mode)
val bubbles = Bubbles(items.toMutableList(), config.copy())
bubbles.state.index = state.index
bubbles.state.timeCount = state.timeCount
return bubbles
}
}

View File

@ -7,44 +7,94 @@ import taboolib.common.platform.command.CommandHeader
import taboolib.common.platform.command.subCommand
import io.github.beradq.adybubbles.AdyBubbles.entityFinder
import taboolib.common.platform.ProxyCommandSender
import taboolib.common.platform.command.bool
import taboolib.common.platform.function.adaptCommandSender
import taboolib.common.platform.function.adaptPlayer
import taboolib.module.chat.component
import taboolib.module.nms.*
private fun clearMessageScreen(player: ProxyCommandSender) {
for (i in 1..40) player.sendMessage(" ")
for (i in 1..5) player.sendMessage(" ")
}
fun sendEditMessage(player: ProxyCommandSender, uuid: String, title: String) {
private fun edd(uuid: String, cmd: String): String {
return "/bubbles-chat editdata true $uuid $cmd"
}
private fun String.replaceBracketForSafety(): String {
return this.replace("[", "\\[").replace("]", "\\]")
}
private fun sendEditMessage(player: ProxyCommandSender, uuid: String, title: String) {
clearMessageScreen(player)
val bubbles = TraitBubblesChat.getBubbles(uuid) ?: Bubbles(mutableListOf(), ChatPopupMode.LOOP)
val bubbles = TraitBubblesChat.getOrCreateBubbles(uuid)
player.sendMessage("当前正在编辑: $title 的弹出泡泡")
player.sendMessage(" ")
player.sendMessage(" ")
player.sendMessage(" ")
player.sendMessage("弹出模式(点击切换):")
when (bubbles.mode) {
"[§b\\[ 弹出内容 \\]§f](cmd=${edd(uuid, "items")})".component().sendTo(player)
player.sendMessage(" ")
player.sendMessage(" ")
player.sendMessage("弹出模式:")
when (bubbles.config.mode) {
ChatPopupMode.LOOP ->
"[\\[循环\\]](b;u) [\\[随机\\]](cmd=/bubbles-chat editdata $uuid mode random) [\\[单次\\]](cmd=/bubbles-chat editdata $uuid mode once)"
"[§a\\[循环\\]§f](b;u) [§b\\[随机\\]§f](cmd=${edd(uuid, "mode random")}) [§b\\[单次\\]§f](cmd=${
edd(
uuid,
"mode once"
)
})"
.component().sendTo(player)
ChatPopupMode.RANDOM ->
"[\\[循环\\]](cmd=/bubbles-chat editdata $uuid mode loop) [\\[随机\\]](b;u) [\\[单次\\]](cmd=/bubbles-chat editdata $uuid mode once)"
"[§b\\[循环\\]§f](cmd=${edd(uuid, "mode loop")}) [§a\\[随机\\]§f](b;u) [§b\\[单次\\]§f](cmd=${
edd(
uuid,
"mode once"
)
})"
.component().sendTo(player)
ChatPopupMode.ONCE ->
"[\\[循环\\]](cmd=/bubbles-chat editdata $uuid mode loop) [\\[随机\\]](cmd=/bubbles-chat editdata $uuid mode random) [\\[单次\\]](b;u)"
"[§b\\[循环\\]§f](cmd=${edd(uuid, "mode loop")}) [§b\\[随机\\]§f](cmd=${
edd(
uuid,
"mode random"
)
}) [§a\\[单次\\]§f](b;u)"
.component().sendTo(player)
}
player.sendMessage(" ")
"[\\[弹出内容\\](点击编辑)](cmd=/bubbles-chat editdata $uuid items)".component().sendTo(player)
player.sendMessage(" ")
if (bubbles.config._period != null) {
"弹出间隔: [§b\\[ ${bubbles.config._period.toMString().replaceBracketForSafety()} \\]§f](cmd=${
edd(
uuid,
"period"
)
})"
.component().sendTo(player)
} else {
"弹出间隔: [§b\\[ 默认值 \\]§f](cmd=${edd(uuid, "period")})"
.component().sendTo(player)
}
if (bubbles.config._restDelayRange != null) {
"每轮间隔: [§b\\[ ${bubbles.config._restDelayRange.toMString().replaceBracketForSafety()} \\]§f](cmd=${
edd(
uuid,
"rest-delay"
)
})"
.component().sendTo(player)
} else {
"每轮间隔: [§b\\[ 默认值 \\]§f](cmd=${edd(uuid, "rest-delay")})"
.component().sendTo(player)
}
player.sendMessage(" ")
player.sendMessage(" ")
player.sendMessage(" ")
player.sendMessage("蓝色选项可点击编辑")
player.sendMessage("请展开聊天栏编辑")
player.sendMessage(" ")
player.sendMessage(" ")
player.sendMessage(" ")
}
@CommandHeader("bubbles-chat")
@ -68,40 +118,89 @@ object BubblesChatCommand {
@CommandBody
@Suppress("unused")
val editdata = subCommand {
dynamic("UUID") {
suggestion<CommandSender>(uncheck = true) { _, _ ->
entityFinder.getEntities().map { it.uniqueId }
}
dynamic("FIELD") {
suggestion<CommandSender>(uncheck = false) { _, _ ->
listOf("items", "mode")
bool("RECALL") {
dynamic("UUID") {
suggestion<CommandSender>(uncheck = true) { _, _ ->
entityFinder.getEntities().map { it.uniqueId }
}
execute<CommandSender> { sender, context, _ ->
val field = context["FIELD"]
if (field != "items") {
sender.sendMessage("缺少参数")
return@execute
dynamic("FIELD") {
suggestion<CommandSender>(uncheck = false) { _, _ ->
listOf("items", "mode", "period", "rest-delay")
}
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 recall = context.bool("RECALL")
val uuid = context["UUID"]
val npc = entityFinder.getEntityFromUniqueId(uuid) ?: throw Exception("Entity not found")
val bubbles = TraitBubblesChat.getOrCreateBubbles(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)
"items" -> {
TraitBubblesChat.edit(sender as Player, npc)
sender.sendTitle("请编辑书", null, 1, 20, 1)
}
"period" -> {
(sender as Player).inputSign(
arrayOf(
bubbles.config._period?.toMString() ?: "",
"请在第一行输入数字或",
"区间代表范围内随机"
)
) {
val content = it.first().trim()
if (content.isEmpty()) {
TraitBubblesChat.setPeriod(uuid, null)
} else {
val range = content.toLongRange()
TraitBubblesChat.setPeriod(uuid, range)
}
if (recall) sendEditMessage(adaptCommandSender(sender), uuid, npc.id)
}
}
"rest-delay" -> {
(sender as Player).inputSign(
arrayOf(
bubbles.config._restDelayRange?.toMString() ?: "",
"请在第一行输入数字或",
"区间代表范围内随机"
)
) {
val content = it.first().trim()
if (content.isEmpty()) {
TraitBubblesChat.setRestDelay(uuid, null)
} else {
val range = content.toLongRange()
TraitBubblesChat.setRestDelay(uuid, range)
}
if (recall) sendEditMessage(adaptCommandSender(sender), uuid, npc.id)
}
}
else -> {
sender.sendMessage("Unknown field: $field")
sender.sendMessage("缺少参数")
return@execute
}
}
}
dynamic("VALUE") {
execute<CommandSender> { sender, context, _ ->
val field = context["FIELD"]
val recall = context.bool("RECALL")
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)
if (recall) sendEditMessage(adaptCommandSender(sender), uuid, npc.id)
}
else -> {
sender.sendMessage("Unknown field: $field")
}
}
}
}

View File

@ -11,7 +11,8 @@ object Config {
lateinit var bubblesConfig: BubblesConfig
class BubblesChatConfig(
val period: Long,
val period: LongRange,
val restDelay: LongRange,
val limit: Int,
val lifetime: Long?
)
@ -28,9 +29,10 @@ object Config {
val offset = configFile["offset"] as? Double ?: 0.5
val lineHeight = configFile["line-height"] as? Double ?: 0.5
val chat = configFile.getConfigurationSection("chat")
val period = chat?.get("period") as? Long ?: (20 * 3)
val period = chat?.getString("period")?.toLongRange() ?: (20L * 3)..(20 * 3)
val limit = chat?.get("limit") as? Int ?: 3
val restDelayRange = chat?.getString("rest-delay")?.toLongRange() ?: 0L..0
val lifetime = chat?.get("lifetime") as? Long
bubblesConfig = BubblesConfig(offset, lineHeight, BubblesChatConfig(period, limit, lifetime))
bubblesConfig = BubblesConfig(offset, lineHeight, BubblesChatConfig(period, restDelayRange, limit, lifetime))
}
}

View File

@ -4,49 +4,55 @@ import ink.ptms.adyeshach.core.entity.EntityInstance
import ink.ptms.adyeshach.core.event.AdyeshachEntityVisibleEvent
import ink.ptms.adyeshach.impl.entity.trait.Trait
import ink.ptms.adyeshach.impl.util.Inputs.inputBook
import taboolib.common.LifeCycle
import org.bukkit.entity.Player
import taboolib.common.platform.Awake
import taboolib.common.platform.Schedule
import taboolib.common.platform.event.EventPriority
import taboolib.common.platform.event.SubscribeEvent
import taboolib.common.platform.function.submit
import taboolib.common.platform.service.PlatformExecutor
import taboolib.module.chat.uncolored
import java.util.concurrent.CompletableFuture
object TraitBubblesChat : Trait() {
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
if (!data.contains(uuid)) return null
val conf = data.getConfigurationSection(uuid)!!
val items = conf.getStringList("items")
val mode = conf.getString("mode")?.let { ChatPopupMode.fromString(it) } ?: ChatPopupMode.LOOP
return Bubbles(items.toMutableList(), mode)
val period = conf.getString("period")?.toLongRange()
val restDelay = conf.getString("rest-delay")?.toLongRange()
return Bubbles(items.toMutableList(), Bubbles.BubbleConfig(mode, restDelay, period))
}
@Suppress("unused")
fun setBubbles(uuid: String, bubbles: Bubbles) {
data[uuid] = mapOf(
"items" to bubbles.items,
"mode" to bubbles.mode.toString()
"mode" to bubbles.config.mode.toString(),
"period" to bubbles.config._period?.toMString(),
"rest-delay" to bubbles.config._restDelayRange?.toMString()
)
uuid pipe this::update
}
fun getOrCreateBubbles(uuid: String): Bubbles {
val bubbles = bubbles.getOrPut(uuid) { Bubbles(mutableListOf(), Bubbles.BubbleConfig(ChatPopupMode.LOOP)) }
update(uuid)
return bubbles
}
override fun edit(player: Player, entityInstance: EntityInstance): CompletableFuture<Void> {
val future = CompletableFuture<Void>()
val uuid = entityInstance.uniqueId
val content = bubbles.getOrPut(uuid) { Bubbles(mutableListOf()) }.items
val content = getOrCreateBubbles(uuid).items
player.inputBook(content) {
(future::complete.bind(null))()
future.complete(null)
if (it.all { s -> s.isBlank() }) {
data[uuid] = null
uuid also bubbles::remove also BubblesBundle::clear
} else {
if (!data.contains(uuid)) data[uuid] = mapOf("items" to it.uncolored())
else data.getConfigurationSection(uuid)!!["items"] = it.uncolored()
getConfiguration(uuid)["items"] = it.uncolored()
}
uuid pipe this::update
}
@ -54,17 +60,27 @@ object TraitBubblesChat : Trait() {
}
fun setMode(uuid: String, mode: ChatPopupMode) {
if (!data.contains(uuid)) data[uuid] = mapOf("mode" to mode.toString())
else data.getConfigurationSection(uuid)!!["mode"] = mode.toString()
getConfiguration(uuid)["mode"] = mode.toString()
uuid pipe this::update
}
fun setPeriod(uuid: String, period: LongRange?) {
getConfiguration(uuid)["period"] = period?.toMString()
uuid pipe this::update
}
fun setRestDelay(uuid: String, restDelay: LongRange?) {
getConfiguration(uuid)["rest-delay"] = restDelay?.toMString()
uuid pipe this::update
}
fun getConfiguration(uuid: String): ink.ptms.adyeshach.taboolib.library.configuration.ConfigurationSection {
if (!data.contains(uuid)) data[uuid] = mapOf<String, String>()
return data.getConfigurationSection(uuid)!!
}
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)
bubbles[uuid] = getBubbles(uuid) ?: return
}
private fun fistUpdate(uuid: String) {
@ -73,44 +89,18 @@ object TraitBubblesChat : Trait() {
}
@Awake(LifeCycle.ACTIVE)
private fun startTicking() {
handle?.cancel()
handle = submit(period = Config.bubblesConfig.chat.period, async = true) {
val ve = visibleEntity.toList()
ve.forEach {
val bubble = bubbles[it] ?: return@forEach
val item = when (bubble.mode) {
ChatPopupMode.LOOP -> {
bubble.next()
}
ChatPopupMode.RANDOM -> bubble.random()
else -> return@forEach
} ?: return@forEach
popup(it, item)
}
@Schedule(async = true, period = 1)
fun tick() {
for (uuid in visibleEntity.toList()) {
val bubble = bubbles[uuid] ?: continue
bubble.tick(uuid)
}
}
fun startOnce(uuid: String) {
val bubble = bubbles[uuid] ?: return
if (bubble.mode != ChatPopupMode.ONCE) return
if (bubble.config.mode != ChatPopupMode.ONCE) return
bubble.resetState()
val cloneBubble = bubble.clone()
submit(period = Config.bubblesConfig.chat.period, async = true) {
val item = cloneBubble.next() ?: return@submit this.cancel()
popup(uuid, item)
}
}
fun popup(uuid: String, text: String) {
if (Config.bubblesConfig.chat.lifetime != null) {
BubblesBundle.popupTick(uuid, text, Config.bubblesConfig.chat.lifetime!!)
} else {
BubblesBundle.popup(uuid, text)
}
BubblesBundle.limit(uuid, Config.bubblesConfig.chat.limit)
}
@SubscribeEvent(priority = EventPriority.MONITOR, ignoreCancelled = true)

View File

@ -4,15 +4,54 @@ 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) }
fun parseLongRange(range: String): LongRange? {
val rangeStr = range.trim()
val start: Long
val end: Long
val startInclusive: Boolean = when (rangeStr[0]) {
'[' -> true
'(' -> false
else -> {
// 如果只有一个数字,则返回该数字
return rangeStr.toLongOrNull()?.let { it..it }
}
}
val endInclusive: Boolean = when (rangeStr[rangeStr.length - 1]) {
']' -> true
')' -> false
else -> return null
}
val rangeParts = rangeStr.substring(1, rangeStr.length - 1).split(',').map { it.trim() }
if (rangeParts.size != 2) {
return null
}
start = rangeParts[0].toLongOrNull() ?: return null
end = rangeParts[1].toLongOrNull() ?: return null
return when {
startInclusive && endInclusive -> start..end
startInclusive && !endInclusive -> start until end
!startInclusive && endInclusive -> (start + 1)..end
else -> (start + 1) until end
}
}
fun String.toLongRange() = parseLongRange(this)
fun longRangeToString(range: LongRange): String {
val start = range.first
val end = range.last
if (start == end) return "$start"
return "[$start, $end]"
}
fun LongRange.toMString() = longRangeToString(this)