taboolib-dev
git clone https://github.com/TabooLib/taboolib-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/TabooLib/taboolib-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/taboolib-dev" ~/.claude/skills/taboolib-taboolib-skill-taboolib-dev && rm -rf "$T"
skills/taboolib-dev/SKILL.mdTabooLib Plugin Development
Overview
TabooLib is a Kotlin-based cross-platform Minecraft plugin framework (supports Bukkit/Spigot/Paper, BungeeCord, Velocity). Current stable version: 6.1.2-beta12, Gradle plugin: 2.0.31.
Key traits:
- ~30KB runtime footprint (modules downloaded on demand)
- Kotlin DSL APIs for commands, UI, config, database
- Cross-version NMS abstraction (1.8 ~ 1.21+)
- Annotation-driven lifecycle (
,@Awake
,@SubscribeEvent
,@Schedule
)@Config
Instructions
Step 1: Project Setup
When user wants to create a new TabooLib plugin:
- Generate
with the TabooLib Gradle plugin:build.gradle.kts
import io.izzel.taboolib.gradle.* plugins { java id("io.izzel.taboolib") version "2.0.31" kotlin("jvm") version "2.0.0" } taboolib { env { install(UNIVERSAL, BUKKIT) // Add modules as needed: CONFIGURATION, LANG, CHAT, NMS, NMS_UTIL, UI, DATABASE, KETHER } version { taboolib = "6.1.2-beta12" } } repositories { mavenCentral() } dependencies { compileOnly("ink.ptms.core:v12004:12004:mapped") compileOnly("ink.ptms.core:v12004:12004:universal") compileOnly(kotlin("stdlib")) taboo("ink.ptms:um:1.2.0") // Universal Minecraft API } tasks.withType<JavaCompile> { options.encoding = "UTF-8" }
- Create the plugin main class (use
, NOTobject
):class
import taboolib.common.platform.Plugin object MyPlugin : Plugin() { override fun onLoad() { } override fun onEnable() { info("Plugin enabled!") } override fun onActive() { } // Called after server fully started override fun onDisable() { } }
- No
needed — TabooLib auto-generates it.plugin.yml
Step 2: Module Selection Guide
Help user pick the right modules based on their needs:
| Need | Module Constant | Notes |
|---|---|---|
| Config files (YAML/TOML/HOCON) | | Enables , |
| Multi-language / i18n | | Requires CONFIGURATION |
| Rich text / clickable messages | | Components API |
| NMS / packet operations | , | Cross-version abstraction |
| Chest GUI / Anvil GUI | | Requires CHAT + NMS |
| Database (MySQL/SQLite) | | HikariCP connection pool |
| Kether scripting | | Requires CONFIGURATION |
| Bukkit utilities (ItemBuilder etc) | | XMaterial, ItemBuilder |
| PlaceholderAPI / Vault hooks | | Third-party integration |
| BungeeCord messaging | | Cross-server communication |
Module dependencies (auto-resolved by Gradle plugin, but useful to know):
depends onUI
+CHATNMS
depends onLANGCONFIGURATION
depends onNMS_UTILNMS
Step 3: Core API Patterns
Always follow these patterns when writing TabooLib code:
Pattern: Singleton Object + @Awake
@Awake object PlayerManager { private val cache = ConcurrentHashMap<UUID, PlayerData>() @Awake(LifeCycle.ENABLE) fun init() { /* startup logic */ } @SubscribeEvent fun onQuit(event: PlayerQuitEvent) { cache.remove(event.player.uniqueId) } @Schedule(async = true, period = 6000L) fun autoSave() { cache.values.forEach { it.save() } } }
Pattern: Config Injection
@Awake object Settings { @Config("config.yml", autoReload = true) lateinit var config: Configuration @ConfigNode("debug") var debug = false @ConfigNode("settings.max-players") var maxPlayers = 100 }
Pattern: Command DSL
command("myplugin", aliases = listOf("mp"), permission = "myplugin.use") { literal("reload") { execute<ProxyCommandSender> { sender, _, _ -> Settings.config.reload() sender.sendMessage("Reloaded!") } } dynamic("player") { suggestion<ProxyCommandSender> { _, _ -> Bukkit.getOnlinePlayers().map { it.name } } execute<ProxyCommandSender> { sender, _, argument -> sender.sendMessage("Target: $argument") } } }
Pattern: Event Listening
// Annotation-based (recommended) @Awake object Listeners { @SubscribeEvent fun onJoin(event: PlayerJoinEvent) { event.player.sendMessage("Welcome!") } @SubscribeEvent(priority = EventPriority.HIGHEST, ignoreCancelled = true) fun onDamage(event: EntityDamageEvent) { /* ... */ } } // Functional style listenEvent<PlayerJoinEvent> { it.player.sendMessage("Welcome!") }
Pattern: Chest UI
player.openMenu<Chest> { title("My Menu") rows(3) map( "# # # # # # # # #", "# A # B # C # D #", "# # # # # # # # #" ) set('A', buildItem(XMaterial.DIAMOND) { name = "&bDiamond"; colored() }) onClick('A') { event -> event.clicker.sendMessage("Clicked!") } handLocked(false) }
Pattern: Database Table
val table = Table<HostSQL, Connection>("player_data", host) { add("uuid") { type(ColumnTypeSQL.VARCHAR, 36) } add("data") { type(ColumnTypeSQL.TEXT) } } // Always use async for DB operations submitAsync { table.createTable(dataSource) table.select(dataSource) { where { "uuid" eq player.uniqueId.toString() } }.firstOrNull() }
Pattern: Task Scheduling
// Delayed sync task (20 ticks = 1 second) submit(delay = 20L) { /* runs on main thread */ } // Async repeating task submit(async = true, period = 20L) { /* every 1 second, off main thread */ } // Annotation-based @Schedule(async = true, period = 100L, delay = 20L) fun periodicTask() { /* called automatically */ }
Pattern: NMS Version Guard
if (MinecraftVersion.isHigherOrEqual(MinecraftVersion.V1_17)) { // 1.17+ specific code } // majorLegacy: 12004 = 1.20.4 val ver = MinecraftVersion.majorLegacy
Step 4: Validation Checklist
Before the plugin is considered complete, verify:
-
has correctbuild.gradle.kts
block with needed modulestaboolib {} - Main class is an
extendingobject
, not aPlugin()class - All stateful components use
, not global variables@Awake object - Database operations are wrapped in
submitAsync {} - NMS-dependent code has version guards
- Commands have proper permission nodes
- Config files use
+@Config
for auto-injection@ConfigNode - No blocking operations on the main thread
Examples
Example 1: Basic Plugin with Config and Command
User says: "Create a TabooLib plugin with a config and a reload command"
Action:
- Set up
withbuild.gradle.kts
,UNIVERSAL
,BUKKITCONFIGURATION - Create
object main classPlugin - Create
object withSettings@Config - Create command with
subcommandreload - Create default
config.yml
Example 2: Plugin with Chest GUI
User says: "I need a shop GUI plugin with TabooLib"
Action:
- Set up with
,UNIVERSAL
,BUKKIT
,CONFIGURATION
,CHAT
,NMS
,NMS_UTIL
,UIBUKKIT_ALL - Create config for shop items
- Build Chest menu with
layoutmap() - Handle click events for purchase logic
- Add NMS version check if using version-specific features
Example 3: Plugin with Database
User says: "Player data storage plugin with MySQL"
Action:
- Set up with
,UNIVERSAL
,BUKKIT
,CONFIGURATIONDATABASE - Define table schema using
APITable - Create async data load/save in
handlers@SubscribeEvent - Use
for all DB operationssubmitAsync {} - Configure
with database connection settingsconfig.yml
Troubleshooting
Error: Module not found at runtime
- Cause: Module not installed in
taboolib { env { install(...) } } - Solution: Add the missing module constant. Check
for dependencies.references/module-map.md
Error: @Awake object not being discovered
- Cause: Missing
annotation on the object, or object is in a package not scanned@Awake - Solution: Ensure
is on the object declaration. TabooLib scans all classes in the plugin JAR.@Awake
Error: NMS operation fails on specific MC version
- Cause: NMS API changed between versions
- Solution: Use
guards. For packet operations, check field names per version.MinecraftVersion.isHigherOrEqual()
Error: Config not reloading
- Cause:
not set, orautoReload = true
not activeFileWatcher - Solution: Set
inautoReload = true
. For manual reload call@Config
.config.reload()
Error: UI clicks not registering
- Cause: Missing
or wrong slot mappinghandLocked(false) - Solution: Verify
character assignments matchmap()
andset()
characters.onClick()
Error: Gradle build fails with TabooLib plugin
- Cause: Version mismatch or repository not configured
- Solution: Ensure
plugin version matches, andio.izzel.taboolib
is in repositories.mavenCentral()
Error: Chinese filename causes server crash on restart
- Known bug: Avoid Chinese characters in config file names. Use ASCII-only filenames.