工具集(util 包 + 服务)
QinhCoreLib(QCL)在 util 包及配套服务中提供了一整套通用工具。它们覆盖调度、特效、物品、坐标、服务端兼容、文本、全息与动态装配等高频场景,子插件可以直接拿来即用,不必重复造轮子。
本页按工具类分节,每节给出方法表与典型用法代码示例。所有代码示例均为照抄即用,标识符大小写敏感。
全局约定(务必先读)
用
Runnable/Supplier,不要直接传 Kotlin lambda。 QCL 的调度 API(TaskScheduler、ThrottledExecutor等)形参类型是 Java 的Runnable与Supplier<T>。如果从 Kotlin 子插件直接以 SAM 转换方式传入裸 lambda,跨插件类加载时可能触发LinkageError。请显式包成Runnable { ... }/Supplier { ... },或在 Java 中正常传 lambda。Folia 兼容。
TaskScheduler基于 Bukkit 调度器封装,兼容 Folia。请始终通过TaskScheduler调度,而不是自己Bukkit.getScheduler()。MiniMessage 支持。 文本类(
TextUtil、HologramManager、AssemblySystem等)均支持 MiniMessage 标签、旧版&颜色码、§颜色码与纯文本,自动识别转换为Component。多名降级。
ServerCompat的sound(...)/particle(...)/material(...)/resolveSound/resolvePotionEffectType接受多个候选名,按顺序尝试解析,命中第一个有效项,跨版本更稳。
TaskScheduler(调度)
封装 Bukkit 调度器,兼容 Folia。所有方法以 Runnable / Supplier 为参,避免 Kotlin lambda 的 LinkageError。
| 方法 | 说明 |
|---|---|
runSync(Runnable) | 主线程立即执行 |
runSyncLater(delay, Runnable) | 主线程延迟 delay tick 执行 |
runSyncRepeating(delay, period, Runnable) | 主线程定时重复执行 |
runAsync(Runnable) | 异步线程立即执行 |
runAsyncLater(delay, Runnable) | 异步线程延迟执行 |
runAsyncRepeating(delay, period, Runnable) | 异步线程定时重复执行 |
supplySync<T>(Supplier<T>): CompletableFuture<T> | 主线程取值,返回 CompletableFuture |
supplyAsync<T>(Supplier<T>): CompletableFuture<T> | 异步线程取值,返回 CompletableFuture |
典型用法
// 主线程立即执行
TaskScheduler.runSync(Runnable {
player.sendMessage("已在主线程执行")
})
// 延迟 20 tick(1 秒)后执行
TaskScheduler.runSyncLater(20L, Runnable {
player.health = player.maxHealthValue()
})
// 每 5 tick 重复一次
val taskRef = TaskScheduler.runSyncRepeating(0L, 5L, Runnable {
EffectUtils.spawnParticle(loc, Particle.HEART, 1)
})
// 异步计算后回主线程使用结果
TaskScheduler.supplyAsync(Supplier {
heavyDatabaseQuery() // 异步耗时操作
}).thenAccept { result ->
TaskScheduler.runSync(Runnable {
player.sendMessage("查询结果:$result")
})
}ThrottledExecutor(节流执行器)
按键节流:相同 key 在 throttleMs 内只执行一次。
| 方法 | 说明 |
|---|---|
ThrottledExecutor(throttleMs) | 构造,指定节流间隔(毫秒) |
execute(key, Runnable) | 若该 key 未在节流窗口内,则执行 |
clear() | 清空所有节流记录 |
val throttle = ThrottledExecutor(200L) // 200ms 节流
// 高频事件(如移动)里防止刷屏
throttle.execute(player.uniqueId.toString(), Runnable {
player.sendActionBar("移动中…")
})CooldownManager(冷却管理)
按键冷却,常用于技能、交互冷却。
| 方法 | 说明 |
|---|---|
hasCooldown(key) | 是否处于冷却中 |
getRemaining(key) | 剩余冷却时间 |
setCooldown(key, duration, unit) | 设置冷却 |
removeCooldown(key) | 移除某键冷却 |
clear() | 清空全部冷却 |
val cd = CooldownManager()
val key = player.uniqueId.toString()
if (cd.hasCooldown(key)) {
player.sendMessage("还需等待 ${cd.getRemaining(key)}")
return
}
cd.setCooldown(key, 3, TimeUnit.SECONDS)
// 释放技能…EffectUtils(特效)
声音与粒子的统一入口,附常用预置(Presets)。
| 方法 | 说明 |
|---|---|
playSound(loc, ...) | 在坐标处播放声音 |
playSoundForPlayer(player, ...) | 仅对该玩家播放声音 |
playSoundForAll(...) | 对全服播放声音 |
spawnParticle(loc, particle, count) | 在坐标处生成粒子 |
spawnParticleForPlayer(player, ...) | 仅对该玩家显示粒子 |
spawnColoredDust(loc, color, ...) | 生成彩色尘埃粒子 |
spawnCircle(...) | 生成圆形粒子 |
spawnHelix(...) | 生成螺旋粒子 |
spawnLine(...) | 生成直线粒子 |
预置(Presets)
EffectUtils.Presets 提供一键场景特效,参数为坐标 loc:
success(loc) / error(loc) / warning(loc) / info(loc) / click(loc) / plant(loc) / harvest(loc) / breakBlock(loc)
// 成功特效
EffectUtils.Presets.success(player.location)
// 圆形粒子环
EffectUtils.spawnCircle(player.location, Particle.FLAME, /* 半径、点数等 */)
// 仅给单个玩家放声音
EffectUtils.playSoundForPlayer(player, Sound.ENTITY_PLAYER_LEVELUP)ItemUtils(物品)
物品创建与编辑的便捷封装。
| 方法 | 说明 |
|---|---|
isEmpty(item) / isNotEmpty(item) | 判空(含 AIR 与 null) |
createItem(material, amount, name, lore, cmd) | 一次性创建带名/Lore/CustomModelData 的物品 |
setDisplayName(item, name) / getDisplayName(item) | 显示名读写 |
setLore(item, lore) / getLore(item) / addLoreLine(item, line) | Lore 读写与追加 |
setCustomModelData(item, cmd) / getCustomModelData(item) / hasCustomModelData(item) | CustomModelData 读写与判断 |
isSimilar(a, b) / compareWithNbt(a, b) | 相似比较 / 带 NBT 比较 |
clone(item, amount?) | 克隆物品(可指定数量) |
takeItem(item, amount) | 扣减数量 |
val sword = ItemUtils.createItem(
Material.DIAMOND_SWORD,
1,
"<gold>秦淮之刃", // 支持 MiniMessage
listOf("<gray>一把好剑"),
100100 // CustomModelData
)
ItemUtils.addLoreLine(sword, "<yellow>已强化")
if (ItemUtils.isNotEmpty(sword) && ItemUtils.hasCustomModelData(sword)) {
val cloned = ItemUtils.clone(sword, 2)
}LocationUtils(坐标)
坐标序列化、计算与范围查询。
| 方法 | 说明 |
|---|---|
serialize(loc) / deserialize(world, x, y, z, yaw, pitch) | 坐标序列化/反序列化 |
serializeList(...) / deserializeList(...) | 列表序列化(分号分隔) |
getBlockLocation(loc) / getCenterLocation(loc) | 方块坐标 / 方块中心坐标 |
distance(a, b) / distanceSquared(a, b) | 距离 / 距离平方 |
isSameBlock(a, b) | 是否同一方块 |
getDirection(from, to) / lookAt(loc, target) | 方向向量 / 朝向目标 |
getNearbyLocations(...) / getNearbyBlocks(...) | 附近坐标 / 附近方块 |
isInRange(a, b, range) | 是否在范围内 |
getChunkKey(loc) | 区块键 |
val serialized = LocationUtils.serialize(player.location)
// 列表用分号分隔
val listStr = LocationUtils.serializeList(spawnPoints)
val center = LocationUtils.getCenterLocation(block.location)
if (LocationUtils.isInRange(player.location, target, 5.0)) {
val dir = LocationUtils.getDirection(player.location, target)
}ServerCompat(服务端兼容)
跨平台、跨版本兼容层。提供平台探测、版本校验与多名降级解析。
| 方法 / 成员 | 说明 |
|---|---|
detectPlatform() / platformLabel() | 探测平台 / 平台标签 |
bukkitVersionLabel() / parseBukkitVersion() | Bukkit 版本标签 / 解析 |
pluginVersion(plugin) | 插件版本 |
validateServer(logger) | 校验服务端环境 |
validateJava(...) | 校验 Java(要求 ≥ 25) |
validateMinecraftVersion(...) | 校验 Minecraft(要求 ≥ 1.21.11) |
platform | 当前平台 |
supportsPluginLibraries | 是否支持 plugin libraries |
supportsAsyncChatEvent | 是否支持异步聊天事件 |
resolveSound(...) / resolvePotionEffectType(...) | 解析声音 / 药水效果(多名降级) |
sound(vararg) / particle(vararg) / material(vararg) | 多名降级解析声音/粒子/材质 |
ATTR_MAX_HEALTH 等 | 属性常量 |
applyMaxStackSize(...) | 应用最大堆叠数 |
playBlockStepEffect(...) | 播放方块脚步特效 |
多名降级
// 不同版本中材质名可能不同,按顺序尝试,命中第一个有效项
val mat = ServerCompat.material("GRASS_BLOCK", "GRASS")
val snd = ServerCompat.sound("BLOCK_NOTE_BLOCK_PLING", "NOTE_PLING")
val particle = ServerCompat.particle("DUST", "REDSTONE")
// 启动期环境校验
ServerCompat.validateServer(logger) // Java ≥ 25、MC ≥ 1.21.11TextUtil(文本,单数)
文本到 Component 的转换与彩色输出。toComponent 自动识别 MiniMessage / 旧版 & 码 / § 码 / 纯文本。
| 方法 | 说明 |
|---|---|
toComponent(text) | 文本 → Component(自动识别格式) |
colored(text) | 上色 |
sendColored(target, text) | 发送彩色消息 |
logColored(text) | 彩色日志 |
broadcastColored(text) | 彩色广播 |
applyItemDisplay(meta, name, lore) | 给 ItemMeta 套用名与 Lore |
showColoredTitle(player, text, fadeIn, stay, fadeOut) | 显示彩色标题 |
Extensions.kt 扩展函数
| 扩展 | 说明 |
|---|---|
String.toComponent() | 字符串 → Component |
String.colored() | 字符串上色 |
String.parseMiniMessage() | 解析 MiniMessage |
Player.sendColoredMessage(text) | 给玩家发彩色消息 |
String.replacePlaceholders(vararg pairs) | 替换占位符 |
Player.maxHealthValue() | 取玩家最大生命值 |
import com.qinhuai.corelib.util.toComponent
import com.qinhuai.corelib.util.colored
import com.qinhuai.corelib.util.sendColoredMessage
import com.qinhuai.corelib.util.replacePlaceholders
import com.qinhuai.corelib.util.maxHealthValue
player.sendColoredMessage("<gold>欢迎回来")
val title = "你好 %name%".replacePlaceholders("%name%" to player.name)
TextUtil.showColoredTitle(player, "<rainbow>胜利", 10, 40, 10)
val hp = player.maxHealthValue()TextUtils(文本工具,复数)
格式化与字符串处理工具。
| 方法 | 说明 |
|---|---|
formatNumber(double, 小数位) / formatNumber(int) | 数字格式化 |
formatTime(ms) | 毫秒 → 1小时30分45秒 |
formatTimeCompact(ms) | 毫秒 → 紧凑时间 |
joinList(list, sep, lastSep) | 拼接列表(含末项分隔符) |
capitalize(s) / toTitleCase(s) | 首字母大写 / 标题式 |
limitLength(s, n) | 截断长度 |
stripColors(s) | 去颜色码 |
countOccurrences(s, sub) | 子串出现次数 |
levenshteinDistance(a, b) | 编辑距离 |
findSimilar(...) | 模糊匹配 |
TextUtils.formatNumber(1234.5678, 2) // "1234.57"
TextUtils.formatTime(5445000L) // "1小时30分45秒"
TextUtils.joinList(listOf("甲", "乙", "丙"), "、", " 和 ") // "甲、乙 和 丙"
// 输错命令时给出近似建议
val guess = TextUtils.findSimilar(input, validCommands)HologramManager(全息)
基于 ArmorStand 的全息文字。支持 MiniMessage,行高 0.25。被附加到实体上时,每秒自动重绑跟随。
HologramManager 方法
| 方法 | 说明 |
|---|---|
create(id, location, vararg lines) | 在坐标创建全息 |
createAsPassenger(id, entity, offsetY, vararg lines) | 作为实体乘客创建(跟随实体) |
get(id) / remove(id) / removeAll() / getAll() | 取/移除/全移除/取全部 |
showTemporary(location, text, durationTicks) | 临时全息(到时自动消失) |
showPlayerBubble(player, text, fadeIn, stay, fadeOut) | 玩家头顶气泡 |
Hologram 实例方法
| 方法 | 说明 |
|---|---|
setLine(index, text) / addLine(text) / removeLine(index) | 行编辑 |
getLines() / clearLines() | 取所有行 / 清空 |
update() | 刷新显示 |
setPassenger(entity, offsetY) / removePassenger() | 绑定/解绑实体乘客 |
show() / hide() | 显示 / 隐藏 |
teleport(location) | 传送 |
delete() | 删除 |
startRepairTask() / stopRepairTask() | 启/停自动重绑任务 |
val holo = HologramManager.create(
"shop-1",
shopLocation,
"<gold>秦淮商店",
"<gray>右键购买"
)
holo.addLine("<yellow>库存充足")
holo.update()
// 头顶气泡
HologramManager.showPlayerBubble(player, "<aqua>升级了!", 5, 40, 10)
// 临时提示,60 tick 后消失
HologramManager.showTemporary(loc, "<green>+10 金币", 60)
// 跟随实体
val mobHolo = HologramManager.createAsPassenger("boss-hp", boss, 2.5, "<red>BOSS 血量")AssemblySystem(动态装配)
动态物品显示分层系统:把名称、Lore 等显示拆成可叠加的 DisplayLayer,按 priority 升序应用。
核心类型
DisplayLayer接口:priority、apply(meta, AssemblyContext)。AssemblyContext(variables):携带变量上下文。ItemAssembly:addLayer(layer)、apply(item, context): ItemStack。- 预置层:
NameLayer(priority) { ctx -> String }LoreLayer(priority) { ctx -> List<String> }
val assembly = ItemAssembly()
assembly.addLayer(NameLayer(0) { ctx ->
"<gold>${ctx.variables["itemName"]}"
})
assembly.addLayer(LoreLayer(10) { ctx ->
listOf(
"<gray>等级:${ctx.variables["level"]}",
"<yellow>攻击:${ctx.variables["atk"]}"
)
})
val context = AssemblyContext(mapOf(
"itemName" to "秦淮之刃",
"level" to "5",
"atk" to "120"
))
val finalItem: ItemStack = assembly.apply(baseItem, context)自定义层:实现 DisplayLayer 接口,重写 priority 与 apply(meta, ctx),再 addLayer 即可。所有层按 priority 从小到大依次作用于同一 ItemMeta。