从零开始学习 TabooLib 插件开发,包含完整的代码示例与实战经验
什么是 TabooLib?TabooLib 是一个基于 Kotlin 的 Minecraft 跨平台服务端插件开发框架,仅占 30+ KB 插件体积,却提供了完整的模块化开发能力。
一套代码,多端运行
按需加载,减少体积
现代化 API 设计
| 类型 | 模块 | 功能 |
|---|---|---|
| 基础 | Basic | 配置文件、任务调度 |
| 平台 | Bukkit | Bukkit 事件、命令 |
| 工具 | BukkitUtil | 玩家操作、物品构建 |
| 协议 | NMS | 网络包、版本兼容 |
| 脚本 | Kether | 脚本引擎 |
| 界面 | BukkitUI | 箱子菜单 |
基础插件:Basic + Bukkit
功能插件:Basic + Bukkit + BukkitUtil + NMS
完整配置:Basic + Bukkit + BukkitUtil + NMS + Kether + BukkitUI
package com.example.myplugin
import taboolib.common.platform.Plugin
import taboolib.platform.BukkitPlugin
object MyPlugin : BukkitPlugin() {
override fun onEnable() {
logger.info("插件已启动!")
}
override fun onDisable() {
logger.info("插件已关闭!")
}
}为什么用 object?单例模式可以让你在任何地方直接访问插件实例,无需每次调用 getPlugin() 方法。
name: MyPlugin
version: ${version}
main: com.example.myplugin.MyPlugin
api-version: "1.20"
author: YourName
description: 我的第一个 TabooLib 插件
taboolib:
id: myplugin
loader: kotlin注意:TabooLib 6.x 需要 taboolib 配置块,否则无法加载!
配置文件名,相对于插件目录。默认从 plugins/插件ID/ 目录下读取。
启用热重载。当文件在服务器运行时被外部修改,会自动重新加载。
配置迁移路径。当插件更新需要迁移旧配置文件时使用。
是否绑定资源文件。如果为 true,文件不存在时会从 jar 包中释放。
// 基础用法
@Config(file = "config.yml")
lateinit var config: SecuredFile
private set
// 带完整参数
@Config(file = "config.yml", autoReload = true, migrate = "%plugin%/config.yml.old")
lateinit var fullConfig: SecuredFile
private set
// 读取配置
val name = config.getString("plugin.name")
val version = config.getInt("plugin.version")
val enabled = config.getBoolean("plugin.enabled", true)
// 读取列表
val list = config.getStringList("rewards")
// 写入配置
config.set("plugin.name", "MyPlugin")
config.save()| 方法 | 返回类型 | 说明 |
|---|---|---|
getString(path) | String? | 读取字符串 |
getInt(path) | Int | 读取整数 |
getDouble(path) | Double | 读取浮点数 |
getBoolean(path) | Boolean | 读取布尔值 |
getStringList(path) | List<String> | 读取字符串列表 |
getConfigurationSection(path) | MemorySection? | 读取配置节 |
getColoredString(path) | String | 读取并转换颜色 |
set(path, value) | - | 设置值 |
save() | - | 保存到文件 |
reload() | - | 重新加载配置 |
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
delay | Long | 0 | 延迟执行时间(tick) |
period | Long | - | 重复执行周期(tick) |
async | Boolean | false | 是否异步执行 |
owner | Plugin | 当前插件 | 任务所属插件 |
import taboolib.common.platform.function.submit
// 延迟执行(1秒后)
submit(delay = 20) { player.sendMessage("Hello!") }
// 异步执行(不在主线程)
submit(async = true) {
val result = database.query("SELECT * FROM players")
submit { player.sendMessage("查询完成: $result") }
}
// 定时重复执行(每秒)
submit(period = 20) { world.players.forEach { it.health = it.maxHealth } }
// 取消任务
val task = submit(period = 20) { }
task.cancel()| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
priority | EventPriority | NORMAL | 事件优先级 |
ignoreCancelled | Boolean | false | 是否忽略已取消的事件 |
| 优先级 | 使用场景 |
|---|---|
LOWEST | 拦截、取消事件 |
NORMAL | 常规逻辑 |
HIGHEST | 最终修改 |
MONITOR | 统计、数据记录(不应修改事件) |
import taboolib.common.platform.SubscribeEvent
import org.bukkit.event.EventPriority
// 基本用法
@SubscribeEvent
fun onJoin(event: PlayerJoinEvent) {
val player = event.player
player.sendMessage("欢迎来到服务器!")
}
// 指定优先级
@SubscribeEvent(priority = EventPriority.HIGHEST)
fun onHighJoin(event: PlayerJoinEvent) { }
// 忽略已取消的事件
@SubscribeEvent(ignoreCancelled = true)
fun onMove(event: PlayerMoveEvent) { }
// 组合使用
@SubscribeEvent(priority = EventPriority.HIGH, ignoreCancelled = true)
fun onPlayerCommand(event: PlayerCommandPreprocessEvent) { }| 方法 | 说明 |
|---|---|
command(name) | 创建主命令 |
literal(name) | 创建字面子命令 |
dynamic(name) | 创建动态参数 |
execute<T> | 注册执行回调 |
suggestion<T> | 注册补全建议 |
permission | 设置权限要求 |
optional() | 标记为可选参数 |
import taboolib.common.platform.command.*
command("heal", permission = "myplugin.heal") {
execute<Player> { player, _, _ ->
player.health = player.maxHealth
player.sendMessage("§a已恢复生命值!")
}
}
command("teleport", permission = "myplugin.teleport") {
literal("random") {
execute<Player> { player, _, _ ->
val randomPlayer = player.world.players.randomOrNull()
if (randomPlayer != null) {
player.teleport(randomPlayer.location)
}
}
}
dynamic("target", optional = true) {
suggestion<Player> { _, _ -> server.onlinePlayers().map { it.name } }
execute<Player> { player, _, args ->
val target = server.getPlayer(args["target"]!!)
if (target != null) player.teleport(target.location)
}
}
}| 方法 | 参数 | 说明 |
|---|---|---|
name(text) | String | 设置物品名称 |
lore(lines) | String... | 设置描述文本 |
shiny() | - | 添加发光效果 |
unbreakable() | - | 设置不可破坏 |
nbt(key, value) | String, Any | 添加 NBT 数据 |
enchant(enchant, level) | Enchantment, Int | 添加附魔 |
skullOwner(player) | ProxyPlayer/String | 设置头颅所有者 |
editMeta { } | ItemMeta.() -> Unit | 直接编辑元数据 |
build() | - | 构建物品 |
import taboolib.platform.util.*
import taboolib.platform.util.ItemBuilder
import org.bukkit.Material
import org.bukkit.enchantments.Enchantment
// 创建物品
val item = ItemBuilder(Material.DIAMOND_SWORD)
.name("§b钻石剑")
.lore("§7一把锋利的钻石剑", "§7攻击力: 10")
.shiny()
.nbt("custom", "value")
.enchant(Enchantment.DAMAGE_ALL, 5)
.build()
// 玩家头颅
val skull = ItemBuilder(Material.PLAYER_HEAD)
.name("§e${player.name}")
.skullOwner(player)
.build()
// 玩家扩展函数
player.hasItem(Material.DIAMOND, 10) // 检查物品
player.giveItem(ItemStack(Material.DIAMOND)) // 给予物品import taboolib.module.chat.Component
import taboolib.module.chat.HoverEvent
import taboolib.module.chat.ClickEvent
import taboolib.module.chat.hoverAction
import taboolib.module.chat.clickAction
// 悬停显示文本
val component = Component.create("§6[点我打开链接]")
.hover(hoverAction { showText("§e点击打开 GitHub") })
.click(clickAction { openUrl("https://github.com") })
// 点击执行命令
val cmdComponent = Component.text("§e[传送]")
.hover(hoverAction { showText("§7传送到主城") })
.click(clickAction { runCommand("/spawn") })
// 建议补全
val suggestComponent = Component.text("§a[私聊]")
.click(clickAction { suggestCommand("/msg ${player.name} ") })⚠️ NMS 警告:NMS(Net Minecraft Server)是版本相关的代码。不同服务端版本的 NMS 类路径和方法签名完全不同。
import taboolib.library.nms.NMSRegistry
// 获取完整版本字符串
val version = NMSRegistry.getServerVersion() // 如 "v1_20_R3"
// 获取主版本号
val majorVersion = NMSRegistry.getMajorVersion() // 如 20
// 版本范围判断
when {
majorVersion <= 8 -> { /* 1.8.x */ }
majorVersion in 9..12 -> { /* 1.9 - 1.12 */ }
majorVersion in 13..16 -> { /* 1.13 - 1.16 */ }
majorVersion in 17..20 -> { /* 1.17 - 1.20 */ }
majorVersion >= 21 -> { /* 1.21+ */ }
}
// 动态加载 NMS 类
val nmsClass = NMSRegistry.getNMSClass("PacketPlayOutChat")| 方法 | 类型 | 说明 |
|---|---|---|
ItemTagData.ofString(value) | String | 字符串类型 |
ItemTagData.ofInt(value) | Int | 整数类型 |
ItemTagData.ofDouble(value) | Double | 浮点数类型 |
ItemTagData.ofBoolean(value) | Boolean | 布尔类型 |
ItemTagData.ofLong(value) | Long | 长整数类型 |
ItemTagData.ofList(list) | List | 列表类型 |
ItemTagData.ofCompound(tag) | ItemTag | 复合标签 |
import taboolib.module.nms.ItemTag
import taboolib.module.nms.ItemTagData
import taboolib.module.nms.toNBT
import taboolib.module.nms.toItemTag
// 创建 NBT
val tag = ItemTag()
tag["customKey"] = ItemTagData.ofString("value")
tag["number"] = ItemTagData.ofInt(123)
tag["enabled"] = ItemTagData.ofBoolean(true)
// 应用到物品
val itemWithNBT = tag.toItemTag(itemStack)
// 读取物品的 NBT
val readTag = itemWithNBT.toNBT()
val customValue = readTag["customKey"]?.asString()
val intValue = readTag["number"]?.asInt()ofString() 保存的值必须用 .asString() 读取。Kether 简介:Kether 是一个以单行语句驱动的脚本语言,适合配置文件中的动作定义和 UI 菜单的点击事件。
| 动作 | 语法 | 说明 |
|---|---|---|
| tell | tell "消息" | 发送消息 |
| cast | cast "类型" | 类型转换 |
| cmd / command | cmd "/命令" | 执行命令 |
| set | set{ "key" to value } | 设置变量 |
| get | get{ "key" } | 获取变量 |
| if / unless | if { } then { } | 条件判断 |
| foreach | foreach [list] { } | 循环遍历 |
| loop | loop 10 { } | 循环执行 |
| delay | delay 20 { } | 延迟执行 |
| async | async { } | 异步执行 |
// 变量
set{ "name" to "苏屿" }
set{ "level" to 100 }
// 发送消息
tell "Hello World!"
tell color "&a绿色消息"
tell color inline "&bHello {{ player }}"
// 执行命令
cmd "/spawn" as console
cmd "/eco give {{ player }} 1000" as player
// 条件判断
if{ "%player_level%" >= 50 } then {
tell "你达到了50级!"
} else {
tell "继续加油!"
}
// 循环
foreach [1, 2, 3, 4, 5] {
tell "%loop_value%"
}
// 延迟
delay 20 {
tell "延迟后的消息"
}import taboolib.module.kether.*
import taboolib.common.platform.ProxyPlayer
// 注册自定义动作
@KetherProperty("myplugin")
object MyActions {
@KetherShell(shared = true)
val heal = KetherAction.ofScript { script ->
script.frame.newFrame {
script.executor().thenAccept { value ->
val amount = (value as? Number)?.toDouble() ?: 20.0
val player = script.sender?.castSafely<ProxyPlayer>()
player?.health = amount
}
}
}
}
// 代码中调用 Kether
fun executeKetherScript(player: ProxyPlayer, script: String) {
KetherShell.eval(script, sender = player).thenAccept { result ->
println("Script result: $result")
}
}