+
+---
+
+KPaper is a powerful utility library designed to simplify plugin development with [Paper](https://papermc.io/) and [Kotlin](https://kotlinlang.org/). It provides Kotlin-friendly APIs, intuitive abstractions, and comprehensive tools to make Paper plugin development **faster**, **cleaner**, and **more enjoyable**.
+
+## ✨ Key Features
+
+- **🎯 Kotlin-First Design** - Built specifically for Kotlin with idiomatic APIs
+- **⚡ Event System** - Simplified event handling with powerful filtering and custom events
+- **🎮 GUI Framework** - Intuitive inventory and GUI creation with built-in interactions
+- **⌨️ Command Builder** - Fluent command creation with automatic argument parsing
+- **🔧 Rich Extensions** - Extensive Kotlin extensions for Bukkit/Paper classes
+- **🔄 Coroutines Support** - Full async/await support with Kotlin coroutines
+- **🌍 World Management** - Advanced world generation and manipulation tools
+- **💬 Message System** - Internationalization and rich text formatting
+- **🎨 Visual Effects** - Particle systems and display management
+- **🎯 Game Mechanics** - Timers, countdowns, and player management systems
+
+## 🚀 Quick Start
+
+### Installation
```kotlin
repositories {
+ mavenCentral()
maven("https://nexus.modlabs.cc/repository/maven-mirrors/")
}
dependencies {
- implementation("cc.modlabs:KPaper:$version")
+ implementation("cc.modlabs:KPaper:LATEST") // Replace with latest version
}
```
-### Example Usage
-
-Here's a basic example of using KPaper in a plugin:
+### Your First Plugin
```kotlin
-import cc.modlabs.kpaper.event.listen
import cc.modlabs.kpaper.main.KPlugin
+import cc.modlabs.kpaper.event.listen
+import cc.modlabs.kpaper.command.CommandBuilder
+import cc.modlabs.kpaper.inventory.ItemBuilder
+import cc.modlabs.kpaper.inventory.simple.simpleGUI
+import org.bukkit.Material
import org.bukkit.event.player.PlayerJoinEvent
class MyPlugin : KPlugin() {
override fun startup() {
- listen {
- it.player.sendMessage("Welcome to the server, ${it.player.name}!")
+ // 🎉 Event handling made simple
+ listen { event ->
+ val player = event.player
+ player.sendMessage("Welcome to the server, ${player.name}!")
+
+ // Give welcome items
+ val welcomeItem = ItemBuilder(Material.DIAMOND)
+ .name("&6Welcome Gift!")
+ .lore("&7Thanks for joining our server!")
+ .glowing(true)
+ .build()
+
+ player.inventory.addItem(welcomeItem)
+ }
+
+ // ⌨️ Commands with fluent API
+ CommandBuilder("shop")
+ .description("Open the server shop")
+ .permission("shop.use")
+ .playerOnly(true)
+ .execute { sender, _ ->
+ openShopGUI(sender as Player)
+ }
+ .register()
+ }
+
+ private fun openShopGUI(player: Player) {
+ // 🎮 Beautiful GUIs in seconds
+ val gui = simpleGUI("Server Shop", 27) {
+
+ item(13, ItemBuilder(Material.DIAMOND_SWORD)
+ .name("&cWeapons")
+ .lore("&7Click to browse weapons!")
+ .build()) {
+ player.sendMessage("&aOpening weapons shop...")
+ // Open weapons submenu
+ }
+
+ item(22, ItemBuilder(Material.BARRIER)
+ .name("&cClose")
+ .build()) {
+ player.closeInventory()
+ }
+ }
+
+ player.openInventory(gui)
+ }
+}
+```
+
+## 💡 Examples
+
+
+Advanced Event Handling
+
+```kotlin
+// Multi-event listener with conditions
+listen { event ->
+ val player = event.player
+ val block = event.block
+
+ // Only in mining world
+ if (block.world.name != "mining") return@listen
+
+ // Give experience based on block type
+ when (block.type) {
+ Material.DIAMOND_ORE -> player.giveExp(50)
+ Material.GOLD_ORE -> player.giveExp(25)
+ Material.IRON_ORE -> player.giveExp(10)
+ else -> player.giveExp(1)
+ }
+
+ // Custom drop with chance
+ if (Random.nextDouble() < 0.1) { // 10% chance
+ val bonus = ItemBuilder(Material.DIAMOND)
+ .name("&bBonus Diamond!")
+ .build()
+ block.world.dropItem(block.location, bonus)
+ }
+}
+
+// Custom event creation and handling
+class PlayerLevelUpEvent(val player: Player, val newLevel: Int) : KEvent()
+
+fun levelUpPlayer(player: Player, level: Int) {
+ PlayerLevelUpEvent(player, level).callEvent()
+}
+
+listen { event ->
+ val rewards = mapOf(
+ 10 to listOf(ItemStack(Material.DIAMOND, 5)),
+ 25 to listOf(ItemStack(Material.NETHERITE_INGOT, 1)),
+ 50 to listOf(ItemStack(Material.DRAGON_EGG, 1))
+ )
+
+ rewards[event.newLevel]?.forEach { reward ->
+ event.player.inventory.addItem(reward)
+ }
+}
+```
+
+
+
+Complex GUI Systems
+
+```kotlin
+// Paginated shop with categories
+class ShopGUI(private val category: ShopCategory) : KGUI() {
+ override val title = "Shop - ${category.name}"
+ override val size = 54
+
+ private var page = 0
+ private val itemsPerPage = 28
+
+ override fun build(player: Player): GUI {
+ return GUI(title, size) {
+ // Category tabs
+ ShopCategory.values().forEachIndexed { index, cat ->
+ item(index, ItemBuilder(cat.icon)
+ .name(if (cat == category) "&a${cat.name}" else "&7${cat.name}")
+ .glowing(cat == category)
+ .build()) {
+ ShopGUI(cat).open(player)
+ }
+ }
+
+ // Items grid
+ val items = category.getItems()
+ val startIndex = page * itemsPerPage
+
+ items.drop(startIndex).take(itemsPerPage).forEachIndexed { index, item ->
+ val slot = 18 + (index % 7) + (index / 7) * 9
+
+ item(slot, ItemBuilder(item.material)
+ .name("&e${item.name}")
+ .lore(
+ "&7Price: &a$${item.price}",
+ "&7Stock: &b${item.stock}",
+ "",
+ "&eLeft-click to buy 1",
+ "&eRight-click to buy stack"
+ )
+ .build()) { clickEvent ->
+
+ val amount = if (clickEvent.isRightClick) item.maxStackSize else 1
+ purchaseItem(player, item, amount)
+ }
+ }
+
+ // Navigation
+ if (page > 0) {
+ item(48, ItemBuilder(Material.ARROW)
+ .name("&7← Previous Page")
+ .build()) {
+ page--
+ refresh(player)
+ }
+ }
+
+ if (startIndex + itemsPerPage < items.size) {
+ item(50, ItemBuilder(Material.ARROW)
+ .name("&7Next Page →")
+ .build()) {
+ page++
+ refresh(player)
+ }
+ }
+ }
+ }
+}
+```
+
+
+
+Advanced Commands
+
+```kotlin
+// Complex command with sub-commands and validation
+CommandBuilder("economy")
+ .description("Economy management commands")
+ .permission("economy.admin")
+
+ // /economy balance [player]
+ .subcommand("balance")
+ .argument(playerArgument("target", optional = true))
+ .execute { sender, args ->
+ val target = args.getPlayerOrSelf("target", sender)
+ val balance = economyAPI.getBalance(target)
+ sender.sendMessage("${target.name}'s balance: $${balance}")
+ }
+
+ // /economy pay
+ .subcommand("pay")
+ .argument(playerArgument("target"))
+ .argument(doubleArgument("amount", min = 0.01))
+ .playerOnly(true)
+ .execute { sender, args ->
+ val player = sender as Player
+ val target = args.getPlayer("target")
+ val amount = args.getDouble("amount")
+
+ if (economyAPI.getBalance(player) < amount) {
+ player.sendMessage("&cInsufficient funds!")
+ return@execute
+ }
+
+ economyAPI.transfer(player, target, amount)
+ player.sendMessage("&aSent $${amount} to ${target.name}")
+ target.sendMessage("&aReceived $${amount} from ${player.name}")
+ }
+
+ // /economy set
+ .subcommand("set")
+ .permission("economy.admin.set")
+ .argument(playerArgument("target"))
+ .argument(doubleArgument("amount", min = 0.0))
+ .execute { sender, args ->
+ val target = args.getPlayer("target")
+ val amount = args.getDouble("amount")
+
+ economyAPI.setBalance(target, amount)
+ sender.sendMessage("&aSet ${target.name}'s balance to $${amount}")
+ }
+ .register()
+```
+
+
+
+Async Operations & Coroutines
+
+```kotlin
+// Database operations with coroutines
+class PlayerDataManager {
+
+ suspend fun loadPlayerData(player: Player): PlayerData {
+ return withContext(Dispatchers.IO) {
+ database.getPlayerData(player.uniqueId)
+ }
+ }
+
+ fun setupPlayer(player: Player) {
+ // Load data asynchronously
+ launch {
+ val data = loadPlayerData(player)
+
+ // Switch back to main thread for Bukkit operations
+ withContext(Dispatchers.Main) {
+ applyPlayerData(player, data)
+ player.sendMessage("&aData loaded successfully!")
+ }
+ }
+ }
+
+ // Delayed operations
+ fun startWelcomeSequence(player: Player) {
+ launch {
+ player.sendMessage("&eWelcome to the server!")
+
+ delay(2000) // 2 seconds
+ player.sendMessage("&eLoading your data...")
+
+ val data = loadPlayerData(player)
+
+ delay(1000) // 1 second
+ withContext(Dispatchers.Main) {
+ player.sendMessage("&aReady to play!")
+ teleportToSpawn(player)
+ }
+ }
+ }
+}
+```
+
+
+## 📚 Documentation
+
+### 🏁 Getting Started
+- **[Installation & Setup](docs/getting-started/installation.md)** - Get up and running quickly
+- **[Your First Plugin](docs/getting-started/first-plugin.md)** - Step-by-step tutorial
+- **[Migration Guide](docs/getting-started/migration.md)** - Move from vanilla Bukkit/Paper
+
+### 🔧 Core Concepts
+- **[Plugin Development](docs/core/plugin-development.md)** - Understanding KPlugin and features
+- **[Feature Configuration](docs/core/feature-config.md)** - Managing plugin capabilities
+
+### 📖 API Reference
+- **[Event System](docs/api/events.md)** - Event handling and custom events
+- **[Command Framework](docs/api/commands.md)** - Creating powerful commands
+- **[Inventory & GUI](docs/api/inventory.md)** - Interactive menus and item builders
+- **[Extensions](docs/api/extensions.md)** - Kotlin extensions for Bukkit classes
+- **[Utilities](docs/api/utilities.md)** - Helper functions and tools
+- **[Coroutines](docs/api/coroutines.md)** - Async programming support
+- **[World Management](docs/api/world.md)** - World generation and manipulation
+- **[Messages & I18n](docs/api/messages.md)** - Internationalization support
+- **[Visual Effects](docs/api/visuals.md)** - Particles and displays
+- **[Game Mechanics](docs/api/game.md)** - Timers and game systems
+
+### 💡 Examples & Guides
+- **[Common Patterns](docs/examples/common-patterns.md)** - Frequently used patterns
+- **[Complete Plugins](docs/examples/plugins.md)** - Full plugin examples
+- **[Best Practices](docs/examples/best-practices.md)** - Recommended approaches
+
+## 🏗️ Architecture
+
+KPaper is organized into focused, cohesive packages:
+
+| Package | Purpose | Key Features |
+|---------|---------|--------------|
+| **`main`** | Core plugin functionality | KPlugin base class, feature management |
+| **`event`** | Event handling system | Simplified listeners, custom events |
+| **`command`** | Command framework | Fluent builders, argument parsing |
+| **`inventory`** | GUI and item systems | Item builders, interactive menus |
+| **`extensions`** | Kotlin extensions | Enhanced Bukkit/Paper APIs |
+| **`coroutines`** | Async operations | Kotlin coroutines integration |
+| **`util`** | Utility functions | Logging, text processing, helpers |
+| **`world`** | World management | Generation, manipulation tools |
+| **`messages`** | Messaging system | I18n, formatting, components |
+| **`visuals`** | Visual effects | Particles, displays, animations |
+| **`game`** | Game mechanics | Timers, countdowns, player systems |
+| **`file`** | File management | Configuration, serialization |
+
+## 🎯 Why Choose KPaper?
+
+### Before KPaper (Vanilla Paper/Bukkit)
+```kotlin
+class OldPlugin : JavaPlugin() {
+ override fun onEnable() {
+ // Verbose event registration
+ server.pluginManager.registerEvents(object : Listener {
+ @EventHandler
+ fun onPlayerJoin(event: PlayerJoinEvent) {
+ event.player.sendMessage("Welcome!")
+ }
+ }, this)
+
+ // Manual command registration
+ getCommand("test")?.setExecutor { sender, _, _, args ->
+ if (sender !is Player) {
+ sender.sendMessage("Players only!")
+ return@setExecutor false
+ }
+
+ if (args.isEmpty()) {
+ sender.sendMessage("Usage: /test ")
+ return@setExecutor false
+ }
+
+ sender.sendMessage("You said: ${args.joinToString(" ")}")
+ true
+ }
+
+ // Complex inventory creation
+ val inventory = server.createInventory(null, 27, "My GUI")
+ val item = ItemStack(Material.DIAMOND_SWORD)
+ val meta = item.itemMeta
+ meta.displayName(Component.text("Click me!"))
+ item.itemMeta = meta
+ inventory.setItem(13, item)
+ // ... more boilerplate
+ }
+}
+```
+
+### With KPaper
+```kotlin
+class NewPlugin : KPlugin() {
+ override fun startup() {
+ // Clean, concise event handling
+ listen {
+ it.player.sendMessage("Welcome!")
+ }
+
+ // Fluent command building
+ command("test") {
+ description = "Test command"
+ playerOnly = true
+ execute { sender, args ->
+ if (args.isEmpty()) {
+ sender.sendMessage("Usage: /test ")
+ return@execute
+ }
+ sender.sendMessage("You said: ${args.joinToString(" ")}")
+ }
+ }
+
+ // Intuitive GUI creation
+ val gui = simpleGUI("My GUI", 27) {
+ item(13, ItemBuilder(Material.DIAMOND_SWORD)
+ .name("Click me!")
+ .build()) {
+ player.sendMessage("Button clicked!")
+ }
}
}
}
```
-## Package Structure
+**Result:** 60% less boilerplate, 90% more readable, 100% more maintainable!
+
+## 🤝 Contributing
+
+We welcome contributions from the community! Whether you're fixing bugs, adding features, or improving documentation, your help makes KPaper better for everyone.
+
+### How to Contribute
+
+1. **Fork the repository** on GitHub
+2. **Create a feature branch** (`git checkout -b feature/amazing-feature`)
+3. **Make your changes** following our coding standards
+4. **Add tests** for new functionality
+5. **Update documentation** as needed
+6. **Commit your changes** (`git commit -m 'Add amazing feature'`)
+7. **Push to your branch** (`git push origin feature/amazing-feature`)
+8. **Open a Pull Request** with a clear description
+
+### Development Setup
+
+```bash
+git clone https://github.com/ModLabsCC/KPaper.git
+cd KPaper
+./gradlew build
+```
+
+### Contribution Guidelines
+
+- Follow [Kotlin coding conventions](https://kotlinlang.org/docs/coding-conventions.html)
+- Add KDoc comments for public APIs
+- Include unit tests for new features
+- Update documentation for user-facing changes
+- Keep commits focused and atomic
+
+## 📄 License
+
+KPaper is licensed under the **GPL-3.0 License**. See [LICENSE](LICENSE) for details.
+
+## 🙏 Acknowledgments
-KPaper is organized into several focused packages:
+- **[Paper](https://papermc.io/)** - For providing exceptional server software that makes modern Minecraft development possible
+- **[Fruxz](https://github.com/TheFruxz)** - For inspiration and foundational libraries that helped shape KPaper's design
+- **[Kotlin](https://kotlinlang.org/)** - For creating a language that makes JVM development enjoyable
+- **Our Contributors** - Thank you to everyone who has contributed code, documentation, and feedback!
-- **`util`** - Utility functions including console output, logging, random number generation, and text processing
-- **`extensions`** - Kotlin extension functions for Bukkit/Paper classes
-- **`event`** - Event handling system with custom events and listeners
-- **`inventory`** - Inventory management, item builders, and GUI systems
-- **`command`** - Command framework and argument parsing
-- **`main`** - Core plugin functionality and feature configuration
-- **`world`** - World generation and manipulation utilities
-- **`messages`** - Message formatting and translation support
-- **`visuals`** - Visual effects and display systems
-- **`game`** - Game mechanics like countdowns and player management
-- **`file`** - File I/O and configuration management
-- **`coroutines`** - Kotlin coroutines integration for async operations
+---
-Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests on the [GitHub repository](https://github.com/ModLabsCC/KPaper).
+
-## Acknowledgments
+**Made with ❤️ by the ModLabs Team**
-- [Paper](https://papermc.io/) for providing the high-performance server software.
-- [Fruxz](https://github.com/TheFruxz) for the inspiration and libraries that helped shape KPaper.
+[Website](https://modlabs.cc) • [Discord](https://discord.gg/modlabs) • [GitHub](https://github.com/ModLabsCC)
-
+
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..52a5c87
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,74 @@
+# KPaper Documentation
+
+Welcome to the comprehensive documentation for KPaper - a Kotlin utility library that simplifies Paper plugin development.
+
+## 📚 Documentation Overview
+
+### Getting Started
+- [Installation & Setup](getting-started/installation.md) - Set up KPaper in your project
+- [Your First Plugin](getting-started/first-plugin.md) - Create your first KPaper plugin
+- [Migration Guide](getting-started/migration.md) - Migrate from vanilla Bukkit/Paper
+
+### Core Concepts
+- [Plugin Development](core/plugin-development.md) - Understanding KPlugin and core concepts
+- [Feature Configuration](core/feature-config.md) - Managing plugin features
+- [Dependency Injection](core/dependency-injection.md) - Working with dependencies
+
+### API Guides
+- [Event System](api/events.md) - Event handling and custom events
+- [Command Framework](api/commands.md) - Creating commands with arguments
+- [Inventory & GUI](api/inventory.md) - Item builders and GUI systems
+- [Extensions](api/extensions.md) - Kotlin extensions for Bukkit classes
+- [Utilities](api/utilities.md) - Helper functions and utilities
+- [Coroutines](api/coroutines.md) - Async operations with Kotlin coroutines
+- [World Management](api/world.md) - World generation and manipulation
+- [Messages & I18n](api/messages.md) - Message formatting and translations
+- [Visual Effects](api/visuals.md) - Particle effects and displays
+- [Game Mechanics](api/game.md) - Countdowns, timers, and game systems
+- [File Management](api/file.md) - Configuration and file handling
+
+### Examples & Recipes
+- [Common Patterns](examples/common-patterns.md) - Frequently used code patterns
+- [Plugin Examples](examples/plugins.md) - Complete plugin examples
+- [Best Practices](examples/best-practices.md) - Recommended practices and conventions
+
+### Reference
+- [API Reference](reference/api.md) - Complete API documentation
+- [Configuration Reference](reference/config.md) - All configuration options
+- [Troubleshooting](reference/troubleshooting.md) - Common issues and solutions
+
+## 🚀 Quick Start
+
+```kotlin
+class MyPlugin : KPlugin() {
+ override fun startup() {
+ // Event handling
+ listen {
+ it.player.sendMessage("Welcome ${it.player.name}!")
+ }
+
+ // Commands
+ command("hello") {
+ description = "Say hello to a player"
+ execute { sender, args ->
+ sender.sendMessage("Hello ${args.getOrNull(0) ?: "World"}!")
+ }
+ }
+
+ // Custom GUIs
+ val gui = simpleGUI("My GUI", 9) {
+ item(0, ItemBuilder(Material.DIAMOND).name("Click me!").build()) {
+ player.sendMessage("You clicked the diamond!")
+ }
+ }
+ }
+}
+```
+
+## 🤝 Contributing
+
+We welcome contributions! Please see our [Contributing Guide](../CONTRIBUTING.md) for details on how to contribute to KPaper.
+
+## 📝 License
+
+KPaper is licensed under the GPL-3.0 License. See [LICENSE](../LICENSE) for details.
\ No newline at end of file
diff --git a/docs/api/commands.md b/docs/api/commands.md
new file mode 100644
index 0000000..3f45d27
--- /dev/null
+++ b/docs/api/commands.md
@@ -0,0 +1,617 @@
+# Command Framework
+
+KPaper's command framework provides a powerful, fluent API for creating commands with automatic argument parsing, validation, and sub-command support.
+
+## Basic Command Creation
+
+### Simple Commands
+
+The easiest way to create commands is using the `CommandBuilder`:
+
+```kotlin
+import cc.modlabs.kpaper.command.CommandBuilder
+
+class MyPlugin : KPlugin() {
+ override fun startup() {
+ // Basic command
+ CommandBuilder("hello")
+ .description("Say hello to the world")
+ .execute { sender, args ->
+ sender.sendMessage("Hello, World!")
+ }
+ .register()
+ }
+}
+```
+
+### Command with Arguments
+
+```kotlin
+// Command with string argument
+CommandBuilder("greet")
+ .description("Greet a specific player")
+ .argument(stringArgument("name"))
+ .execute { sender, args ->
+ val name = args.getString("name")
+ sender.sendMessage("Hello, $name!")
+ }
+ .register()
+
+// Command with player argument
+CommandBuilder("teleport")
+ .description("Teleport to a player")
+ .argument(playerArgument("target"))
+ .playerOnly(true)
+ .execute { sender, args ->
+ val player = sender as Player
+ val target = args.getPlayer("target")
+
+ player.teleport(target.location)
+ player.sendMessage("Teleported to ${target.name}!")
+ }
+ .register()
+```
+
+## Argument Types
+
+### Built-in Argument Types
+
+KPaper provides many built-in argument types with automatic validation:
+
+```kotlin
+import cc.modlabs.kpaper.command.arguments.*
+
+CommandBuilder("admin")
+ .description("Admin command with various arguments")
+
+ // String arguments
+ .argument(stringArgument("message"))
+
+ // Player arguments (with online validation)
+ .argument(playerArgument("target"))
+
+ // Numeric arguments
+ .argument(intArgument("amount", min = 1, max = 100))
+ .argument(doubleArgument("multiplier", min = 0.1, max = 10.0))
+
+ // Boolean arguments
+ .argument(booleanArgument("force", optional = true))
+
+ // World arguments
+ .argument(worldArgument("world"))
+
+ // Material arguments
+ .argument(materialArgument("item"))
+
+ // Location arguments
+ .argument(locationArgument("position"))
+
+ .execute { sender, args ->
+ val message = args.getString("message")
+ val target = args.getPlayer("target")
+ val amount = args.getInt("amount")
+ val multiplier = args.getDouble("multiplier")
+ val force = args.getBoolean("force") ?: false
+ val world = args.getWorld("world")
+ val material = args.getMaterial("item")
+ val location = args.getLocation("position")
+
+ // Use the arguments...
+ }
+ .register()
+```
+
+### Optional Arguments
+
+```kotlin
+CommandBuilder("give")
+ .description("Give items to players")
+ .argument(playerArgument("target", optional = true)) // Defaults to sender
+ .argument(materialArgument("item"))
+ .argument(intArgument("amount", optional = true, default = 1))
+ .execute { sender, args ->
+ val target = args.getPlayerOrSelf("target", sender)
+ val material = args.getMaterial("item")
+ val amount = args.getIntOrDefault("amount", 1)
+
+ val item = ItemStack(material, amount)
+ target.inventory.addItem(item)
+
+ sender.sendMessage("Gave ${amount}x ${material.name} to ${target.name}")
+ }
+ .register()
+```
+
+### Custom Argument Types
+
+Create your own argument types for complex validation:
+
+```kotlin
+// Custom argument for economy amounts
+fun economyAmountArgument(name: String, min: Double = 0.01): ArgumentType {
+ return object : ArgumentType {
+ override val name = name
+ override val optional = false
+
+ override fun parse(input: String): Double? {
+ val amount = input.toDoubleOrNull() ?: return null
+ return if (amount >= min) amount else null
+ }
+
+ override fun complete(input: String): List {
+ return listOf("10", "100", "1000").filter { it.startsWith(input, ignoreCase = true) }
+ }
+
+ override fun getUsage(): String = ""
+ override fun getDescription(): String = "Amount of money (minimum $min)"
+ }
+}
+
+// Usage
+CommandBuilder("pay")
+ .argument(playerArgument("target"))
+ .argument(economyAmountArgument("amount", min = 1.0))
+ .execute { sender, args ->
+ val target = args.getPlayer("target")
+ val amount = args.get("amount")!!
+
+ // Process payment...
+ }
+ .register()
+```
+
+## Sub-Commands
+
+### Basic Sub-Commands
+
+```kotlin
+CommandBuilder("economy")
+ .description("Economy management commands")
+
+ // /economy balance [player]
+ .subcommand("balance")
+ .description("Check player balance")
+ .argument(playerArgument("target", optional = true))
+ .execute { sender, args ->
+ val target = args.getPlayerOrSelf("target", sender)
+ val balance = economyAPI.getBalance(target)
+ sender.sendMessage("${target.name}'s balance: $${balance}")
+ }
+
+ // /economy pay
+ .subcommand("pay")
+ .description("Pay another player")
+ .argument(playerArgument("target"))
+ .argument(economyAmountArgument("amount"))
+ .playerOnly(true)
+ .execute { sender, args ->
+ // Payment logic...
+ }
+
+ .register()
+```
+
+### Nested Sub-Commands
+
+```kotlin
+CommandBuilder("admin")
+ .description("Administration commands")
+ .permission("admin.use")
+
+ // /admin player
+ .subcommand("player")
+ .description("Player management")
+
+ // /admin player ban [reason]
+ .subcommand("ban")
+ .argument(playerArgument("target"))
+ .argument(stringArgument("reason", optional = true))
+ .execute { sender, args ->
+ val target = args.getPlayer("target")
+ val reason = args.getStringOrDefault("reason", "Banned by admin")
+
+ target.ban(reason, sender.name)
+ sender.sendMessage("Banned ${target.name} for: $reason")
+ }
+
+ // /admin player unban
+ .subcommand("unban")
+ .argument(stringArgument("playerName"))
+ .execute { sender, args ->
+ val playerName = args.getString("playerName")
+ val offlinePlayer = server.getOfflinePlayer(playerName)
+
+ offlinePlayer.banList?.pardon(offlinePlayer)
+ sender.sendMessage("Unbanned $playerName")
+ }
+
+ // /admin server
+ .subcommand("server")
+ .description("Server management")
+
+ // /admin server restart [delay]
+ .subcommand("restart")
+ .argument(intArgument("delay", optional = true, default = 10))
+ .execute { sender, args ->
+ val delay = args.getIntOrDefault("delay", 10)
+ scheduleServerRestart(delay)
+ sender.sendMessage("Server restart scheduled in ${delay} seconds")
+ }
+
+ .register()
+```
+
+## Command Features
+
+### Permissions and Requirements
+
+```kotlin
+CommandBuilder("teleport")
+ .description("Teleportation commands")
+ .permission("tp.use") // Base permission
+ .playerOnly(true) // Only players can use
+
+ .subcommand("player")
+ .permission("tp.player") // Additional permission for sub-command
+ .argument(playerArgument("target"))
+ .execute { sender, args ->
+ // Teleport logic...
+ }
+
+ .subcommand("location")
+ .permission("tp.location")
+ .requirement { sender ->
+ sender is Player && sender.hasPermission("tp.admin")
+ } // Custom requirement check
+ .argument(locationArgument("position"))
+ .execute { sender, args ->
+ // Teleport to location...
+ }
+
+ .register()
+```
+
+### Command Aliases
+
+```kotlin
+CommandBuilder("gamemode")
+ .description("Change game mode")
+ .aliases("gm", "mode") // Alternative command names
+ .argument(stringArgument("mode"))
+ .argument(playerArgument("target", optional = true))
+ .execute { sender, args ->
+ val mode = when (args.getString("mode").lowercase()) {
+ "survival", "s", "0" -> GameMode.SURVIVAL
+ "creative", "c", "1" -> GameMode.CREATIVE
+ "adventure", "a", "2" -> GameMode.ADVENTURE
+ "spectator", "sp", "3" -> GameMode.SPECTATOR
+ else -> {
+ sender.sendMessage("Invalid game mode!")
+ return@execute
+ }
+ }
+
+ val target = args.getPlayerOrSelf("target", sender)
+ target.gameMode = mode
+ sender.sendMessage("Set ${target.name}'s game mode to ${mode.name}")
+ }
+ .register()
+```
+
+### Tab Completion
+
+KPaper automatically provides tab completion for built-in argument types:
+
+```kotlin
+// Tab completion is automatic for:
+CommandBuilder("give")
+ .argument(playerArgument("target")) // Completes online player names
+ .argument(materialArgument("item")) // Completes material names
+ .argument(worldArgument("world")) // Completes world names
+ .register()
+
+// Custom completion
+CommandBuilder("team")
+ .argument(object : ArgumentType {
+ override val name = "team"
+ override val optional = false
+
+ override fun parse(input: String): String? {
+ return if (isValidTeam(input)) input else null
+ }
+
+ override fun complete(input: String): List {
+ return getAvailableTeams().filter {
+ it.startsWith(input, ignoreCase = true)
+ }
+ }
+
+ override fun getUsage() = ""
+ override fun getDescription() = "Team name"
+ })
+ .execute { sender, args ->
+ // Team command logic...
+ }
+ .register()
+```
+
+## Advanced Features
+
+### Command Cooldowns
+
+```kotlin
+class CooldownManager {
+ private val cooldowns = mutableMapOf>()
+
+ fun setCooldown(player: Player, command: String, seconds: Int) {
+ val playerCooldowns = cooldowns.computeIfAbsent(player.uniqueId) { mutableMapOf() }
+ playerCooldowns[command] = System.currentTimeMillis() + (seconds * 1000)
+ }
+
+ fun getCooldown(player: Player, command: String): Long {
+ val playerCooldowns = cooldowns[player.uniqueId] ?: return 0
+ val cooldownEnd = playerCooldowns[command] ?: return 0
+ return maxOf(0, cooldownEnd - System.currentTimeMillis())
+ }
+
+ fun hasCooldown(player: Player, command: String): Boolean {
+ return getCooldown(player, command) > 0
+ }
+}
+
+val cooldownManager = CooldownManager()
+
+CommandBuilder("heal")
+ .description("Heal yourself")
+ .playerOnly(true)
+ .requirement { sender ->
+ val player = sender as Player
+ if (cooldownManager.hasCooldown(player, "heal")) {
+ val remaining = cooldownManager.getCooldown(player, "heal") / 1000
+ player.sendMessage("&cHeal is on cooldown for ${remaining} seconds!")
+ false
+ } else {
+ true
+ }
+ }
+ .execute { sender, _ ->
+ val player = sender as Player
+ player.health = player.maxHealth
+ player.sendMessage("&aYou have been healed!")
+
+ // Set 30 second cooldown
+ cooldownManager.setCooldown(player, "heal", 30)
+ }
+ .register()
+```
+
+### Command Usage Tracking
+
+```kotlin
+class CommandStats {
+ private val usage = mutableMapOf()
+
+ fun recordUsage(command: String) {
+ usage[command] = usage.getOrDefault(command, 0) + 1
+ }
+
+ fun getUsage(command: String): Int = usage.getOrDefault(command, 0)
+ fun getAllStats(): Map = usage.toMap()
+}
+
+val stats = CommandStats()
+
+CommandBuilder("stats")
+ .description("View command usage statistics")
+ .permission("admin.stats")
+ .execute { sender, _ ->
+ sender.sendMessage("&6Command Usage Statistics:")
+ stats.getAllStats().entries
+ .sortedByDescending { it.value }
+ .take(10)
+ .forEach { (command, count) ->
+ sender.sendMessage("&e$command: &f$count uses")
+ }
+ }
+ .register()
+
+// Add usage tracking to other commands
+CommandBuilder("teleport")
+ .execute { sender, args ->
+ stats.recordUsage("teleport")
+ // Command logic...
+ }
+ .register()
+```
+
+### Dynamic Command Registration
+
+```kotlin
+class DynamicCommands {
+
+ fun registerWarpCommands(warps: List) {
+ warps.forEach { warp ->
+ CommandBuilder("warp-${warp.name}")
+ .description("Teleport to ${warp.name}")
+ .playerOnly(true)
+ .execute { sender, _ ->
+ val player = sender as Player
+ player.teleport(warp.location)
+ player.sendMessage("Teleported to ${warp.name}!")
+ }
+ .register()
+ }
+ }
+
+ fun registerKitCommands(kits: List) {
+ kits.forEach { kit ->
+ CommandBuilder("kit-${kit.name}")
+ .description("Get the ${kit.name} kit")
+ .permission("kits.${kit.name}")
+ .playerOnly(true)
+ .execute { sender, _ ->
+ val player = sender as Player
+ giveKit(player, kit)
+ }
+ .register()
+ }
+ }
+}
+
+// Register commands dynamically
+val dynamicCommands = DynamicCommands()
+dynamicCommands.registerWarpCommands(loadWarps())
+dynamicCommands.registerKitCommands(loadKits())
+```
+
+## Error Handling
+
+### Validation and Error Messages
+
+```kotlin
+CommandBuilder("economy")
+ .subcommand("transfer")
+ .argument(playerArgument("from"))
+ .argument(playerArgument("to"))
+ .argument(economyAmountArgument("amount"))
+ .execute { sender, args ->
+ val from = args.getPlayer("from")
+ val to = args.getPlayer("to")
+ val amount = args.get("amount")!!
+
+ // Validation
+ if (from == to) {
+ sender.sendMessage("&cCannot transfer money to yourself!")
+ return@execute
+ }
+
+ val fromBalance = economyAPI.getBalance(from)
+ if (fromBalance < amount) {
+ sender.sendMessage("&c${from.name} doesn't have enough money! (Has: $$fromBalance, Needs: $$amount)")
+ return@execute
+ }
+
+ // Execute transfer
+ try {
+ economyAPI.transfer(from, to, amount)
+ sender.sendMessage("&aTransferred $$amount from ${from.name} to ${to.name}")
+
+ // Notify players
+ from.sendMessage("&c-$$amount (transferred to ${to.name})")
+ to.sendMessage("&a+$$amount (from ${from.name})")
+
+ } catch (e: Exception) {
+ sender.sendMessage("&cTransfer failed: ${e.message}")
+ logger.error("Economy transfer failed", e)
+ }
+ }
+ .register()
+```
+
+### Command Exception Handling
+
+```kotlin
+// Global command error handler
+abstract class SafeCommandBuilder(name: String) : CommandBuilder(name) {
+
+ override fun execute(handler: (CommandSender, CommandArguments) -> Unit): CommandBuilder {
+ return super.execute { sender, args ->
+ try {
+ handler(sender, args)
+ } catch (e: PlayerNotFoundException) {
+ sender.sendMessage("&cPlayer not found: ${e.playerName}")
+ } catch (e: InsufficientPermissionException) {
+ sender.sendMessage("&cYou don't have permission to do that!")
+ } catch (e: InvalidArgumentException) {
+ sender.sendMessage("&cInvalid argument: ${e.message}")
+ } catch (e: Exception) {
+ sender.sendMessage("&cAn error occurred while executing the command.")
+ logger.error("Command execution failed", e)
+ }
+ }
+ }
+}
+
+// Usage
+SafeCommandBuilder("risky-command")
+ .execute { sender, args ->
+ // Code that might throw exceptions
+ performRiskyOperation()
+ }
+ .register()
+```
+
+## Best Practices
+
+### 1. Use Descriptive Names and Help
+
+```kotlin
+CommandBuilder("player-management")
+ .description("Comprehensive player management system")
+ .usage("/player-management [options...]")
+ .examples(
+ "/player-management ban PlayerName Griefing",
+ "/player-management mute PlayerName 10m Spamming",
+ "/player-management info PlayerName"
+ )
+ .register()
+```
+
+### 2. Validate Input Early
+
+```kotlin
+CommandBuilder("set-spawn")
+ .playerOnly(true)
+ .requirement { sender ->
+ val player = sender as Player
+ val world = player.world
+
+ if (world.name == "nether" || world.name == "the_end") {
+ player.sendMessage("&cCannot set spawn in ${world.name}!")
+ false
+ } else {
+ true
+ }
+ }
+ .execute { sender, _ ->
+ val player = sender as Player
+ world.spawnLocation = player.location
+ player.sendMessage("&aSpawn location set!")
+ }
+ .register()
+```
+
+### 3. Provide Helpful Feedback
+
+```kotlin
+CommandBuilder("teleport")
+ .argument(playerArgument("target"))
+ .playerOnly(true)
+ .execute { sender, args ->
+ val player = sender as Player
+ val target = args.getPlayer("target")
+
+ // Pre-teleport validation
+ if (target.world != player.world) {
+ player.sendMessage("&e${target.name} is in a different world. Teleporting anyway...")
+ }
+
+ val oldLocation = player.location
+ player.teleport(target.location)
+
+ // Success feedback with context
+ player.sendMessage("&aTeleported to ${target.name} at ${target.location.blockX}, ${target.location.blockY}, ${target.location.blockZ}")
+
+ // Allow quick return
+ player.sendMessage("&7Use /back to return to your previous location")
+ storeLocation(player, oldLocation)
+ }
+ .register()
+```
+
+## Related Topics
+
+- [Events](events.md) - Handling command-related events
+- [Inventory](inventory.md) - Creating command-triggered GUIs
+- [Utilities](utilities.md) - Helper functions for commands
+- [Examples](../examples/common-patterns.md) - Command patterns and examples
\ No newline at end of file
diff --git a/docs/api/events.md b/docs/api/events.md
new file mode 100644
index 0000000..1382579
--- /dev/null
+++ b/docs/api/events.md
@@ -0,0 +1,514 @@
+# Event System
+
+KPaper provides a powerful and intuitive event system that simplifies Bukkit event handling while adding advanced features like custom events and event priorities.
+
+## Basic Event Handling
+
+### Simple Event Listening
+
+The most common way to handle events in KPaper is using the `listen` function:
+
+```kotlin
+import cc.modlabs.kpaper.event.listen
+import org.bukkit.event.player.PlayerJoinEvent
+
+class MyPlugin : KPlugin() {
+ override fun startup() {
+ // Basic event listener
+ listen { event ->
+ val player = event.player
+ player.sendMessage("Welcome ${player.name}!")
+ }
+ }
+}
+```
+
+### Event Priorities
+
+You can specify event priorities to control execution order:
+
+```kotlin
+import org.bukkit.event.EventPriority
+
+// High priority - executes early
+listen(priority = EventPriority.HIGH) { event ->
+ logger.info("${event.player.name} is joining (high priority)")
+}
+
+// Low priority - executes late
+listen(priority = EventPriority.LOW) { event ->
+ logger.info("${event.player.name} joined (low priority)")
+}
+```
+
+### Conditional Event Handling
+
+Only handle events when certain conditions are met:
+
+```kotlin
+// Only handle events for specific players
+listen { event ->
+ if (event.player.hasPermission("vip.join")) {
+ event.player.sendMessage("Welcome VIP player!")
+ // Give VIP welcome items
+ }
+}
+
+// Handle events in specific worlds
+listen { event ->
+ if (event.block.world.name == "mining_world") {
+ // Special mining world logic
+ event.player.giveExp(1)
+ }
+}
+```
+
+## Advanced Event Features
+
+### Event Cancellation
+
+Easily cancel events based on conditions:
+
+```kotlin
+listen { event ->
+ if (event.player.world.name == "spawn") {
+ if (!event.player.hasPermission("spawn.interact")) {
+ event.isCancelled = true
+ event.player.sendMessage("You cannot interact in spawn!")
+ }
+ }
+}
+```
+
+### Multiple Event Handlers
+
+Register multiple handlers for the same event:
+
+```kotlin
+// Handler 1: Logging
+listen { event ->
+ logger.info("Player ${event.player.name} joined from ${event.player.address?.address}")
+}
+
+// Handler 2: Welcome message
+listen { event ->
+ event.player.sendMessage("Welcome to our server!")
+}
+
+// Handler 3: First-time player setup
+listen { event ->
+ if (!event.player.hasPlayedBefore()) {
+ giveStarterKit(event.player)
+ }
+}
+```
+
+## Custom Events
+
+KPaper makes it easy to create and dispatch custom events:
+
+### Creating Custom Events
+
+```kotlin
+import cc.modlabs.kpaper.event.KEvent
+import org.bukkit.entity.Player
+import org.bukkit.event.HandlerList
+
+class PlayerLevelUpEvent(
+ val player: Player,
+ val oldLevel: Int,
+ val newLevel: Int
+) : KEvent() {
+
+ companion object {
+ private val HANDLERS = HandlerList()
+
+ @JvmStatic
+ fun getHandlerList(): HandlerList = HANDLERS
+ }
+
+ override fun getHandlers(): HandlerList = HANDLERS
+}
+```
+
+### Dispatching Custom Events
+
+```kotlin
+import cc.modlabs.kpaper.event.callEvent
+
+fun levelUpPlayer(player: Player, newLevel: Int) {
+ val oldLevel = getPlayerLevel(player)
+
+ // Dispatch the custom event
+ val event = PlayerLevelUpEvent(player, oldLevel, newLevel)
+ event.callEvent()
+
+ // Continue with level up logic if event wasn't cancelled
+ if (!event.isCancelled) {
+ setPlayerLevel(player, newLevel)
+ }
+}
+```
+
+### Listening to Custom Events
+
+```kotlin
+listen { event ->
+ val player = event.player
+ val newLevel = event.newLevel
+
+ player.sendMessage("Congratulations! You reached level $newLevel!")
+
+ // Give rewards based on level
+ when (newLevel) {
+ 10 -> player.inventory.addItem(ItemStack(Material.DIAMOND, 5))
+ 25 -> player.inventory.addItem(ItemStack(Material.NETHERITE_INGOT, 1))
+ 50 -> {
+ player.sendMessage("You've reached level 50! Here's a special reward!")
+ // Give special reward
+ }
+ }
+}
+```
+
+## Event Handler Classes
+
+For more complex event handling, you can create dedicated event handler classes:
+
+### Creating Event Handlers
+
+```kotlin
+import cc.modlabs.kpaper.event.EventHandler
+
+class PlayerEventHandler : EventHandler() {
+
+ override fun load() {
+ // Register events when handler is loaded
+ listen { handlePlayerJoin(it) }
+ listen { handlePlayerQuit(it) }
+ listen { handlePlayerDeath(it) }
+ }
+
+ override fun unload() {
+ // Cleanup when handler is unloaded
+ logger.info("Player event handler unloaded")
+ }
+
+ private fun handlePlayerJoin(event: PlayerJoinEvent) {
+ val player = event.player
+
+ // Update join message
+ event.joinMessage(Component.text("${player.name} has entered the realm!")
+ .color(NamedTextColor.GREEN))
+
+ // First time player setup
+ if (!player.hasPlayedBefore()) {
+ setupNewPlayer(player)
+ }
+
+ // Update player data
+ updatePlayerData(player)
+ }
+
+ private fun handlePlayerQuit(event: PlayerQuitEvent) {
+ val player = event.player
+
+ // Update quit message
+ event.quitMessage(Component.text("${player.name} has left the realm!")
+ .color(NamedTextColor.YELLOW))
+
+ // Save player data
+ savePlayerData(player)
+ }
+
+ private fun handlePlayerDeath(event: PlayerDeathEvent) {
+ val player = event.entity
+ val killer = player.killer
+
+ if (killer != null) {
+ // PvP death
+ event.deathMessage(Component.text("${player.name} was slain by ${killer.name}")
+ .color(NamedTextColor.RED))
+ } else {
+ // Environmental death
+ event.deathMessage(Component.text("${player.name} met an unfortunate end")
+ .color(NamedTextColor.DARK_RED))
+ }
+
+ // Custom death handling
+ handleDeathRewards(player, killer)
+ }
+}
+```
+
+### Registering Event Handlers
+
+```kotlin
+class MyPlugin : KPlugin() {
+ private lateinit var playerHandler: PlayerEventHandler
+
+ override fun startup() {
+ // Register the event handler
+ playerHandler = PlayerEventHandler()
+ playerHandler.load()
+ }
+
+ override fun shutdown() {
+ // Unload the event handler
+ if (::playerHandler.isInitialized) {
+ playerHandler.unload()
+ }
+ }
+}
+```
+
+## Event Utilities
+
+### Event Filtering
+
+Create reusable event filters:
+
+```kotlin
+fun isInWorld(worldName: String): (T) -> Boolean = { event ->
+ when (event) {
+ is PlayerEvent -> event.player.world.name == worldName
+ is BlockEvent -> event.block.world.name == worldName
+ is EntityEvent -> event.entity.world.name == worldName
+ else -> false
+ }
+}
+
+// Usage
+listen(filter = isInWorld("pvp_arena")) { event ->
+ // Only handle interactions in PvP arena
+ handlePvPInteraction(event)
+}
+```
+
+### Delayed Event Handling
+
+Handle events after a delay:
+
+```kotlin
+import cc.modlabs.kpaper.coroutines.taskRunLater
+
+listen { event ->
+ val player = event.player
+
+ // Send welcome message after 3 seconds
+ taskRunLater(60) { // 60 ticks = 3 seconds
+ if (player.isOnline) {
+ player.sendMessage("Hope you're enjoying the server!")
+ }
+ }
+}
+```
+
+### Event Metrics
+
+Track event statistics:
+
+```kotlin
+class EventMetrics {
+ private val eventCounts = mutableMapOf()
+
+ fun trackEvent(eventName: String) {
+ eventCounts[eventName] = eventCounts.getOrDefault(eventName, 0) + 1
+ }
+
+ fun getEventCount(eventName: String): Int = eventCounts.getOrDefault(eventName, 0)
+
+ fun getAllCounts(): Map = eventCounts.toMap()
+}
+
+val metrics = EventMetrics()
+
+listen { event ->
+ metrics.trackEvent("player_join")
+ // Handle join...
+}
+
+listen { event ->
+ metrics.trackEvent("player_quit")
+ // Handle quit...
+}
+```
+
+## Common Event Patterns
+
+### Player Management
+
+```kotlin
+class PlayerManager {
+ private val playerData = mutableMapOf()
+
+ fun initialize() {
+ // Load player on join
+ listen { event ->
+ val player = event.player
+ playerData[player.uniqueId] = loadPlayerData(player.uniqueId)
+ }
+
+ // Save player on quit
+ listen { event ->
+ val player = event.player
+ playerData[player.uniqueId]?.let { data ->
+ savePlayerData(player.uniqueId, data)
+ }
+ playerData.remove(player.uniqueId)
+ }
+
+ // Auto-save periodically
+ listen { event ->
+ if (event.hasChangedBlock()) {
+ updateLastLocation(event.player, event.to)
+ }
+ }
+ }
+}
+```
+
+### World Protection
+
+```kotlin
+fun setupWorldProtection() {
+ val protectedWorlds = setOf("spawn", "hub", "tutorial")
+
+ // Prevent block breaking in protected worlds
+ listen { event ->
+ if (event.block.world.name in protectedWorlds) {
+ if (!event.player.hasPermission("worldprotect.bypass")) {
+ event.isCancelled = true
+ event.player.sendMessage("You cannot break blocks in this world!")
+ }
+ }
+ }
+
+ // Prevent block placing in protected worlds
+ listen { event ->
+ if (event.block.world.name in protectedWorlds) {
+ if (!event.player.hasPermission("worldprotect.bypass")) {
+ event.isCancelled = true
+ event.player.sendMessage("You cannot place blocks in this world!")
+ }
+ }
+ }
+}
+```
+
+### Anti-Grief System
+
+```kotlin
+class AntiGriefSystem {
+ private val recentBreaks = mutableMapOf>()
+
+ fun initialize() {
+ listen { event ->
+ val player = event.player
+ val record = BlockBreakRecord(
+ location = event.block.location,
+ material = event.block.type,
+ timestamp = System.currentTimeMillis()
+ )
+
+ recentBreaks.computeIfAbsent(player.uniqueId) { mutableListOf() }
+ .add(record)
+
+ // Check for suspicious activity
+ if (isSuspiciousActivity(player.uniqueId)) {
+ handleSuspiciousActivity(player, event)
+ }
+
+ // Cleanup old records
+ cleanupOldRecords(player.uniqueId)
+ }
+ }
+
+ private fun isSuspiciousActivity(playerId: UUID): Boolean {
+ val breaks = recentBreaks[playerId] ?: return false
+ val recentBreaks = breaks.filter {
+ System.currentTimeMillis() - it.timestamp < 60000 // Last minute
+ }
+
+ return recentBreaks.size > 50 // More than 50 blocks per minute
+ }
+}
+```
+
+## Best Practices
+
+### 1. Use Specific Event Types
+```kotlin
+// Good - specific event
+listen { event ->
+ if (event.rightClicked is Villager) {
+ // Handle villager interaction
+ }
+}
+
+// Avoid - too general
+listen { event ->
+ // Handles all interactions, less efficient
+}
+```
+
+### 2. Handle Null Cases
+```kotlin
+listen { event ->
+ val damager = event.damager
+ val victim = event.entity
+
+ // Safe casting
+ if (damager is Player && victim is Player) {
+ handlePvPDamage(damager, victim, event.damage)
+ }
+}
+```
+
+### 3. Avoid Heavy Operations
+```kotlin
+listen { event ->
+ // Avoid heavy operations in frequently called events
+ if (event.hasChangedBlock()) { // Only check when player changes blocks
+ checkPlayerRegion(event.player)
+ }
+}
+```
+
+### 4. Use Event Cancellation Wisely
+```kotlin
+listen { event ->
+ if (shouldCancelInteraction(event)) {
+ event.isCancelled = true
+ event.player.sendMessage("Interaction cancelled!")
+ return@listen // Exit early after cancellation
+ }
+
+ // Continue with normal logic
+ handleInteraction(event)
+}
+```
+
+## Troubleshooting
+
+### Events Not Firing
+- Ensure the event class is imported correctly
+- Check that the plugin is properly registered
+- Verify event priority conflicts
+
+### Performance Issues
+- Avoid heavy operations in frequently called events (like PlayerMoveEvent)
+- Use event filtering to reduce unnecessary processing
+- Profile your event handlers to identify bottlenecks
+
+### Memory Leaks
+- Clean up collections in event handlers
+- Remove event listeners when no longer needed
+- Avoid storing references to event objects
+
+## Related Topics
+
+- [Plugin Development](../core/plugin-development.md) - Core KPaper concepts
+- [Extensions](extensions.md) - Kotlin extensions for events
+- [Coroutines](coroutines.md) - Async event handling
+- [Examples](../examples/common-patterns.md) - Common event patterns
\ No newline at end of file
diff --git a/docs/api/inventory.md b/docs/api/inventory.md
new file mode 100644
index 0000000..727c01b
--- /dev/null
+++ b/docs/api/inventory.md
@@ -0,0 +1,598 @@
+# Inventory & GUI System
+
+KPaper provides a comprehensive inventory and GUI system that makes creating interactive menus, item builders, and custom inventories simple and intuitive.
+
+## Item Building
+
+### Basic Item Creation
+
+The `ItemBuilder` class provides a fluent API for creating custom items:
+
+```kotlin
+import cc.modlabs.kpaper.inventory.ItemBuilder
+import org.bukkit.Material
+import org.bukkit.enchantments.Enchantment
+
+// Basic item
+val sword = ItemBuilder(Material.DIAMOND_SWORD)
+ .name("&6Legendary Sword")
+ .lore(
+ "&7A powerful weapon forged",
+ "&7by ancient smiths.",
+ "",
+ "&eDamage: &c+15",
+ "&eSharpness: &aV"
+ )
+ .enchant(Enchantment.SHARPNESS, 5)
+ .enchant(Enchantment.UNBREAKING, 3)
+ .build()
+```
+
+### Advanced Item Features
+
+```kotlin
+// Item with custom model data and flags
+val customItem = ItemBuilder(Material.STICK)
+ .name("&bMagic Wand")
+ .lore("&7Channel your inner wizard!")
+ .customModelData(12345)
+ .hideEnchants()
+ .hideAttributes()
+ .unbreakable(true)
+ .glowing(true) // Adds enchantment glow without enchantment
+ .build()
+
+// Player head with custom texture
+val playerHead = ItemBuilder(Material.PLAYER_HEAD)
+ .name("&a${player.name}'s Head")
+ .skullOwner(player)
+ .build()
+
+// Item with NBT data
+val nbtItem = ItemBuilder(Material.PAPER)
+ .name("&eQuest Item")
+ .nbt("quest_id", "dragon_slayer")
+ .nbt("quest_progress", 0)
+ .build()
+```
+
+### Item Click Handling
+
+Create items with built-in click handlers:
+
+```kotlin
+val interactiveItem = ItemBuilder(Material.COMPASS)
+ .name("&eTeleporter")
+ .lore("&7Click to teleport home!")
+ .onClick { player, event ->
+ player.teleport(player.bedSpawnLocation ?: player.world.spawnLocation)
+ player.sendMessage("&aTeleported home!")
+ }
+ .build()
+```
+
+## Simple GUIs
+
+### Basic GUI Creation
+
+Create simple inventory GUIs with click handlers:
+
+```kotlin
+import cc.modlabs.kpaper.inventory.simple.simpleGUI
+
+fun openShopGUI(player: Player) {
+ val gui = simpleGUI("Shop", 27) {
+
+ // Weapons section
+ item(10, ItemBuilder(Material.DIAMOND_SWORD)
+ .name("&cWeapons")
+ .lore("&7Click to browse weapons")
+ .build()) {
+ openWeaponsShop(player)
+ }
+
+ // Tools section
+ item(12, ItemBuilder(Material.DIAMOND_PICKAXE)
+ .name("&bTools")
+ .lore("&7Click to browse tools")
+ .build()) {
+ openToolsShop(player)
+ }
+
+ // Blocks section
+ item(14, ItemBuilder(Material.GRASS_BLOCK)
+ .name("&aBlocks")
+ .lore("&7Click to browse blocks")
+ .build()) {
+ openBlocksShop(player)
+ }
+
+ // Close button
+ item(22, ItemBuilder(Material.BARRIER)
+ .name("&cClose")
+ .build()) {
+ player.closeInventory()
+ }
+ }
+
+ player.openInventory(gui)
+}
+```
+
+### Paginated GUIs
+
+Create GUIs with multiple pages:
+
+```kotlin
+class PaginatedShopGUI(private val items: List) {
+ private var currentPage = 0
+ private val itemsPerPage = 21
+
+ fun open(player: Player) {
+ val totalPages = (items.size + itemsPerPage - 1) / itemsPerPage
+
+ val gui = simpleGUI("Shop - Page ${currentPage + 1}/$totalPages", 54) {
+
+ // Display items for current page
+ val startIndex = currentPage * itemsPerPage
+ val endIndex = minOf(startIndex + itemsPerPage, items.size)
+
+ for (i in startIndex until endIndex) {
+ val slot = i - startIndex
+ val item = items[i]
+
+ item(slot, ItemBuilder(item.material)
+ .name("&e${item.name}")
+ .lore(
+ "&7Price: &a$${item.price}",
+ "&7Stock: &b${item.stock}",
+ "",
+ "&eClick to purchase!"
+ )
+ .build()) {
+ purchaseItem(player, item)
+ }
+ }
+
+ // Navigation buttons
+ if (currentPage > 0) {
+ item(45, ItemBuilder(Material.ARROW)
+ .name("&7← Previous Page")
+ .build()) {
+ currentPage--
+ open(player) // Reopen with new page
+ }
+ }
+
+ if (currentPage < totalPages - 1) {
+ item(53, ItemBuilder(Material.ARROW)
+ .name("&7Next Page →")
+ .build()) {
+ currentPage++
+ open(player)
+ }
+ }
+
+ // Close button
+ item(49, ItemBuilder(Material.BARRIER)
+ .name("&cClose")
+ .build()) {
+ player.closeInventory()
+ }
+ }
+
+ player.openInventory(gui)
+ }
+}
+```
+
+## Advanced GUI System (KGUI)
+
+### Creating Complex GUIs
+
+For more advanced GUIs, use the KGUI system:
+
+```kotlin
+import cc.modlabs.kpaper.inventory.KGUI
+import cc.modlabs.kpaper.inventory.GUI
+
+class AdvancedShopGUI : KGUI() {
+
+ override val title = "Advanced Shop"
+ override val size = 54
+
+ private var selectedCategory: ShopCategory = ShopCategory.WEAPONS
+ private val playerMoney = mutableMapOf()
+
+ override fun build(player: Player): GUI {
+ return GUI(title, size) {
+
+ // Category selection
+ drawCategorySelector(player, this)
+
+ // Item display based on selected category
+ drawItems(player, this)
+
+ // Player info section
+ drawPlayerInfo(player, this)
+
+ // Action buttons
+ drawActionButtons(player, this)
+ }
+ }
+
+ private fun drawCategorySelector(player: Player, gui: GUI) {
+ ShopCategory.values().forEachIndexed { index, category ->
+ val slot = index + 9
+
+ gui.item(slot, ItemBuilder(category.icon)
+ .name(if (category == selectedCategory) "&a${category.displayName}" else "&7${category.displayName}")
+ .lore(
+ "&7Items: &b${category.items.size}",
+ if (category == selectedCategory) "&a► Selected" else "&eClick to select"
+ )
+ .glowing(category == selectedCategory)
+ .build()) {
+ selectedCategory = category
+ refresh(player)
+ }
+ }
+ }
+
+ private fun drawItems(player: Player, gui: GUI) {
+ val items = selectedCategory.items
+ val startSlot = 18
+
+ items.take(21).forEachIndexed { index, item ->
+ val slot = startSlot + index
+
+ gui.item(slot, ItemBuilder(item.material)
+ .name("&e${item.name}")
+ .lore(
+ "&7Price: &a$${item.price}",
+ "&7Description:",
+ *item.description.map { "&8$it" }.toTypedArray(),
+ "",
+ if (canAfford(player, item)) "&aClick to purchase!" else "&cNot enough money!"
+ )
+ .build()) {
+
+ if (canAfford(player, item)) {
+ purchaseItem(player, item)
+ refresh(player)
+ } else {
+ player.sendMessage("&cYou don't have enough money!")
+ }
+ }
+ }
+ }
+
+ private fun drawPlayerInfo(player: Player, gui: GUI) {
+ val money = playerMoney[player.uniqueId] ?: 0
+
+ gui.item(4, ItemBuilder(Material.PLAYER_HEAD)
+ .name("&a${player.name}")
+ .lore(
+ "&7Money: &a$$money",
+ "&7Purchases: &b${getPurchaseCount(player)}",
+ "&7Member since: &e${getJoinDate(player)}"
+ )
+ .skullOwner(player)
+ .build())
+ }
+
+ private fun drawActionButtons(player: Player, gui: GUI) {
+ // Sell items button
+ gui.item(48, ItemBuilder(Material.EMERALD)
+ .name("&aSell Items")
+ .lore("&7Click to sell items from your inventory")
+ .build()) {
+ openSellGUI(player)
+ }
+
+ // Transaction history
+ gui.item(50, ItemBuilder(Material.BOOK)
+ .name("&eTransaction History")
+ .lore("&7View your recent purchases")
+ .build()) {
+ openTransactionHistory(player)
+ }
+ }
+}
+```
+
+### GUI Animation
+
+Add animations to your GUIs:
+
+```kotlin
+class AnimatedLoadingGUI : KGUI() {
+ override val title = "Loading..."
+ override val size = 27
+
+ private var animationFrame = 0
+ private val loadingSlots = listOf(10, 11, 12, 14, 15, 16)
+
+ override fun build(player: Player): GUI {
+ return GUI(title, size) {
+
+ // Static background
+ fillBorder(ItemBuilder(Material.GRAY_STAINED_GLASS_PANE)
+ .name(" ")
+ .build())
+
+ // Animated loading indicator
+ loadingSlots.forEachIndexed { index, slot ->
+ val isActive = index == (animationFrame % loadingSlots.size)
+
+ item(slot, ItemBuilder(if (isActive) Material.LIME_STAINED_GLASS_PANE else Material.GRAY_STAINED_GLASS_PANE)
+ .name(if (isActive) "&aLoading..." else " ")
+ .build())
+ }
+
+ // Start animation
+ animateLoading(player)
+ }
+ }
+
+ private fun animateLoading(player: Player) {
+ taskRunLater(10) {
+ if (player.openInventory.topInventory.holder == this) {
+ animationFrame++
+ refresh(player)
+
+ // Continue animation or complete loading
+ if (animationFrame < 20) {
+ animateLoading(player)
+ } else {
+ // Loading complete, open actual GUI
+ openMainGUI(player)
+ }
+ }
+ }
+ }
+}
+```
+
+## Anvil GUIs
+
+Create custom anvil interfaces for text input:
+
+```kotlin
+import cc.modlabs.kpaper.inventory.AnvilGUI
+
+fun openNameInputGUI(player: Player) {
+ AnvilGUI.builder()
+ .title("Enter a name:")
+ .itemLeft(ItemBuilder(Material.PAPER)
+ .name("Enter name here...")
+ .build())
+ .onClick { slot, snapshot ->
+ if (slot != AnvilGUI.Slot.OUTPUT) {
+ return@onClick Collections.emptyList()
+ }
+
+ val inputText = snapshot.text
+
+ if (inputText.isBlank()) {
+ return@onClick listOf(AnvilGUI.ResponseAction.replaceInputText("Name cannot be empty!"))
+ }
+
+ if (inputText.length > 16) {
+ return@onClick listOf(AnvilGUI.ResponseAction.replaceInputText("Name too long!"))
+ }
+
+ // Process the input
+ processNameInput(player, inputText)
+
+ return@onClick listOf(AnvilGUI.ResponseAction.close())
+ }
+ .plugin(this)
+ .open(player)
+}
+```
+
+## Inventory Extensions
+
+### Inventory Utilities
+
+```kotlin
+import cc.modlabs.kpaper.extensions.*
+
+// Check if inventory has space
+if (player.inventory.hasSpace()) {
+ player.inventory.addItem(newItem)
+} else {
+ player.sendMessage("Your inventory is full!")
+}
+
+// Remove specific amount of items
+player.inventory.removeItem(Material.DIAMOND, 5)
+
+// Get available space
+val spaces = player.inventory.availableSpace()
+player.sendMessage("You have $spaces free inventory slots")
+
+// Clear specific item types
+player.inventory.clearItems(Material.DIRT, Material.COBBLESTONE)
+```
+
+### Custom Inventory Serialization
+
+Save and load inventory contents:
+
+```kotlin
+import cc.modlabs.kpaper.inventory.InventorySerializer
+
+class PlayerDataManager {
+
+ fun savePlayerInventory(player: Player) {
+ val serialized = InventorySerializer.serialize(player.inventory)
+ // Save to database or file
+ saveToDatabase(player.uniqueId, "inventory", serialized)
+ }
+
+ fun loadPlayerInventory(player: Player) {
+ val serialized = loadFromDatabase(player.uniqueId, "inventory")
+ if (serialized != null) {
+ val inventory = InventorySerializer.deserialize(serialized)
+ player.inventory.contents = inventory.contents
+ }
+ }
+
+ fun createBackup(player: Player): String {
+ return InventorySerializer.serialize(player.inventory)
+ }
+
+ fun restoreFromBackup(player: Player, backup: String) {
+ val inventory = InventorySerializer.deserialize(backup)
+ player.inventory.contents = inventory.contents
+ player.updateInventory()
+ }
+}
+```
+
+## MineSkin Integration
+
+Use custom player head textures:
+
+```kotlin
+import cc.modlabs.kpaper.inventory.mineskin.MineSkinFetcher
+
+fun createCustomHead(textureUrl: String): ItemStack {
+ val texture = MineSkinFetcher.fetchTexture(textureUrl)
+
+ return ItemBuilder(Material.PLAYER_HEAD)
+ .name("&6Custom Head")
+ .texture(texture)
+ .build()
+}
+
+// Pre-defined texture heads
+fun createZombieHead(): ItemStack {
+ return ItemBuilder(Material.PLAYER_HEAD)
+ .name("&2Zombie Head")
+ .texture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTZmYzg1NGJiODRjZjRiNzY5NzI5Nzk3M2UwMmI3OWJjNzBmYzNjODM4MjNmODA4ZjY4ZmZmYjQ2ZjkwNSJ9fX0=")
+ .build()
+}
+```
+
+## GUI Best Practices
+
+### 1. Consistent Layout
+
+```kotlin
+// Create reusable layout functions
+fun GUI.drawBorder(item: ItemStack) {
+ val borderSlots = listOf(0, 1, 2, 6, 7, 8, 9, 17, 18, 26, 27, 35, 36, 44, 45, 46, 47, 51, 52, 53)
+ borderSlots.forEach { slot ->
+ item(slot, item)
+ }
+}
+
+fun GUI.drawNavigationButtons() {
+ // Back button
+ item(48, ItemBuilder(Material.ARROW)
+ .name("&7← Back")
+ .build()) {
+ openPreviousGUI(player)
+ }
+
+ // Close button
+ item(49, ItemBuilder(Material.BARRIER)
+ .name("&cClose")
+ .build()) {
+ player.closeInventory()
+ }
+
+ // Help button
+ item(50, ItemBuilder(Material.BOOK)
+ .name("&eHelp")
+ .lore("&7Click for help with this menu")
+ .build()) {
+ showHelp(player)
+ }
+}
+```
+
+### 2. Error Handling
+
+```kotlin
+fun safeGUIOpen(player: Player, guiFactory: () -> Inventory) {
+ try {
+ val gui = guiFactory()
+ player.openInventory(gui)
+ } catch (exception: Exception) {
+ logger.error("Failed to open GUI for ${player.name}", exception)
+ player.sendMessage("&cFailed to open menu. Please try again.")
+
+ // Fallback to a simple error GUI
+ val errorGUI = simpleGUI("Error", 9) {
+ item(4, ItemBuilder(Material.BARRIER)
+ .name("&cAn error occurred")
+ .lore("&7Please contact an administrator")
+ .build())
+ }
+ player.openInventory(errorGUI)
+ }
+}
+```
+
+### 3. Performance Optimization
+
+```kotlin
+// Cache frequently used items
+object GUIItems {
+ val BORDER_ITEM = ItemBuilder(Material.GRAY_STAINED_GLASS_PANE)
+ .name(" ")
+ .build()
+
+ val CLOSE_BUTTON = ItemBuilder(Material.BARRIER)
+ .name("&cClose")
+ .build()
+
+ val BACK_BUTTON = ItemBuilder(Material.ARROW)
+ .name("&7← Back")
+ .build()
+}
+
+// Reuse GUI instances when possible
+class GUIManager {
+ private val guiCache = mutableMapOf()
+
+ fun getGUI(type: String): KGUI {
+ return guiCache.computeIfAbsent(type) { createGUI(type) }
+ }
+}
+```
+
+## Troubleshooting
+
+### Common Issues
+
+**GUI not opening:**
+- Check if player is online
+- Verify inventory isn't already open
+- Ensure GUI size is valid (multiple of 9, max 54)
+
+**Click events not working:**
+- Confirm event listeners are registered
+- Check for event cancellation in other plugins
+- Verify correct slot indices
+
+**Items not displaying:**
+- Check for null items
+- Verify slot numbers are within bounds
+- Ensure ItemBuilder creates valid items
+
+**Memory leaks:**
+- Clean up GUI references when players disconnect
+- Avoid storing player references in static collections
+- Properly close inventories on plugin disable
+
+## Related Topics
+
+- [Events](events.md) - Handling inventory events
+- [Extensions](extensions.md) - Inventory-related extensions
+- [Utilities](utilities.md) - Helper functions for items
+- [Examples](../examples/common-patterns.md) - GUI examples and patterns
\ No newline at end of file
diff --git a/docs/examples/common-patterns.md b/docs/examples/common-patterns.md
new file mode 100644
index 0000000..304c6ca
--- /dev/null
+++ b/docs/examples/common-patterns.md
@@ -0,0 +1,923 @@
+# Common Patterns
+
+This guide covers frequently used patterns and best practices when developing with KPaper. These patterns will help you write more maintainable, efficient, and robust plugins.
+
+## Plugin Architecture Patterns
+
+### 1. Feature-Based Organization
+
+Organize your plugin into focused feature modules:
+
+```kotlin
+class MyPlugin : KPlugin() {
+
+ // Feature modules
+ private lateinit var playerManager: PlayerManager
+ private lateinit var economySystem: EconomySystem
+ private lateinit var shopSystem: ShopSystem
+ private lateinit var pvpManager: PvPManager
+
+ override val featureConfig = featureConfig {
+ enableEventFeatures = true
+ enableCommandFeatures = true
+ enableInventoryFeatures = true
+ enableCoroutineFeatures = true
+ }
+
+ override fun startup() {
+ // Initialize core systems first
+ playerManager = PlayerManager(this)
+ economySystem = EconomySystem(this, playerManager)
+
+ // Initialize dependent systems
+ shopSystem = ShopSystem(this, economySystem)
+ pvpManager = PvPManager(this, playerManager)
+
+ // Load all features
+ listOf(playerManager, economySystem, shopSystem, pvpManager)
+ .forEach { it.initialize() }
+ }
+
+ override fun shutdown() {
+ // Shutdown in reverse order
+ listOf(pvpManager, shopSystem, economySystem, playerManager)
+ .forEach { it.shutdown() }
+ }
+}
+
+abstract class PluginFeature(protected val plugin: MyPlugin) {
+ abstract fun initialize()
+ abstract fun shutdown()
+}
+
+class PlayerManager(plugin: MyPlugin) : PluginFeature(plugin) {
+ private val playerData = mutableMapOf()
+
+ override fun initialize() {
+ // Register events, commands, etc.
+ listen { handlePlayerJoin(it) }
+ listen { handlePlayerQuit(it) }
+ }
+
+ override fun shutdown() {
+ // Save all player data
+ playerData.values.forEach { savePlayerData(it) }
+ }
+}
+```
+
+### 2. Configuration-Driven Features
+
+Make features configurable and toggleable:
+
+```kotlin
+class ConfigurablePlugin : KPlugin() {
+
+ private lateinit var config: PluginConfig
+ private val features = mutableListOf()
+
+ override fun startup() {
+ config = loadConfiguration()
+
+ // Register features based on configuration
+ if (config.economyEnabled) {
+ features.add(EconomyFeature(this, config.economyConfig))
+ }
+
+ if (config.pvpEnabled) {
+ features.add(PvPFeature(this, config.pvpConfig))
+ }
+
+ if (config.shopEnabled && config.economyEnabled) {
+ features.add(ShopFeature(this, config.shopConfig))
+ }
+
+ // Initialize all enabled features
+ features.forEach { it.initialize() }
+ }
+}
+
+abstract class ConditionalFeature(
+ protected val plugin: ConfigurablePlugin,
+ protected val config: FeatureConfig
+) {
+ abstract fun initialize()
+ abstract fun shutdown()
+ abstract val name: String
+
+ protected fun log(message: String) {
+ plugin.logger.info("[$name] $message")
+ }
+}
+```
+
+## Event Handling Patterns
+
+### 1. Event Manager Pattern
+
+Centralize event handling with dedicated managers:
+
+```kotlin
+class EventManager(private val plugin: MyPlugin) {
+
+ private val handlers = mutableListOf()
+
+ fun initialize() {
+ // Register all event handlers
+ handlers.addAll(listOf(
+ PlayerEventHandler(plugin),
+ BlockEventHandler(plugin),
+ InventoryEventHandler(plugin),
+ CombatEventHandler(plugin)
+ ))
+
+ handlers.forEach { it.load() }
+ }
+
+ fun shutdown() {
+ handlers.forEach { it.unload() }
+ handlers.clear()
+ }
+}
+
+class PlayerEventHandler(private val plugin: MyPlugin) : EventHandler() {
+
+ override fun load() {
+ listen { handleJoin(it) }
+ listen { handleQuit(it) }
+ listen { handleMove(it) }
+ }
+
+ override fun unload() {
+ // Cleanup if needed
+ }
+
+ private fun handleJoin(event: PlayerJoinEvent) {
+ val player = event.player
+
+ // Load player data
+ launch {
+ val data = loadPlayerData(player.uniqueId)
+ withContext(Dispatchers.Main) {
+ applyPlayerData(player, data)
+ }
+ }
+
+ // Send welcome message
+ event.joinMessage(
+ Component.text("${player.name} joined the game!")
+ .color(NamedTextColor.GREEN)
+ )
+
+ // First-time player setup
+ if (!player.hasPlayedBefore()) {
+ setupNewPlayer(player)
+ }
+ }
+}
+```
+
+### 2. Event Priority Chain
+
+Handle events with different priorities for different purposes:
+
+```kotlin
+class LayeredEventSystem {
+
+ fun initialize() {
+ // HIGHEST: Security and anti-cheat (can cancel everything)
+ listen(priority = EventPriority.HIGHEST) { event ->
+ if (isSecurityViolation(event)) {
+ event.isCancelled = true
+ handleSecurityViolation(event.player)
+ return@listen
+ }
+ }
+
+ // HIGH: Region protection
+ listen(priority = EventPriority.HIGH) { event ->
+ val region = getRegion(event.interactionPoint ?: return@listen)
+ if (!region.canInteract(event.player)) {
+ event.isCancelled = true
+ event.player.sendMessage("You cannot interact here!")
+ return@listen
+ }
+ }
+
+ // NORMAL: Feature functionality
+ listen(priority = EventPriority.NORMAL) { event ->
+ handleCustomItems(event)
+ handleInteractables(event)
+ }
+
+ // LOW: Statistics and logging
+ listen(priority = EventPriority.LOW) { event ->
+ if (!event.isCancelled) {
+ recordInteraction(event)
+ updateStatistics(event.player)
+ }
+ }
+ }
+}
+```
+
+## Data Management Patterns
+
+### 1. Repository Pattern
+
+Abstract data access with repository interfaces:
+
+```kotlin
+interface PlayerRepository {
+ suspend fun getPlayer(uuid: UUID): PlayerData?
+ suspend fun savePlayer(data: PlayerData)
+ suspend fun getAllPlayers(): List
+ suspend fun deletePlayer(uuid: UUID)
+}
+
+class DatabasePlayerRepository(
+ private val database: Database
+) : PlayerRepository {
+
+ override suspend fun getPlayer(uuid: UUID): PlayerData? = withContext(Dispatchers.IO) {
+ database.selectFrom("players")
+ .where("uuid", uuid.toString())
+ .executeQuery()
+ ?.let { PlayerData.fromResultSet(it) }
+ }
+
+ override suspend fun savePlayer(data: PlayerData) = withContext(Dispatchers.IO) {
+ database.insertOrUpdate("players", data.toMap())
+ }
+}
+
+class FilePlayerRepository(
+ private val dataFolder: File
+) : PlayerRepository {
+
+ override suspend fun getPlayer(uuid: UUID): PlayerData? = withContext(Dispatchers.IO) {
+ val file = File(dataFolder, "players/$uuid.json")
+ if (file.exists()) {
+ gson.fromJson(file.readText(), PlayerData::class.java)
+ } else null
+ }
+
+ override suspend fun savePlayer(data: PlayerData) = withContext(Dispatchers.IO) {
+ val file = File(dataFolder, "players/${data.uuid}.json")
+ file.parentFile.mkdirs()
+ file.writeText(gson.toJson(data))
+ }
+}
+
+// Usage in plugin
+class PlayerDataManager(
+ private val repository: PlayerRepository
+) {
+ private val cache = mutableMapOf()
+
+ suspend fun getPlayerData(uuid: UUID): PlayerData {
+ return cache[uuid] ?: run {
+ val data = repository.getPlayer(uuid) ?: PlayerData.createDefault(uuid)
+ cache[uuid] = data
+ data
+ }
+ }
+
+ suspend fun savePlayerData(data: PlayerData) {
+ cache[data.uuid] = data
+ repository.savePlayer(data)
+ }
+}
+```
+
+### 2. Caching Strategies
+
+Implement efficient caching for frequently accessed data:
+
+```kotlin
+class CacheManager(
+ private val loader: suspend (K) -> V,
+ private val maxSize: Int = 1000,
+ private val expireAfter: Duration = Duration.ofMinutes(30)
+) {
+ private val cache = mutableMapOf>()
+ private val accessOrder = LinkedHashMap()
+
+ suspend fun get(key: K): V {
+ val now = System.currentTimeMillis()
+ val entry = cache[key]
+
+ // Check if cached and not expired
+ if (entry != null && (now - entry.timestamp) < expireAfter.toMillis()) {
+ accessOrder[key] = now
+ return entry.value
+ }
+
+ // Load new value
+ val value = loader(key)
+ cache[key] = CacheEntry(value, now)
+ accessOrder[key] = now
+
+ // Evict old entries if needed
+ evictIfNeeded()
+
+ return value
+ }
+
+ private fun evictIfNeeded() {
+ while (cache.size > maxSize) {
+ val oldestKey = accessOrder.keys.first()
+ cache.remove(oldestKey)
+ accessOrder.remove(oldestKey)
+ }
+ }
+
+ private data class CacheEntry(val value: V, val timestamp: Long)
+}
+
+// Usage
+class OptimizedPlayerManager {
+ private val playerCache = CacheManager(
+ loader = { uuid -> repository.getPlayer(uuid) ?: PlayerData.createDefault(uuid) },
+ maxSize = 500,
+ expireAfter = Duration.ofMinutes(15)
+ )
+
+ suspend fun getPlayer(uuid: UUID): PlayerData {
+ return playerCache.get(uuid)
+ }
+}
+```
+
+## GUI Patterns
+
+### 1. Menu Navigation System
+
+Create a hierarchical menu system:
+
+```kotlin
+abstract class NavigableGUI(
+ protected val player: Player,
+ protected val parent: NavigableGUI? = null
+) : KGUI() {
+
+ protected fun addBackButton(slot: Int = 45) {
+ if (parent != null) {
+ item(slot, ItemBuilder(Material.ARROW)
+ .name("&7← Back")
+ .lore("&7Return to previous menu")
+ .build()) {
+ parent.open(player)
+ }
+ }
+ }
+
+ protected fun addCloseButton(slot: Int = 49) {
+ item(slot, ItemBuilder(Material.BARRIER)
+ .name("&cClose")
+ .lore("&7Close this menu")
+ .build()) {
+ player.closeInventory()
+ }
+ }
+}
+
+class MainMenuGUI(player: Player) : NavigableGUI(player) {
+ override val title = "Main Menu"
+ override val size = 27
+
+ override fun build(player: Player): GUI {
+ return GUI(title, size) {
+
+ item(11, ItemBuilder(Material.DIAMOND_SWORD)
+ .name("&cPvP Arena")
+ .lore("&7Join the PvP arena!")
+ .build()) {
+ PvPMenuGUI(player, this@MainMenuGUI).open(player)
+ }
+
+ item(13, ItemBuilder(Material.CHEST)
+ .name("&eShop")
+ .lore("&7Buy and sell items!")
+ .build()) {
+ ShopMenuGUI(player, this@MainMenuGUI).open(player)
+ }
+
+ item(15, ItemBuilder(Material.PLAYER_HEAD)
+ .name("&aProfile")
+ .lore("&7View your profile!")
+ .skullOwner(player)
+ .build()) {
+ ProfileGUI(player, this@MainMenuGUI).open(player)
+ }
+
+ addCloseButton()
+ }
+ }
+}
+
+class ShopMenuGUI(
+ player: Player,
+ parent: NavigableGUI
+) : NavigableGUI(player, parent) {
+
+ override val title = "Shop"
+ override val size = 54
+
+ override fun build(player: Player): GUI {
+ return GUI(title, size) {
+ // Shop categories...
+ addBackButton()
+ addCloseButton()
+ }
+ }
+}
+```
+
+### 2. Dynamic Content Loading
+
+Load GUI content asynchronously:
+
+```kotlin
+class AsyncShopGUI(player: Player) : KGUI() {
+ override val title = "Shop (Loading...)"
+ override val size = 54
+
+ private var items: List = emptyList()
+ private var isLoading = true
+
+ override fun build(player: Player): GUI {
+ return GUI(title, size) {
+ if (isLoading) {
+ // Show loading animation
+ showLoadingAnimation()
+
+ // Load items asynchronously
+ launch {
+ items = loadShopItems()
+ isLoading = false
+
+ withContext(Dispatchers.Main) {
+ refresh(player) // Rebuild GUI with loaded content
+ }
+ }
+ } else {
+ // Show actual shop content
+ showShopItems()
+ }
+ }
+ }
+
+ private fun GUI.showLoadingAnimation() {
+ val loadingSlots = listOf(19, 20, 21, 22, 23, 24, 25)
+ val frame = (System.currentTimeMillis() / 200) % loadingSlots.size
+
+ loadingSlots.forEachIndexed { index, slot ->
+ val isActive = index == frame.toInt()
+ item(slot, ItemBuilder(
+ if (isActive) Material.LIME_STAINED_GLASS_PANE
+ else Material.GRAY_STAINED_GLASS_PANE
+ ).name(" ").build())
+ }
+
+ // Continue animation if still loading
+ if (isLoading) {
+ taskRunLater(10) { refresh(player) }
+ }
+ }
+
+ private fun GUI.showShopItems() {
+ items.forEachIndexed { index, item ->
+ item(index + 9, ItemBuilder(item.material)
+ .name("&e${item.name}")
+ .lore("&7Price: &a$${item.price}")
+ .build()) {
+ purchaseItem(player, item)
+ }
+ }
+ }
+}
+```
+
+## Command Patterns
+
+### 1. Command Router Pattern
+
+Route commands to appropriate handlers:
+
+```kotlin
+class CommandRouter(private val plugin: MyPlugin) {
+
+ private val handlers = mutableMapOf()
+
+ fun initialize() {
+ // Register command handlers
+ handlers["economy"] = EconomyCommandHandler(plugin)
+ handlers["admin"] = AdminCommandHandler(plugin)
+ handlers["player"] = PlayerCommandHandler(plugin)
+
+ // Create router command
+ CommandBuilder("game")
+ .description("Game command router")
+ .argument(stringArgument("category"))
+ .argument(stringArgument("action"))
+ .execute { sender, args ->
+ val category = args.getString("category")
+ val action = args.getString("action")
+
+ val handler = handlers[category]
+ if (handler == null) {
+ sender.sendMessage("Unknown category: $category")
+ return@execute
+ }
+
+ handler.handle(sender, action, args)
+ }
+ .register()
+ }
+}
+
+abstract class CommandHandler(protected val plugin: MyPlugin) {
+ abstract fun handle(sender: CommandSender, action: String, args: CommandArguments)
+
+ protected fun requirePlayer(sender: CommandSender): Player? {
+ if (sender !is Player) {
+ sender.sendMessage("This command can only be used by players!")
+ return null
+ }
+ return sender
+ }
+
+ protected fun requirePermission(sender: CommandSender, permission: String): Boolean {
+ if (!sender.hasPermission(permission)) {
+ sender.sendMessage("You don't have permission to do that!")
+ return false
+ }
+ return true
+ }
+}
+
+class EconomyCommandHandler(plugin: MyPlugin) : CommandHandler(plugin) {
+ override fun handle(sender: CommandSender, action: String, args: CommandArguments) {
+ when (action.lowercase()) {
+ "balance" -> handleBalance(sender, args)
+ "pay" -> handlePay(sender, args)
+ "top" -> handleTop(sender, args)
+ else -> sender.sendMessage("Unknown economy action: $action")
+ }
+ }
+
+ private fun handleBalance(sender: CommandSender, args: CommandArguments) {
+ // Balance command logic
+ }
+}
+```
+
+### 2. Command Validation Pipeline
+
+Create reusable validation chains:
+
+```kotlin
+abstract class CommandValidator {
+ abstract fun validate(sender: CommandSender, args: CommandArguments): ValidationResult
+
+ companion object {
+ fun chain(vararg validators: CommandValidator): CommandValidator {
+ return ChainValidator(validators.toList())
+ }
+ }
+}
+
+class ChainValidator(private val validators: List) : CommandValidator() {
+ override fun validate(sender: CommandSender, args: CommandArguments): ValidationResult {
+ validators.forEach { validator ->
+ val result = validator.validate(sender, args)
+ if (!result.isValid) {
+ return result
+ }
+ }
+ return ValidationResult.success()
+ }
+}
+
+class PlayerOnlyValidator : CommandValidator() {
+ override fun validate(sender: CommandSender, args: CommandArguments): ValidationResult {
+ return if (sender is Player) {
+ ValidationResult.success()
+ } else {
+ ValidationResult.failure("This command can only be used by players!")
+ }
+ }
+}
+
+class PermissionValidator(private val permission: String) : CommandValidator() {
+ override fun validate(sender: CommandSender, args: CommandArguments): ValidationResult {
+ return if (sender.hasPermission(permission)) {
+ ValidationResult.success()
+ } else {
+ ValidationResult.failure("You don't have permission to use this command!")
+ }
+ }
+}
+
+class CooldownValidator(
+ private val cooldownManager: CooldownManager,
+ private val commandName: String,
+ private val cooldownSeconds: Int
+) : CommandValidator() {
+
+ override fun validate(sender: CommandSender, args: CommandArguments): ValidationResult {
+ if (sender !is Player) return ValidationResult.success()
+
+ if (cooldownManager.hasCooldown(sender, commandName)) {
+ val remaining = cooldownManager.getCooldown(sender, commandName) / 1000
+ return ValidationResult.failure("Command is on cooldown for ${remaining} seconds!")
+ }
+
+ return ValidationResult.success()
+ }
+}
+
+// Usage
+CommandBuilder("heal")
+ .validator(CommandValidator.chain(
+ PlayerOnlyValidator(),
+ PermissionValidator("heal.use"),
+ CooldownValidator(cooldownManager, "heal", 30)
+ ))
+ .execute { sender, args ->
+ val player = sender as Player
+ player.health = player.maxHealth
+ player.sendMessage("You have been healed!")
+ cooldownManager.setCooldown(player, "heal", 30)
+ }
+ .register()
+```
+
+## Async Patterns
+
+### 1. Task Queue System
+
+Process tasks asynchronously with proper ordering:
+
+```kotlin
+class TaskQueue(
+ private val processor: suspend (T) -> Unit,
+ private val maxConcurrent: Int = 3
+) {
+ private val queue = Channel(Channel.UNLIMITED)
+ private val workers = mutableListOf()
+
+ fun start() {
+ repeat(maxConcurrent) { workerId ->
+ val worker = launch {
+ for (task in queue) {
+ try {
+ processor(task)
+ } catch (e: Exception) {
+ logger.error("Task processing failed in worker $workerId", e)
+ }
+ }
+ }
+ workers.add(worker)
+ }
+ }
+
+ suspend fun submit(task: T) {
+ queue.send(task)
+ }
+
+ fun stop() {
+ queue.close()
+ runBlocking {
+ workers.joinAll()
+ }
+ }
+}
+
+// Usage
+class PlayerDataProcessor(private val plugin: MyPlugin) {
+
+ private val saveQueue = TaskQueue(
+ processor = { task -> savePlayerData(task) },
+ maxConcurrent = 2
+ )
+
+ fun initialize() {
+ saveQueue.start()
+
+ // Auto-save every 5 minutes
+ taskRunTimer(0, 6000) { // 5 minutes in ticks
+ launch {
+ plugin.server.onlinePlayers.forEach { player ->
+ saveQueue.submit(PlayerSaveTask(player.uniqueId, getCurrentPlayerData(player)))
+ }
+ }
+ }
+ }
+
+ private suspend fun savePlayerData(task: PlayerSaveTask) {
+ // Perform actual save operation
+ withContext(Dispatchers.IO) {
+ database.savePlayer(task.uuid, task.data)
+ }
+ }
+}
+```
+
+### 2. Resource Management
+
+Properly manage resources and cleanup:
+
+```kotlin
+class ResourceManager {
+ private val resources = mutableListOf()
+
+ fun register(resource: T): T {
+ resources.add(resource)
+ return resource
+ }
+
+ fun cleanup() {
+ resources.reversed().forEach { resource ->
+ try {
+ resource.close()
+ } catch (e: Exception) {
+ logger.error("Failed to close resource", e)
+ }
+ }
+ resources.clear()
+ }
+}
+
+class DatabaseConnection : AutoCloseable {
+ private val connection = DriverManager.getConnection(url, user, password)
+
+ override fun close() {
+ connection.close()
+ }
+}
+
+class MyPlugin : KPlugin() {
+ private val resourceManager = ResourceManager()
+
+ override fun startup() {
+ // Register resources for automatic cleanup
+ val database = resourceManager.register(DatabaseConnection())
+ val httpClient = resourceManager.register(OkHttpClient())
+ val scheduler = resourceManager.register(ScheduledExecutorService())
+
+ // Use resources...
+ }
+
+ override fun shutdown() {
+ resourceManager.cleanup()
+ }
+}
+```
+
+## Error Handling Patterns
+
+### 1. Result Pattern
+
+Handle operations that can fail gracefully:
+
+```kotlin
+sealed class Result {
+ data class Success(val value: T) : Result()
+ data class Failure(val error: String, val exception: Throwable? = null) : Result()
+
+ fun isSuccess(): Boolean = this is Success
+ fun isFailure(): Boolean = this is Failure
+
+ fun getOrNull(): T? = when (this) {
+ is Success -> value
+ is Failure -> null
+ }
+
+ fun getOrDefault(default: T): T = when (this) {
+ is Success -> value
+ is Failure -> default
+ }
+
+ inline fun onSuccess(action: (T) -> Unit): Result {
+ if (this is Success) action(value)
+ return this
+ }
+
+ inline fun onFailure(action: (String, Throwable?) -> Unit): Result {
+ if (this is Failure) action(error, exception)
+ return this
+ }
+}
+
+class PlayerService {
+
+ suspend fun loadPlayerData(uuid: UUID): Result {
+ return try {
+ val data = withContext(Dispatchers.IO) {
+ database.getPlayerData(uuid)
+ }
+
+ if (data != null) {
+ Result.Success(data)
+ } else {
+ Result.Failure("Player data not found")
+ }
+ } catch (e: Exception) {
+ Result.Failure("Failed to load player data", e)
+ }
+ }
+
+ fun handlePlayerJoin(player: Player) {
+ launch {
+ loadPlayerData(player.uniqueId)
+ .onSuccess { data ->
+ withContext(Dispatchers.Main) {
+ applyPlayerData(player, data)
+ player.sendMessage("Welcome back!")
+ }
+ }
+ .onFailure { error, exception ->
+ logger.error("Failed to load player data for ${player.name}: $error", exception)
+ withContext(Dispatchers.Main) {
+ player.sendMessage("Failed to load your data. Using defaults.")
+ applyDefaultData(player)
+ }
+ }
+ }
+ }
+}
+```
+
+### 2. Circuit Breaker Pattern
+
+Prevent cascading failures:
+
+```kotlin
+class CircuitBreaker(
+ private val failureThreshold: Int = 5,
+ private val recoveryTimeout: Duration = Duration.ofMinutes(1)
+) {
+ private var failures = 0
+ private var lastFailureTime = 0L
+ private var state = State.CLOSED
+
+ enum class State { CLOSED, OPEN, HALF_OPEN }
+
+ suspend fun execute(operation: suspend () -> T): Result {
+ when (state) {
+ State.OPEN -> {
+ if (System.currentTimeMillis() - lastFailureTime >= recoveryTimeout.toMillis()) {
+ state = State.HALF_OPEN
+ } else {
+ return Result.Failure("Circuit breaker is open")
+ }
+ }
+ State.HALF_OPEN -> {
+ // Allow one test request
+ }
+ State.CLOSED -> {
+ // Normal operation
+ }
+ }
+
+ return try {
+ val result = operation()
+ onSuccess()
+ Result.Success(result)
+ } catch (e: Exception) {
+ onFailure()
+ Result.Failure("Operation failed", e)
+ }
+ }
+
+ private fun onSuccess() {
+ failures = 0
+ state = State.CLOSED
+ }
+
+ private fun onFailure() {
+ failures++
+ lastFailureTime = System.currentTimeMillis()
+
+ if (failures >= failureThreshold) {
+ state = State.OPEN
+ }
+ }
+}
+
+// Usage
+class ExternalAPIService {
+ private val circuitBreaker = CircuitBreaker(failureThreshold = 3)
+
+ suspend fun fetchPlayerStats(playerName: String): Result {
+ return circuitBreaker.execute {
+ httpClient.get("$apiUrl/player/$playerName")
+ .body()
+ }
+ }
+}
+```
+
+These patterns provide a solid foundation for building robust, maintainable KPaper plugins. Mix and match them as needed for your specific use case!
\ No newline at end of file
diff --git a/docs/getting-started/first-plugin.md b/docs/getting-started/first-plugin.md
new file mode 100644
index 0000000..7f36dc3
--- /dev/null
+++ b/docs/getting-started/first-plugin.md
@@ -0,0 +1,436 @@
+# Your First KPaper Plugin
+
+This guide walks you through creating your first plugin using KPaper, demonstrating the key features that make development faster and more enjoyable.
+
+## Plugin Structure
+
+We'll create a simple "Welcome" plugin that demonstrates core KPaper features:
+- Player join messages
+- Custom commands
+- GUI interactions
+- Configuration management
+
+## Step 1: Basic Plugin Setup
+
+Create your main plugin class:
+
+```kotlin
+package com.example.welcomeplugin
+
+import cc.modlabs.kpaper.main.KPlugin
+import cc.modlabs.kpaper.main.featureConfig
+
+class WelcomePlugin : KPlugin() {
+
+ // Configure which KPaper features to enable
+ override val featureConfig = featureConfig {
+ enableInventoryFeatures = true
+ enableCommandFeatures = true
+ enableEventFeatures = true
+ }
+
+ override fun startup() {
+ logger.info("Welcome Plugin is starting up!")
+
+ // Setup will go here
+ setupEvents()
+ setupCommands()
+ setupGUI()
+
+ logger.info("Welcome Plugin enabled successfully!")
+ }
+
+ override fun shutdown() {
+ logger.info("Welcome Plugin is shutting down!")
+ }
+}
+```
+
+## Step 2: Event Handling
+
+KPaper makes event handling much simpler with its `listen` function:
+
+```kotlin
+import cc.modlabs.kpaper.event.listen
+import cc.modlabs.kpaper.extensions.sendFormattedMessage
+import org.bukkit.event.player.PlayerJoinEvent
+import org.bukkit.event.player.PlayerQuitEvent
+
+private fun setupEvents() {
+ // Welcome message when players join
+ listen { event ->
+ val player = event.player
+
+ // Send a welcome message
+ player.sendFormattedMessage("&aWelcome to the server, &e${player.name}&a!")
+
+ // Broadcast join message to all players
+ server.onlinePlayers.forEach { onlinePlayer ->
+ if (onlinePlayer != player) {
+ onlinePlayer.sendFormattedMessage("&7${player.name} joined the server!")
+ }
+ }
+
+ // Give welcome items
+ giveWelcomeItems(player)
+ }
+
+ // Farewell message when players leave
+ listen { event ->
+ val player = event.player
+
+ // Broadcast leave message
+ server.onlinePlayers.forEach { onlinePlayer ->
+ onlinePlayer.sendFormattedMessage("&7${player.name} left the server!")
+ }
+ }
+}
+```
+
+## Step 3: Command Creation
+
+Create commands using KPaper's command builder:
+
+```kotlin
+import cc.modlabs.kpaper.command.CommandBuilder
+import cc.modlabs.kpaper.command.arguments.playerArgument
+import cc.modlabs.kpaper.command.arguments.stringArgument
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Player
+
+private fun setupCommands() {
+ // Simple welcome command
+ CommandBuilder("welcome")
+ .description("Get a welcome message")
+ .permission("welcomeplugin.welcome")
+ .playerOnly(true)
+ .execute { sender, _ ->
+ val player = sender as Player
+ player.sendFormattedMessage("&aWelcome to our server, &e${player.name}&a!")
+ player.sendFormattedMessage("&7Use &f/welcome gui &7to open the welcome GUI!")
+ }
+ .register()
+
+ // Welcome GUI command
+ CommandBuilder("welcome")
+ .subcommand("gui")
+ .description("Open the welcome GUI")
+ .permission("welcomeplugin.gui")
+ .playerOnly(true)
+ .execute { sender, _ ->
+ val player = sender as Player
+ openWelcomeGUI(player)
+ }
+ .register()
+
+ // Admin command to welcome specific players
+ CommandBuilder("welcome")
+ .subcommand("player")
+ .description("Send welcome message to a specific player")
+ .permission("welcomeplugin.admin")
+ .argument(playerArgument("target"))
+ .argument(stringArgument("message", optional = true))
+ .execute { sender, args ->
+ val target = args.getPlayer("target")
+ val message = args.getString("message") ?: "Welcome to the server!"
+
+ target.sendFormattedMessage("&a$message")
+ sender.sendMessage("Welcome message sent to ${target.name}!")
+ }
+ .register()
+}
+```
+
+## Step 4: GUI Creation
+
+Create an interactive GUI using KPaper's inventory system:
+
+```kotlin
+import cc.modlabs.kpaper.inventory.ItemBuilder
+import cc.modlabs.kpaper.inventory.simple.simpleGUI
+import org.bukkit.Material
+import org.bukkit.entity.Player
+import org.bukkit.inventory.ItemStack
+
+private fun openWelcomeGUI(player: Player) {
+ val gui = simpleGUI("Welcome Center", 27) {
+
+ // Welcome info item
+ item(4, ItemBuilder(Material.NETHER_STAR)
+ .name("&6Welcome to Our Server!")
+ .lore(
+ "&7",
+ "&aServer Features:",
+ "&8• &7Friendly community",
+ "&8• &7Custom plugins",
+ "&8• &7Regular events",
+ "&7",
+ "&eClick for more info!"
+ )
+ .build()) {
+ player.closeInventory()
+ player.sendFormattedMessage("&aThank you for visiting our server!")
+ player.sendFormattedMessage("&7Join our Discord: &bdiscord.gg/example")
+ }
+
+ // Rules book
+ item(10, ItemBuilder(Material.BOOK)
+ .name("&cServer Rules")
+ .lore(
+ "&7",
+ "&71. Be respectful to all players",
+ "&72. No griefing or stealing",
+ "&73. No spam or advertising",
+ "&74. Have fun!",
+ "&7",
+ "&eClick to acknowledge rules"
+ )
+ .build()) {
+ player.sendFormattedMessage("&aThank you for reading the rules!")
+ player.giveExp(10)
+ }
+
+ // Free starter kit
+ item(16, ItemBuilder(Material.CHEST)
+ .name("&aFree Starter Kit")
+ .lore(
+ "&7",
+ "&aContains:",
+ "&8• &7Basic tools",
+ "&8• &7Some food",
+ "&8• &7Building blocks",
+ "&7",
+ "&eClick to claim! &7(One time only)"
+ )
+ .build()) {
+ if (player.hasPermission("welcomeplugin.kit.claimed")) {
+ player.sendFormattedMessage("&cYou have already claimed your starter kit!")
+ return@item
+ }
+
+ giveStarterKit(player)
+ player.addAttachment(this@WelcomePlugin, "welcomeplugin.kit.claimed", true)
+ player.sendFormattedMessage("&aStarter kit claimed! Check your inventory.")
+ }
+
+ // Close button
+ item(22, ItemBuilder(Material.BARRIER)
+ .name("&cClose")
+ .lore("&7Click to close this menu")
+ .build()) {
+ player.closeInventory()
+ }
+ }
+
+ player.openInventory(gui)
+}
+```
+
+## Step 5: Helper Functions
+
+Add utility functions to complete the plugin:
+
+```kotlin
+import cc.modlabs.kpaper.inventory.ItemBuilder
+import org.bukkit.Material
+import org.bukkit.entity.Player
+
+private fun giveWelcomeItems(player: Player) {
+ // Give a welcome book
+ val welcomeBook = ItemBuilder(Material.BOOK)
+ .name("&6Welcome Guide")
+ .lore(
+ "&7",
+ "&aWelcome to our server!",
+ "&7This book contains important",
+ "&7information for new players.",
+ "&7",
+ "&eKeep this safe!"
+ )
+ .build()
+
+ player.inventory.addItem(welcomeBook)
+ player.sendFormattedMessage("&aYou received a welcome guide!")
+}
+
+private fun giveStarterKit(player: Player) {
+ val items = listOf(
+ ItemBuilder(Material.WOODEN_SWORD).name("&7Starter Sword").build(),
+ ItemBuilder(Material.WOODEN_PICKAXE).name("&7Starter Pickaxe").build(),
+ ItemBuilder(Material.WOODEN_AXE).name("&7Starter Axe").build(),
+ ItemBuilder(Material.BREAD).amount(16).build(),
+ ItemBuilder(Material.OAK_PLANKS).amount(32).build(),
+ ItemBuilder(Material.TORCH).amount(16).build()
+ )
+
+ items.forEach { item ->
+ player.inventory.addItem(item)
+ }
+}
+```
+
+## Step 6: Configuration (Optional)
+
+Add configuration support:
+
+```kotlin
+import cc.modlabs.kpaper.file.configurationFile
+import org.bukkit.configuration.file.FileConfiguration
+
+class WelcomePlugin : KPlugin() {
+ private lateinit var config: FileConfiguration
+
+ override fun startup() {
+ // Load configuration
+ config = configurationFile("config.yml", saveDefault = true)
+
+ // ... rest of startup code
+ }
+
+ private fun getWelcomeMessage(): String {
+ return config.getString("messages.welcome", "&aWelcome to the server!")!!
+ }
+}
+```
+
+Create `resources/config.yml`:
+
+```yaml
+messages:
+ welcome: "&aWelcome to our amazing server, &e%player%&a!"
+ farewell: "&7See you later, &e%player%&7!"
+
+features:
+ starter-kit-enabled: true
+ welcome-gui-enabled: true
+
+kit:
+ items:
+ - "WOODEN_SWORD:1"
+ - "WOODEN_PICKAXE:1"
+ - "BREAD:16"
+```
+
+## Complete Plugin Code
+
+Here's the complete plugin in one file:
+
+```kotlin
+package com.example.welcomeplugin
+
+import cc.modlabs.kpaper.main.KPlugin
+import cc.modlabs.kpaper.main.featureConfig
+import cc.modlabs.kpaper.event.listen
+import cc.modlabs.kpaper.extensions.sendFormattedMessage
+import cc.modlabs.kpaper.command.CommandBuilder
+import cc.modlabs.kpaper.inventory.ItemBuilder
+import cc.modlabs.kpaper.inventory.simple.simpleGUI
+import org.bukkit.Material
+import org.bukkit.entity.Player
+import org.bukkit.event.player.PlayerJoinEvent
+import org.bukkit.event.player.PlayerQuitEvent
+
+class WelcomePlugin : KPlugin() {
+
+ override val featureConfig = featureConfig {
+ enableInventoryFeatures = true
+ enableCommandFeatures = true
+ enableEventFeatures = true
+ }
+
+ override fun startup() {
+ logger.info("Welcome Plugin starting up!")
+
+ setupEvents()
+ setupCommands()
+
+ logger.info("Welcome Plugin enabled!")
+ }
+
+ private fun setupEvents() {
+ listen { event ->
+ val player = event.player
+ player.sendFormattedMessage("&aWelcome to the server, &e${player.name}&a!")
+ giveWelcomeItems(player)
+ }
+
+ listen { event ->
+ server.broadcast(
+ Component.text("${event.player.name} left the server!")
+ .color(NamedTextColor.GRAY)
+ )
+ }
+ }
+
+ private fun setupCommands() {
+ CommandBuilder("welcome")
+ .description("Welcome commands")
+ .permission("welcomeplugin.use")
+ .playerOnly(true)
+ .execute { sender, _ ->
+ openWelcomeGUI(sender as Player)
+ }
+ .register()
+ }
+
+ private fun giveWelcomeItems(player: Player) {
+ val welcomeBook = ItemBuilder(Material.BOOK)
+ .name("&6Welcome Guide")
+ .lore("&7Your guide to the server!")
+ .build()
+
+ player.inventory.addItem(welcomeBook)
+ }
+
+ private fun openWelcomeGUI(player: Player) {
+ val gui = simpleGUI("Welcome Center", 9) {
+ item(4, ItemBuilder(Material.NETHER_STAR)
+ .name("&6Welcome!")
+ .lore("&7Thanks for joining our server!")
+ .build()) {
+ player.sendFormattedMessage("&aEnjoy your stay!")
+ player.closeInventory()
+ }
+ }
+
+ player.openInventory(gui)
+ }
+}
+```
+
+## Testing Your Plugin
+
+1. **Build the plugin:**
+ ```bash
+ ./gradlew build
+ ```
+
+2. **Copy to server:**
+ - Copy the JAR from `build/libs/` to your Paper server's `plugins/` folder
+
+3. **Test features:**
+ - Join the server to see welcome messages
+ - Run `/welcome` to open the GUI
+ - Check console for startup messages
+
+## Next Steps
+
+Now that you've created your first KPaper plugin, explore more advanced features:
+
+- [Event System](../api/events.md) - Learn about custom events and advanced handlers
+- [Command Framework](../api/commands.md) - Create complex commands with validation
+- [Inventory System](../api/inventory.md) - Build sophisticated GUIs and item systems
+- [Plugin Development](../core/plugin-development.md) - Understand KPaper's core concepts
+
+## Common Patterns
+
+This example demonstrates several KPaper patterns you'll use frequently:
+
+- **Event Handling:** Using `listen` for clean event registration
+- **Command Building:** Fluent API for creating commands with arguments
+- **GUI Creation:** Simple inventory GUIs with click handlers
+- **Item Building:** Fluent API for creating custom items
+- **Message Formatting:** Built-in color code support
+- **Feature Configuration:** Enabling specific KPaper features
+
+These patterns form the foundation of most KPaper plugins!
\ No newline at end of file
diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md
new file mode 100644
index 0000000..f63ca69
--- /dev/null
+++ b/docs/getting-started/installation.md
@@ -0,0 +1,189 @@
+# Installation & Setup
+
+This guide will help you set up KPaper in your Paper plugin project.
+
+## Prerequisites
+
+- **Java 21+** - KPaper requires Java 21 or later
+- **Kotlin** - Your project should use Kotlin (though Java interop is supported)
+- **Paper** - KPaper is designed specifically for Paper servers (not Bukkit/Spigot)
+- **Gradle** - We recommend using Gradle as your build system
+
+## Project Setup
+
+### 1. Gradle Configuration
+
+Add the KPaper repository and dependency to your `build.gradle.kts`:
+
+```kotlin
+repositories {
+ mavenCentral()
+ maven("https://nexus.modlabs.cc/repository/maven-mirrors/")
+ maven("https://repo.papermc.io/repository/maven-public/") // Paper repository
+}
+
+dependencies {
+ // Paper development bundle
+ paperweight.paperDevBundle("1.21.6-R0.1-SNAPSHOT") // Use your target version
+
+ // KPaper
+ implementation("cc.modlabs:KPaper:LATEST") // Replace LATEST with specific version
+}
+```
+
+### 2. Plugin Configuration
+
+Your plugin needs to use KPaper's base class instead of the standard JavaPlugin. Here's your main plugin class:
+
+```kotlin
+import cc.modlabs.kpaper.main.KPlugin
+
+class YourPlugin : KPlugin() {
+
+ override fun startup() {
+ // Your plugin initialization code goes here
+ logger.info("Plugin enabled with KPaper!")
+ }
+
+ override fun shutdown() {
+ // Cleanup code when plugin is disabled
+ logger.info("Plugin disabled.")
+ }
+
+ override fun load() {
+ // Called during plugin loading phase (before enable)
+ // Use this for early initialization
+ }
+}
+```
+
+### 3. Plugin.yml Configuration
+
+Create or update your `plugin.yml` file:
+
+```yaml
+name: YourPlugin
+version: '1.0.0'
+main: com.yourpackage.YourPlugin
+api-version: '1.21'
+authors: [YourName]
+description: A plugin using KPaper
+website: https://yourwebsite.com
+
+# Optional: Specify dependencies
+depend:
+ - SomeRequiredPlugin
+softdepend:
+ - SomeOptionalPlugin
+```
+
+## Build System Setup
+
+### Gradle Build Configuration
+
+Here's a complete `build.gradle.kts` example:
+
+```kotlin
+plugins {
+ kotlin("jvm") version "2.0.21"
+ id("io.papermc.paperweight.userdev") version "1.7.4"
+ id("xyz.jpenilla.run-paper") version "2.3.1" // Optional: for testing
+}
+
+group = "com.yourpackage"
+version = "1.0.0"
+
+repositories {
+ mavenCentral()
+ maven("https://nexus.modlabs.cc/repository/maven-mirrors/")
+}
+
+dependencies {
+ paperweight.paperDevBundle("1.21.6-R0.1-SNAPSHOT")
+ implementation("cc.modlabs:KPaper:LATEST")
+}
+
+tasks {
+ compileKotlin {
+ kotlinOptions.jvmTarget = "21"
+ }
+}
+
+kotlin {
+ jvmToolchain(21)
+}
+```
+
+## Verification
+
+### Test Your Setup
+
+Create a simple test to verify KPaper is working:
+
+```kotlin
+import cc.modlabs.kpaper.main.KPlugin
+import cc.modlabs.kpaper.event.listen
+import org.bukkit.event.player.PlayerJoinEvent
+
+class TestPlugin : KPlugin() {
+ override fun startup() {
+ // Test event listening
+ listen { event ->
+ event.player.sendMessage("KPaper is working!")
+ }
+
+ logger.info("KPaper test plugin loaded successfully!")
+ }
+}
+```
+
+### Build and Test
+
+1. **Build your plugin:**
+ ```bash
+ ./gradlew build
+ ```
+
+2. **Run with Paper:** (if using run-paper plugin)
+ ```bash
+ ./gradlew runServer
+ ```
+
+3. **Manual Testing:**
+ - Copy the generated JAR from `build/libs/` to your Paper server's `plugins/` folder
+ - Start your server and verify the plugin loads without errors
+
+## Common Issues
+
+### "Cannot resolve KPaper dependency"
+- Ensure you've added the correct repository URL
+- Check that you're using a valid version number
+- Verify your internet connection can reach the repository
+
+### "Unsupported Java version"
+- KPaper requires Java 21+
+- Update your JDK and ensure Gradle is using the correct version
+
+### "Paper classes not found"
+- Make sure you're using the Paper development bundle
+- Verify your Paper version matches the bundle version
+
+### ClassNotFoundException at runtime
+- Ensure KPaper is properly shaded into your plugin JAR
+- Check that all dependencies are included in your build
+
+## Next Steps
+
+Once you have KPaper set up, check out:
+- [Your First Plugin](first-plugin.md) - Create a simple plugin
+- [Plugin Development](../core/plugin-development.md) - Learn KPaper fundamentals
+- [API Guides](../api/) - Explore KPaper's features
+
+## Version Compatibility
+
+| KPaper Version | Paper Version | Java Version |
+|---------------|---------------|--------------|
+| 2025.x | 1.21.6+ | 21+ |
+| 2024.x | 1.20.4+ | 21+ |
+
+Always check the [releases page](https://github.com/ModLabsCC/KPaper/releases) for the latest version and compatibility information.
\ No newline at end of file
From 779c513e7a9a2b1570351edeff9a4483d615c247 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 29 Jul 2025 11:04:30 +0000
Subject: [PATCH 3/4] Complete comprehensive documentation with utilities,
migration, troubleshooting, and core guides
Co-authored-by: CoasterFreakDE <28011628+CoasterFreakDE@users.noreply.github.com>
---
docs/api/utilities.md | 655 +++++++++++++++++++++++++++
docs/core/plugin-development.md | 691 ++++++++++++++++++++++++++++
docs/getting-started/migration.md | 718 ++++++++++++++++++++++++++++++
docs/reference/troubleshooting.md | 691 ++++++++++++++++++++++++++++
4 files changed, 2755 insertions(+)
create mode 100644 docs/api/utilities.md
create mode 100644 docs/core/plugin-development.md
create mode 100644 docs/getting-started/migration.md
create mode 100644 docs/reference/troubleshooting.md
diff --git a/docs/api/utilities.md b/docs/api/utilities.md
new file mode 100644
index 0000000..9f76de2
--- /dev/null
+++ b/docs/api/utilities.md
@@ -0,0 +1,655 @@
+# Utilities & Extensions
+
+KPaper provides extensive utility functions and Kotlin extensions that make common Minecraft development tasks more intuitive and efficient.
+
+## Utility Functions
+
+### Console and Logging
+
+KPaper enhances console output and logging with color support and structured formatting:
+
+```kotlin
+import cc.modlabs.kpaper.util.*
+
+class MyPlugin : KPlugin() {
+ override fun startup() {
+ // Colored console output
+ consoleOutput("&aPlugin started successfully!")
+ consoleOutput("&eLoading configuration...")
+ consoleOutput("&cWarning: Development mode enabled!")
+
+ // Structured logging
+ logInfo("Player management system initialized")
+ logWarning("Configuration file is missing some values")
+ logError("Failed to connect to database", exception)
+
+ // Debug logging (only shown when debug is enabled)
+ logDebug("Player data: $playerData")
+
+ // Success/failure logging
+ logSuccess("Successfully connected to database")
+ logFailure("Failed to load player data")
+ }
+}
+```
+
+### Text Processing
+
+Powerful text manipulation utilities:
+
+```kotlin
+import cc.modlabs.kpaper.util.*
+
+// Color code processing
+val coloredText = "&aHello &bWorld&r!".translateColorCodes()
+val cleanText = "§aHello §bWorld§r!".stripColorCodes()
+
+// Component building
+val component = textComponent {
+ text("Welcome ") {
+ color = NamedTextColor.GREEN
+ }
+ text(player.name) {
+ color = NamedTextColor.YELLOW
+ bold = true
+ }
+ text("!") {
+ color = NamedTextColor.GREEN
+ }
+}
+
+// Text formatting
+val centeredText = "Welcome to the Server".center(50, '=')
+// Output: ===========Welcome to the Server===========
+
+val truncatedText = "This is a very long message".truncate(15)
+// Output: "This is a ve..."
+
+// List formatting
+val items = listOf("apple", "banana", "cherry")
+val formatted = items.formatList()
+// Output: "apple, banana, and cherry"
+
+// Time formatting
+val duration = Duration.ofMinutes(75)
+val formatted = duration.formatDuration()
+// Output: "1 hour, 15 minutes"
+```
+
+### Random Utilities
+
+Enhanced random number generation and selection:
+
+```kotlin
+import cc.modlabs.kpaper.util.*
+
+// Random numbers with ranges
+val randomInt = randomInt(1, 100) // 1-100 inclusive
+val randomDouble = randomDouble(0.5, 1.5) // 0.5-1.5
+val randomFloat = randomFloat(0.0f, 10.0f)
+
+// Boolean with probability
+val shouldTrigger = randomBoolean(0.25) // 25% chance of true
+
+// Collection utilities
+val items = listOf("common", "rare", "legendary")
+val randomItem = items.randomElement()
+val randomItems = items.randomElements(2) // Get 2 random items
+
+// Weighted selection
+val weightedItems = mapOf(
+ "common" to 70, // 70% chance
+ "rare" to 25, // 25% chance
+ "legendary" to 5 // 5% chance
+)
+val selectedItem = weightedItems.randomWeighted()
+
+// Shuffling
+val shuffled = items.shuffled()
+val mutableList = items.toMutableList()
+mutableList.shuffle()
+```
+
+### Identity and UUID Utilities
+
+Work with player identities more easily:
+
+```kotlin
+import cc.modlabs.kpaper.util.*
+
+// Player lookup utilities
+val player = findPlayerByName("Steve") // Case-insensitive
+val players = findPlayersStartingWith("St") // Returns list
+val exactPlayer = getPlayerExact("Steve") // Exact match only
+
+// UUID utilities
+val uuid = parseUUID("550e8400-e29b-41d4-a716-446655440000")
+val shortUUID = uuid.toShortString() // "550e8400"
+val isValidUUID = "invalid-uuid".isValidUUID() // false
+
+// Offline player utilities
+val offlinePlayer = getOfflinePlayerSafe("PlayerName")
+val hasPlayed = offlinePlayer?.hasPlayedBefore() ?: false
+```
+
+## Bukkit Extensions
+
+### Player Extensions
+
+KPaper adds many convenient extensions to the Player class:
+
+```kotlin
+import cc.modlabs.kpaper.extensions.*
+
+fun handlePlayer(player: Player) {
+ // Location utilities
+ player.teleportSafely(location) // Finds safe spot if needed
+ player.teleportToSpawn()
+ player.teleportToWorld("world_nether")
+
+ // Message sending with formatting
+ player.sendFormattedMessage("&aWelcome &e${player.name}&a!")
+ player.sendCenteredMessage("=== WELCOME ===")
+ player.sendActionBar("&bHealth: ${player.health}/${player.maxHealth}")
+ player.sendTitle("&6Welcome!", "&7Enjoy your stay", 10, 70, 20)
+
+ // Inventory utilities
+ if (player.hasSpace()) {
+ player.giveItem(ItemStack(Material.DIAMOND))
+ }
+
+ player.clearInventory()
+ player.removeItem(Material.DIRT, 32) // Remove 32 dirt
+ player.hasItem(Material.GOLD_INGOT, 5) // Check if has 5 gold
+
+ // Experience utilities
+ player.giveExp(100)
+ player.setLevel(50)
+ player.resetExp()
+
+ // Effect utilities
+ player.addPotionEffect(PotionEffectType.SPEED, 2, 200) // Speed II for 10 seconds
+ player.removePotionEffect(PotionEffectType.POISON)
+ player.clearPotionEffects()
+
+ // Health and food utilities
+ player.heal() // Full heal
+ player.heal(5.0) // Heal 5 hearts
+ player.feed() // Full hunger
+ player.kill() // Kill player
+
+ // Permission utilities
+ player.hasAnyPermission("admin.use", "mod.use") // Check multiple permissions
+ player.addTempPermission("fly.temp", Duration.ofMinutes(5))
+
+ // Sound utilities
+ player.playSound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP)
+ player.playSound(Sound.BLOCK_NOTE_BLOCK_BELL, 1.0f, 1.5f) // Volume and pitch
+
+ // World utilities
+ val isInOverworld = player.isInWorld("world")
+ val isInNether = player.isInNether()
+ val isInEnd = player.isInEnd()
+}
+```
+
+### Location Extensions
+
+Enhanced location manipulation:
+
+```kotlin
+import cc.modlabs.kpaper.extensions.*
+
+fun handleLocation(location: Location) {
+ // Distance utilities
+ val distance = location.distanceTo(otherLocation)
+ val distance2D = location.distance2D(otherLocation) // Ignore Y axis
+ val isNear = location.isNear(otherLocation, 5.0) // Within 5 blocks
+
+ // Direction utilities
+ val direction = location.directionTo(otherLocation)
+ val facing = location.getCardinalDirection() // N, S, E, W, NE, etc.
+
+ // Block utilities
+ val block = location.getBlockSafely() // Null if chunk not loaded
+ val topBlock = location.getHighestBlock()
+ val safeLocation = location.findSafeSpot() // No lava, void, etc.
+
+ // Relative positioning
+ val above = location.above(3) // 3 blocks up
+ val below = location.below(2) // 2 blocks down
+ val forward = location.forward(5) // 5 blocks forward (based on yaw)
+ val right = location.right(2) // 2 blocks to the right
+
+ // Area utilities
+ val nearbyPlayers = location.getNearbyPlayers(10.0)
+ val nearbyEntities = location.getNearbyEntities(5.0)
+ val nearbyBlocks = location.getNearbyBlocks(3)
+
+ // Chunk utilities
+ val chunk = location.chunk
+ val isChunkLoaded = location.isChunkLoaded()
+ val chunkCoords = location.getChunkCoordinates()
+
+ // Serialization
+ val serialized = location.serialize()
+ val deserialized = Location.deserialize(serialized)
+ val string = location.toSimpleString() // "world:100,64,200"
+}
+```
+
+### ItemStack Extensions
+
+More intuitive item manipulation:
+
+```kotlin
+import cc.modlabs.kpaper.extensions.*
+
+fun handleItem(item: ItemStack) {
+ // Display name utilities
+ item.setDisplayName("&6Golden Sword")
+ val name = item.getDisplayName() // Returns null if no custom name
+ val nameOrType = item.getDisplayNameOrType() // Falls back to material name
+
+ // Lore management
+ item.setLore("&7A powerful weapon", "&7Forged by ancient smiths")
+ item.addLore("&eBonus: +5 Damage")
+ item.clearLore()
+ val lore = item.getLore() // Returns list of strings
+
+ // Enchantment utilities
+ item.addEnchant(Enchantment.SHARPNESS, 5)
+ item.removeEnchant(Enchantment.UNBREAKING)
+ val hasSharpness = item.hasEnchant(Enchantment.SHARPNESS)
+ val sharpnessLevel = item.getEnchantLevel(Enchantment.SHARPNESS)
+
+ // Durability
+ item.setDurability(50)
+ item.repair() // Full repair
+ item.damage(10) // Damage by 10 points
+ val isNearlyBroken = item.getDurabilityPercent() < 0.1
+
+ // Comparison utilities
+ val isSimilar = item.isSimilarTo(otherItem) // Same type and meta
+ val isExact = item.isExactlyEqual(otherItem) // Including amount
+
+ // NBT utilities (if supported)
+ item.setNBT("custom_id", "legendary_sword")
+ val customId = item.getNBT("custom_id")
+ val hasNBT = item.hasNBT("custom_id")
+
+ // Cloning and modification
+ val copy = item.cloneWithAmount(64)
+ val renamed = item.cloneWithName("&bIce Sword")
+ val enchanted = item.cloneWithEnchant(Enchantment.FIRE_ASPECT, 2)
+}
+```
+
+### Inventory Extensions
+
+Simplified inventory management:
+
+```kotlin
+import cc.modlabs.kpaper.extensions.*
+
+fun handleInventory(inventory: Inventory) {
+ // Space checking
+ val hasSpace = inventory.hasSpace()
+ val spaceFor = inventory.getSpaceFor(ItemStack(Material.STONE))
+ val availableSlots = inventory.getAvailableSlots()
+
+ // Item management
+ inventory.addItemSafely(item) // Returns items that couldn't fit
+ inventory.removeItem(Material.DIRT, 32)
+ inventory.removeAllItems(Material.COBBLESTONE)
+ val count = inventory.countItems(Material.DIAMOND)
+ val hasItems = inventory.hasItems(Material.GOLD_INGOT, 5)
+
+ // Bulk operations
+ inventory.clearItems(Material.DIRT, Material.COBBLESTONE, Material.GRAVEL)
+ inventory.replaceItems(Material.DIRT, Material.GRASS_BLOCK)
+
+ // Organization
+ inventory.sort() // Sort by material and amount
+ inventory.compress() // Combine similar stacks
+ inventory.shuffle() // Randomize positions
+
+ // Filling and patterns
+ inventory.fillEmpty(ItemStack(Material.GRAY_STAINED_GLASS_PANE))
+ inventory.fillBorder(ItemStack(Material.BLACK_STAINED_GLASS_PANE))
+ inventory.fillSlots(listOf(0, 8, 45, 53), borderItem)
+
+ // Searching
+ val diamondSlots = inventory.findItems(Material.DIAMOND)
+ val firstEmpty = inventory.firstEmpty()
+ val lastEmpty = inventory.lastEmpty()
+
+ // Serialization
+ val contents = inventory.serializeContents()
+ inventory.deserializeContents(contents)
+}
+```
+
+### Block Extensions
+
+Enhanced block manipulation:
+
+```kotlin
+import cc.modlabs.kpaper.extensions.*
+
+fun handleBlock(block: Block) {
+ // Safe state management
+ val state = block.getStateSafely() // Returns null if not a chest
+ block.setType(Material.DIAMOND_BLOCK, updatePhysics = false)
+
+ // Placement utilities
+ block.placeAgainst(otherBlock) // Place against the side of another block
+ val canPlace = block.canPlaceAgainst(Material.STONE)
+
+ // Breaking simulation
+ val drops = block.getDrops(tool) // Get what items would drop
+ val breakTime = block.getBreakTime(player) // How long to break
+ val canBreak = block.canBreak(player)
+
+ // Direction utilities
+ val face = block.getFace(otherBlock) // Which face is touching
+ val adjacent = block.getAdjacent() // All 6 adjacent blocks
+ val adjacentSame = block.getAdjacentSameType() // Adjacent blocks of same type
+
+ // Relative positioning
+ val above = block.above()
+ val below = block.below()
+ val north = block.north()
+ val south = block.south()
+ val east = block.east()
+ val west = block.west()
+
+ // Area utilities
+ val nearbyBlocks = block.getNearby(3) // All blocks within 3 blocks
+ val sameTypeNearby = block.getNearbySameType(5)
+
+ // Light utilities
+ val lightLevel = block.getLightLevel()
+ val isLit = block.isLit() // Light level > 0
+ val canSeeSky = block.canSeeSky()
+
+ // Physics and updates
+ block.updatePhysics()
+ block.scheduleUpdate()
+ val isSupported = block.isSupported() // Has support below
+}
+```
+
+### World Extensions
+
+World management utilities:
+
+```kotlin
+import cc.modlabs.kpaper.extensions.*
+
+fun handleWorld(world: World) {
+ // Player management
+ val onlinePlayers = world.getOnlinePlayers()
+ val playerCount = world.getPlayerCount()
+ world.broadcastMessage("&aServer announcement!")
+ world.broadcastToPermission("admin.notify", "&cAdmin alert!")
+
+ // Time utilities
+ world.setTimeOfDay(TimeOfDay.DAWN)
+ world.setTimeOfDay(TimeOfDay.NOON)
+ world.setTimeOfDay(TimeOfDay.DUSK)
+ world.setTimeOfDay(TimeOfDay.MIDNIGHT)
+ val timeOfDay = world.getTimeOfDay()
+
+ // Weather utilities
+ world.clearWeather()
+ world.setStorm(duration = Duration.ofMinutes(5))
+ world.setThunder(duration = Duration.ofMinutes(3))
+ val isStormy = world.isStormy()
+
+ // Location utilities
+ val randomLocation = world.getRandomLocation(minY = 60, maxY = 120)
+ val safeSpawn = world.findSafeSpawn(around = world.spawnLocation)
+ val highestBlock = world.getHighestBlockAt(x, z)
+
+ // Chunk utilities
+ val loadedChunks = world.getLoadedChunks()
+ world.loadChunk(x, z, generate = true)
+ world.unloadChunk(x, z, save = true)
+ val isChunkLoaded = world.isChunkLoaded(x, z)
+
+ // Entity management
+ val entities = world.getEntitiesByType()
+ val nearbyEntities = world.getNearbyEntities(location, radius = 10.0)
+ world.removeEntitiesByType()
+
+ // Game rules
+ world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false)
+ world.setGameRule(GameRule.KEEP_INVENTORY, true)
+ val keepInventory = world.getGameRuleValue(GameRule.KEEP_INVENTORY)
+
+ // Border utilities
+ val border = world.worldBorder
+ border.setSize(1000.0)
+ border.setCenter(0.0, 0.0)
+ val isInBorder = location.isInWorldBorder()
+}
+```
+
+## Advanced Extensions
+
+### Collection Extensions
+
+Enhanced collection operations for Minecraft contexts:
+
+```kotlin
+import cc.modlabs.kpaper.extensions.*
+
+// Player collections
+val players = server.onlinePlayers
+val admins = players.withPermission("admin.use")
+val survivorPlayers = players.inWorld("world").inGameMode(GameMode.SURVIVAL)
+val nearbyPlayers = players.near(location, 10.0)
+
+// Entity filtering
+val entities = world.entities
+val hostileMobs = entities.ofType()
+val namedEntities = entities.withCustomName()
+val youngAnimals = entities.ofType().filter { !it.isAdult }
+
+// Block collections
+val blocks = location.getNearbyBlocks(5)
+val ores = blocks.ofType(Material.DIAMOND_ORE, Material.GOLD_ORE, Material.IRON_ORE)
+val breakable = blocks.filter { it.canBreak(player) }
+
+// Inventory operations
+val inventories = listOf(player.inventory, enderChest, someChest)
+val totalDiamonds = inventories.countItems(Material.DIAMOND)
+val hasSpace = inventories.any { it.hasSpace() }
+```
+
+### Event Extensions
+
+Simplified event handling patterns:
+
+```kotlin
+import cc.modlabs.kpaper.extensions.*
+
+// Event result shortcuts
+listen { event ->
+ // Quick cancellation with message
+ event.cancelWithMessage("&cYou cannot interact here!")
+
+ // Conditional cancellation
+ event.cancelIf(!player.hasPermission("interact.use")) {
+ "&cNo permission!"
+ }
+
+ // Cancel and execute
+ event.cancelAndExecute {
+ player.sendMessage("Interaction blocked!")
+ player.playSound(Sound.BLOCK_NOTE_BLOCK_BASS)
+ }
+}
+
+// Damage event utilities
+listen { event ->
+ val entity = event.entity
+
+ // Damage modification
+ event.doubleDamage()
+ event.halveDamage()
+ event.reduceDamage(2.0)
+ event.minimumDamage(1.0)
+
+ // Quick checks
+ if (event.isFallDamage()) {
+ event.damage = 0.0
+ }
+
+ if (event.isPlayerDamage()) {
+ val player = entity as Player
+ // Handle player damage
+ }
+}
+```
+
+### Component and Text Extensions
+
+Modern text component handling:
+
+```kotlin
+import cc.modlabs.kpaper.extensions.*
+
+// Component building
+val component = component {
+ text("Welcome ") {
+ color = NamedTextColor.GREEN
+ bold = true
+ }
+ text(player.name) {
+ color = NamedTextColor.YELLOW
+ clickEvent = ClickEvent.suggestCommand("/msg ${player.name} ")
+ hoverEvent = HoverEvent.showText(Component.text("Click to message"))
+ }
+ text("!") {
+ color = NamedTextColor.GREEN
+ }
+}
+
+// Legacy color code conversion
+val modernComponent = "&aHello &bWorld!".toComponent()
+val legacyString = component.toLegacyString()
+
+// Component utilities
+val plainText = component.getPlainText()
+val isEmpty = component.isEmpty()
+val length = component.length()
+
+// Component combining
+val combined = component1 + component2
+val withNewline = component.appendNewline()
+val withPrefix = "[Server] ".toComponent() + component
+```
+
+## Best Practices
+
+### 1. Use Extensions Consistently
+
+```kotlin
+// Good - using extensions for clarity
+fun teleportPlayerSafely(player: Player, location: Location) {
+ val safeLocation = location.findSafeSpot()
+ if (safeLocation != null) {
+ player.teleportSafely(safeLocation)
+ player.sendFormattedMessage("&aTeleported safely!")
+ } else {
+ player.sendFormattedMessage("&cNo safe location found!")
+ }
+}
+
+// Avoid - verbose vanilla API calls
+fun teleportPlayerSafelyOld(player: Player, location: Location) {
+ // Lots of manual safety checks and API calls...
+}
+```
+
+### 2. Leverage Type-Safe Extensions
+
+```kotlin
+// Good - type-safe with null handling
+val chest = block.getStateSafely()
+chest?.inventory?.addItem(item)
+
+// Avoid - unsafe casting
+val chest = block.state as? Chest
+if (chest != null) {
+ chest.inventory.addItem(item)
+ chest.update()
+}
+```
+
+### 3. Chain Operations
+
+```kotlin
+// Good - method chaining for readability
+server.onlinePlayers
+ .withPermission("vip.access")
+ .inWorld("lobby")
+ .forEach { player ->
+ player.sendFormattedMessage("&6VIP message!")
+ player.playSound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP)
+ }
+```
+
+### 4. Use Utility Functions for Common Tasks
+
+```kotlin
+// Good - using utilities
+val selectedReward = weightedRewards.randomWeighted()
+player.sendCenteredMessage("=== REWARD ===")
+logSuccess("Player ${player.name} received reward: $selectedReward")
+
+// Avoid - manual implementation of common patterns
+```
+
+## Performance Tips
+
+### 1. Batch Operations
+
+```kotlin
+// Good - batch inventory operations
+val items = listOf(item1, item2, item3)
+inventory.addItemsSafely(items) // Single operation
+
+// Avoid - individual operations in loop
+items.forEach { inventory.addItem(it) }
+```
+
+### 2. Cache Expensive Operations
+
+```kotlin
+class LocationService {
+ private val safeLocationCache = mutableMapOf()
+
+ fun findSafeLocation(location: Location): Location? {
+ return safeLocationCache.computeIfAbsent(location) {
+ it.findSafeSpot()
+ }
+ }
+}
+```
+
+### 3. Use Lazy Evaluation
+
+```kotlin
+// Good - lazy evaluation for expensive operations
+val nearbyPlayers by lazy { location.getNearbyPlayers(50.0) }
+
+// Only computed when first accessed
+if (someCondition) {
+ nearbyPlayers.forEach { /* process */ }
+}
+```
+
+The utility functions and extensions in KPaper significantly reduce boilerplate code and make Minecraft plugin development more enjoyable and maintainable. Use them liberally to write cleaner, more expressive code!
\ No newline at end of file
diff --git a/docs/core/plugin-development.md b/docs/core/plugin-development.md
new file mode 100644
index 0000000..1cc5f82
--- /dev/null
+++ b/docs/core/plugin-development.md
@@ -0,0 +1,691 @@
+# Plugin Development
+
+This guide covers the core concepts of KPaper plugin development, helping you understand the framework's architecture and best practices.
+
+## Core Architecture
+
+### KPlugin Base Class
+
+KPaper plugins extend the `KPlugin` class instead of the traditional `JavaPlugin`:
+
+```kotlin
+import cc.modlabs.kpaper.main.KPlugin
+
+class MyPlugin : KPlugin() {
+
+ override fun startup() {
+ // Called when plugin is enabled
+ logger.info("Plugin starting up!")
+ }
+
+ override fun shutdown() {
+ // Called when plugin is disabled
+ logger.info("Plugin shutting down!")
+ }
+
+ override fun load() {
+ // Called during plugin loading phase (before startup)
+ logger.info("Plugin loading...")
+ }
+}
+```
+
+### Lifecycle Methods
+
+KPaper provides three lifecycle methods:
+
+| Method | When Called | Purpose |
+|--------|-------------|---------|
+| `load()` | Plugin loading phase | Early initialization, before dependencies |
+| `startup()` | Plugin enabling phase | Main initialization, register features |
+| `shutdown()` | Plugin disabling phase | Cleanup, save data |
+
+## Feature Configuration
+
+### Enabling Features
+
+KPaper uses a feature system to enable only the components you need:
+
+```kotlin
+class MyPlugin : KPlugin() {
+
+ override val featureConfig = featureConfig {
+ // Core features
+ enableEventFeatures = true // Event system
+ enableCommandFeatures = true // Command framework
+ enableInventoryFeatures = true // GUI and inventory system
+
+ // Advanced features
+ enableCoroutineFeatures = true // Async operations
+ enableFileFeatures = true // File I/O utilities
+ enableWorldFeatures = true // World management
+ enableMessageFeatures = true // Message system
+ enableVisualFeatures = true // Visual effects
+ enableGameFeatures = true // Game mechanics
+ enableUtilFeatures = true // Utility functions
+ }
+
+ override fun startup() {
+ // Only enabled features are available
+ if (isFeatureEnabled(Feature.INVENTORY)) {
+ // Inventory features are available
+ setupGUIs()
+ }
+
+ if (isFeatureEnabled(Feature.COMMANDS)) {
+ // Command features are available
+ setupCommands()
+ }
+ }
+}
+```
+
+### Feature Dependencies
+
+Some features depend on others:
+
+```kotlin
+// This configuration automatically enables dependencies
+override val featureConfig = featureConfig {
+ enableInventoryFeatures = true // Requires event features
+ enableCommandFeatures = true // Requires util features
+ enableVisualFeatures = true // Requires world features
+
+ // Dependencies are automatically enabled:
+ // - Event features (for inventory)
+ // - Util features (for commands)
+ // - World features (for visuals)
+}
+```
+
+## Plugin Structure Patterns
+
+### Simple Plugin Structure
+
+For basic plugins:
+
+```kotlin
+class SimplePlugin : KPlugin() {
+
+ override val featureConfig = featureConfig {
+ enableEventFeatures = true
+ enableCommandFeatures = true
+ }
+
+ override fun startup() {
+ setupEvents()
+ setupCommands()
+ }
+
+ private fun setupEvents() {
+ listen { event ->
+ event.player.sendMessage("Welcome ${event.player.name}!")
+ }
+ }
+
+ private fun setupCommands() {
+ CommandBuilder("hello")
+ .description("Say hello")
+ .execute { sender, _ ->
+ sender.sendMessage("Hello from ${description.name}!")
+ }
+ .register()
+ }
+}
+```
+
+### Complex Plugin Structure
+
+For larger plugins with multiple systems:
+
+```kotlin
+class ComplexPlugin : KPlugin() {
+
+ // Core managers
+ private lateinit var configManager: ConfigurationManager
+ private lateinit var dataManager: DataManager
+ private lateinit var playerManager: PlayerManager
+
+ // Feature systems
+ private lateinit var economySystem: EconomySystem
+ private lateinit var shopSystem: ShopSystem
+ private lateinit var questSystem: QuestSystem
+
+ override val featureConfig = featureConfig {
+ enableEventFeatures = true
+ enableCommandFeatures = true
+ enableInventoryFeatures = true
+ enableCoroutineFeatures = true
+ enableFileFeatures = true
+ }
+
+ override fun load() {
+ // Initialize core managers first
+ configManager = ConfigurationManager(this)
+ dataManager = DataManager(this, configManager.databaseConfig)
+ }
+
+ override fun startup() {
+ // Initialize managers
+ playerManager = PlayerManager(this, dataManager)
+
+ // Initialize systems
+ economySystem = EconomySystem(this, playerManager)
+ shopSystem = ShopSystem(this, economySystem)
+ questSystem = QuestSystem(this, playerManager, economySystem)
+
+ // Load all systems
+ val systems = listOf(
+ playerManager,
+ economySystem,
+ shopSystem,
+ questSystem
+ )
+
+ systems.forEach { system ->
+ try {
+ system.initialize()
+ logger.info("Initialized ${system.javaClass.simpleName}")
+ } catch (e: Exception) {
+ logger.error("Failed to initialize ${system.javaClass.simpleName}", e)
+ }
+ }
+
+ logger.info("Plugin fully loaded with ${systems.size} systems")
+ }
+
+ override fun shutdown() {
+ // Shutdown in reverse order
+ listOf(questSystem, shopSystem, economySystem, playerManager)
+ .reversed()
+ .forEach { system ->
+ try {
+ system.shutdown()
+ } catch (e: Exception) {
+ logger.error("Error shutting down ${system.javaClass.simpleName}", e)
+ }
+ }
+
+ dataManager.close()
+ }
+}
+
+// Base class for plugin systems
+abstract class PluginSystem(protected val plugin: ComplexPlugin) {
+ abstract fun initialize()
+ abstract fun shutdown()
+
+ protected fun log(message: String) {
+ plugin.logger.info("[${javaClass.simpleName}] $message")
+ }
+
+ protected fun logError(message: String, exception: Throwable? = null) {
+ plugin.logger.error("[${javaClass.simpleName}] $message", exception)
+ }
+}
+```
+
+## Configuration Management
+
+### Type-Safe Configuration
+
+Define configuration classes for type safety:
+
+```kotlin
+// Configuration data classes
+data class PluginConfig(
+ val database: DatabaseConfig = DatabaseConfig(),
+ val economy: EconomyConfig = EconomyConfig(),
+ val messages: MessageConfig = MessageConfig(),
+ val features: FeatureConfig = FeatureConfig()
+)
+
+data class DatabaseConfig(
+ val type: String = "sqlite",
+ val host: String = "localhost",
+ val port: Int = 3306,
+ val database: String = "minecraft",
+ val username: String = "root",
+ val password: String = "",
+ val connectionTimeout: Int = 30000
+)
+
+data class EconomyConfig(
+ val startingBalance: Double = 100.0,
+ val maxBalance: Double = 1000000.0,
+ val enableBanking: Boolean = true,
+ val transactionFee: Double = 0.05
+)
+
+data class MessageConfig(
+ val prefix: String = "&8[&6MyPlugin&8] &r",
+ val noPermission: String = "&cYou don't have permission to do that!",
+ val playerNotFound: String = "&cPlayer not found!",
+ val economyInsufficientFunds: String = "&cInsufficient funds!"
+)
+
+data class FeatureConfig(
+ val enableShop: Boolean = true,
+ val enableQuests: Boolean = true,
+ val enablePvP: Boolean = false
+)
+
+// Loading configuration
+class ConfigurationManager(private val plugin: KPlugin) {
+
+ lateinit var config: PluginConfig
+ private set
+
+ fun load() {
+ config = plugin.loadConfiguration()
+
+ // Validate configuration
+ validateConfig()
+
+ // Log configuration summary
+ logConfigSummary()
+ }
+
+ fun reload() {
+ plugin.reloadConfig()
+ load()
+ }
+
+ private fun validateConfig() {
+ require(config.economy.startingBalance >= 0) {
+ "Starting balance cannot be negative"
+ }
+
+ require(config.economy.maxBalance > config.economy.startingBalance) {
+ "Max balance must be greater than starting balance"
+ }
+
+ if (config.database.type == "mysql") {
+ require(config.database.host.isNotBlank()) {
+ "MySQL host cannot be empty"
+ }
+ }
+ }
+
+ private fun logConfigSummary() {
+ plugin.logger.info("Configuration loaded:")
+ plugin.logger.info(" Database: ${config.database.type}")
+ plugin.logger.info(" Economy enabled: ${config.features.enableShop}")
+ plugin.logger.info(" Starting balance: ${config.economy.startingBalance}")
+ }
+}
+```
+
+### Configuration Files
+
+Create default configuration files:
+
+```kotlin
+// resources/config.yml
+database:
+ type: "sqlite"
+ host: "localhost"
+ port: 3306
+ database: "minecraft"
+ username: "root"
+ password: ""
+ connection-timeout: 30000
+
+economy:
+ starting-balance: 100.0
+ max-balance: 1000000.0
+ enable-banking: true
+ transaction-fee: 0.05
+
+messages:
+ prefix: "&8[&6MyPlugin&8] &r"
+ no-permission: "&cYou don't have permission to do that!"
+ player-not-found: "&cPlayer not found!"
+ economy-insufficient-funds: "&cInsufficient funds!"
+
+features:
+ enable-shop: true
+ enable-quests: true
+ enable-pvp: false
+```
+
+## Error Handling and Logging
+
+### Structured Error Handling
+
+Implement comprehensive error handling:
+
+```kotlin
+class RobustPlugin : KPlugin() {
+
+ override fun startup() {
+ try {
+ initializePlugin()
+ } catch (e: ConfigurationException) {
+ logger.error("Configuration error: ${e.message}")
+ logger.error("Plugin will be disabled. Please fix your configuration.")
+ server.pluginManager.disablePlugin(this)
+ } catch (e: DatabaseException) {
+ logger.error("Database connection failed: ${e.message}")
+ logger.error("Running in offline mode with limited functionality.")
+ initializeOfflineMode()
+ } catch (e: Exception) {
+ logger.error("Unexpected error during startup", e)
+ server.pluginManager.disablePlugin(this)
+ }
+ }
+
+ private fun initializePlugin() {
+ // Phase 1: Load configuration
+ logPhase("Loading configuration...")
+ val config = loadConfiguration()
+
+ // Phase 2: Initialize database
+ logPhase("Connecting to database...")
+ val database = connectToDatabase(config.database)
+
+ // Phase 3: Load player data
+ logPhase("Loading player data...")
+ val playerManager = PlayerManager(database)
+
+ // Phase 4: Initialize systems
+ logPhase("Initializing game systems...")
+ initializeSystems(config, playerManager)
+
+ logger.info("Plugin initialization completed successfully!")
+ }
+
+ private fun logPhase(message: String) {
+ logger.info("=== $message ===")
+ }
+
+ private fun connectToDatabase(config: DatabaseConfig): Database {
+ return try {
+ when (config.type.lowercase()) {
+ "mysql" -> MySQLDatabase(config)
+ "sqlite" -> SQLiteDatabase(config)
+ else -> throw ConfigurationException("Unsupported database type: ${config.type}")
+ }
+ } catch (e: SQLException) {
+ throw DatabaseException("Failed to connect to ${config.type} database", e)
+ }
+ }
+}
+
+// Custom exceptions
+class ConfigurationException(message: String, cause: Throwable? = null) : Exception(message, cause)
+class DatabaseException(message: String, cause: Throwable? = null) : Exception(message, cause)
+```
+
+### Logging Best Practices
+
+Use structured logging for better debugging:
+
+```kotlin
+class LoggingExamples : KPlugin() {
+
+ override fun startup() {
+ // Use appropriate log levels
+ logger.debug("Debug information for developers")
+ logger.info("General information")
+ logger.warn("Warning about potential issues")
+ logger.error("Error that needs attention")
+
+ // Log with context
+ logPlayerAction("Steve", "joined the server")
+ logSystemEvent("Economy", "player balance updated", mapOf(
+ "player" to "Steve",
+ "oldBalance" to 100.0,
+ "newBalance" to 150.0,
+ "reason" to "quest_completion"
+ ))
+
+ // Log performance metrics
+ val startTime = System.currentTimeMillis()
+ performExpensiveOperation()
+ val duration = System.currentTimeMillis() - startTime
+ logger.info("Expensive operation completed in ${duration}ms")
+ }
+
+ private fun logPlayerAction(player: String, action: String) {
+ logger.info("Player action: $player $action")
+ }
+
+ private fun logSystemEvent(system: String, event: String, data: Map) {
+ val dataString = data.entries.joinToString(", ") { "${it.key}=${it.value}" }
+ logger.info("[$system] $event ($dataString)")
+ }
+}
+```
+
+## Performance Optimization
+
+### Async Operations
+
+Use coroutines for expensive operations:
+
+```kotlin
+class OptimizedPlugin : KPlugin() {
+
+ private val databaseScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+
+ override fun startup() {
+ // Initialize async systems
+ initializeAsyncSystems()
+ }
+
+ override fun shutdown() {
+ // Cancel all async operations
+ databaseScope.cancel()
+ }
+
+ private fun initializeAsyncSystems() {
+ // Load data asynchronously
+ launch {
+ val playerData = withContext(Dispatchers.IO) {
+ loadAllPlayerData() // Heavy database operation
+ }
+
+ // Switch back to main thread for Bukkit operations
+ withContext(Dispatchers.Main) {
+ applyPlayerData(playerData)
+ logger.info("Loaded ${playerData.size} player records")
+ }
+ }
+ }
+
+ fun savePlayerDataAsync(player: Player, data: PlayerData) {
+ databaseScope.launch {
+ try {
+ database.savePlayerData(player.uniqueId, data)
+ logger.debug("Saved data for ${player.name}")
+ } catch (e: Exception) {
+ logger.error("Failed to save data for ${player.name}", e)
+ }
+ }
+ }
+}
+```
+
+### Caching Strategies
+
+Implement efficient caching:
+
+```kotlin
+class CachedDataManager {
+
+ // Player data cache with expiration
+ private val playerCache = ExpiringCache(
+ expireAfter = Duration.ofMinutes(30),
+ maxSize = 1000
+ )
+
+ // Configuration cache (rarely changes)
+ private val configCache = mutableMapOf()
+
+ // World data cache (per-world data)
+ private val worldCache = ConcurrentHashMap()
+
+ suspend fun getPlayerData(uuid: UUID): PlayerData {
+ return playerCache.get(uuid) {
+ // Load from database if not cached
+ database.loadPlayerData(uuid) ?: PlayerData.createDefault(uuid)
+ }
+ }
+
+ fun invalidatePlayerData(uuid: UUID) {
+ playerCache.invalidate(uuid)
+ }
+
+ fun getWorldData(worldName: String): WorldData {
+ return worldCache.computeIfAbsent(worldName) { name ->
+ loadWorldData(name)
+ }
+ }
+}
+
+class ExpiringCache(
+ private val expireAfter: Duration,
+ private val maxSize: Int
+) {
+ private val cache = ConcurrentHashMap>()
+
+ suspend fun get(key: K, loader: suspend (K) -> V): V {
+ val entry = cache[key]
+
+ // Check if cached and not expired
+ if (entry != null && !entry.isExpired()) {
+ return entry.value
+ }
+
+ // Load new value
+ val value = loader(key)
+ cache[key] = CacheEntry(value, System.currentTimeMillis())
+
+ // Cleanup if needed
+ if (cache.size > maxSize) {
+ cleanup()
+ }
+
+ return value
+ }
+
+ fun invalidate(key: K) {
+ cache.remove(key)
+ }
+
+ private fun cleanup() {
+ val now = System.currentTimeMillis()
+ val expireTime = expireAfter.toMillis()
+
+ cache.entries.removeIf { entry ->
+ now - entry.value.timestamp > expireTime
+ }
+ }
+
+ private data class CacheEntry(val value: V, val timestamp: Long) {
+ fun isExpired(): Boolean {
+ return System.currentTimeMillis() - timestamp > Duration.ofMinutes(30).toMillis()
+ }
+ }
+}
+```
+
+## Testing and Debugging
+
+### Unit Testing
+
+Write tests for your plugin logic:
+
+```kotlin
+class PluginTest {
+
+ @Test
+ fun testEconomySystem() {
+ val economy = EconomySystem()
+ val player = mockPlayer("TestPlayer")
+
+ // Test initial balance
+ assertEquals(100.0, economy.getBalance(player))
+
+ // Test deposit
+ economy.deposit(player, 50.0)
+ assertEquals(150.0, economy.getBalance(player))
+
+ // Test withdrawal
+ assertTrue(economy.withdraw(player, 25.0))
+ assertEquals(125.0, economy.getBalance(player))
+
+ // Test insufficient funds
+ assertFalse(economy.withdraw(player, 200.0))
+ assertEquals(125.0, economy.getBalance(player))
+ }
+
+ @Test
+ fun testQuestCompletion() {
+ val questSystem = QuestSystem()
+ val player = mockPlayer("TestPlayer")
+ val quest = Quest("test_quest", "Test Quest", listOf(
+ KillObjective("zombie", 10),
+ CollectObjective(Material.DIAMOND, 5)
+ ))
+
+ questSystem.startQuest(player, quest)
+
+ // Test progress tracking
+ questSystem.updateProgress(player, quest, "zombie_kill")
+ assertEquals(1, questSystem.getProgress(player, quest, "zombie"))
+
+ // Test completion
+ questSystem.setProgress(player, quest, "zombie", 10)
+ questSystem.setProgress(player, quest, "diamond", 5)
+
+ assertTrue(questSystem.isQuestCompleted(player, quest))
+ }
+
+ private fun mockPlayer(name: String): Player {
+ // Create mock player for testing
+ return mock().apply {
+ whenever(this.name).thenReturn(name)
+ whenever(this.uniqueId).thenReturn(UUID.randomUUID())
+ }
+ }
+}
+```
+
+### Debug Tools
+
+Create debugging utilities:
+
+```kotlin
+class DebugTools(private val plugin: KPlugin) {
+
+ fun dumpPlayerData(player: Player) {
+ plugin.logger.info("=== Player Data Dump: ${player.name} ===")
+ plugin.logger.info("UUID: ${player.uniqueId}")
+ plugin.logger.info("Location: ${player.location}")
+ plugin.logger.info("Health: ${player.health}/${player.maxHealth}")
+ plugin.logger.info("Food: ${player.foodLevel}")
+ plugin.logger.info("Level: ${player.level} (${player.exp} exp)")
+ plugin.logger.info("Game Mode: ${player.gameMode}")
+ plugin.logger.info("Permissions: ${player.effectivePermissions.map { it.permission }}")
+ }
+
+ fun dumpSystemStatus() {
+ plugin.logger.info("=== System Status ===")
+ plugin.logger.info("Online Players: ${plugin.server.onlinePlayers.size}")
+ plugin.logger.info("Loaded Worlds: ${plugin.server.worlds.size}")
+ plugin.logger.info("TPS: ${plugin.server.tps.joinToString(", ")}")
+ plugin.logger.info("Memory: ${getMemoryUsage()}")
+ }
+
+ private fun getMemoryUsage(): String {
+ val runtime = Runtime.getRuntime()
+ val used = runtime.totalMemory() - runtime.freeMemory()
+ val max = runtime.maxMemory()
+ return "${used / 1024 / 1024}MB / ${max / 1024 / 1024}MB"
+ }
+}
+```
+
+These patterns and practices will help you build robust, maintainable KPaper plugins that scale well and provide a great user experience.
\ No newline at end of file
diff --git a/docs/getting-started/migration.md b/docs/getting-started/migration.md
new file mode 100644
index 0000000..61eabfa
--- /dev/null
+++ b/docs/getting-started/migration.md
@@ -0,0 +1,718 @@
+# Migration Guide
+
+This guide helps you migrate existing Bukkit/Paper plugins to KPaper, taking advantage of its modern Kotlin-first APIs while maintaining compatibility with your existing code.
+
+## Migration Overview
+
+KPaper is designed to be **incrementally adoptable** - you don't need to rewrite everything at once. You can gradually migrate parts of your plugin while keeping existing code functional.
+
+### Migration Strategy Options
+
+1. **Gradual Migration** - Migrate feature by feature over time
+2. **New Feature Migration** - Use KPaper for new features only
+3. **Full Migration** - Rewrite the entire plugin (recommended for small plugins)
+
+## Basic Plugin Migration
+
+### From JavaPlugin to KPlugin
+
+**Before (Bukkit/Paper):**
+```java
+public class MyPlugin extends JavaPlugin {
+
+ @Override
+ public void onEnable() {
+ getLogger().info("Plugin enabled!");
+
+ // Register events
+ getServer().getPluginManager().registerEvents(new PlayerListener(), this);
+
+ // Register commands
+ Objects.requireNonNull(getCommand("test")).setExecutor(new TestCommand());
+
+ // Load configuration
+ saveDefaultConfig();
+
+ getLogger().info("Plugin fully loaded!");
+ }
+
+ @Override
+ public void onDisable() {
+ getLogger().info("Plugin disabled!");
+ }
+}
+```
+
+**After (KPaper):**
+```kotlin
+class MyPlugin : KPlugin() {
+
+ override fun startup() {
+ logger.info("Plugin enabled!")
+
+ // Register events (much simpler)
+ listen { event ->
+ event.player.sendMessage("Welcome ${event.player.name}!")
+ }
+
+ // Register commands (fluent API)
+ CommandBuilder("test")
+ .description("Test command")
+ .execute { sender, args ->
+ sender.sendMessage("Hello from KPaper!")
+ }
+ .register()
+
+ // Configuration is handled automatically
+
+ logger.info("Plugin fully loaded!")
+ }
+
+ override fun shutdown() {
+ logger.info("Plugin disabled!")
+ }
+}
+```
+
+### Key Differences
+
+| Aspect | Bukkit/Paper | KPaper |
+|--------|-------------|---------|
+| Base Class | `JavaPlugin` | `KPlugin` |
+| Enable Method | `onEnable()` | `startup()` |
+| Disable Method | `onDisable()` | `shutdown()` |
+| Event Registration | Manual `registerEvents()` | `listen()` |
+| Command Registration | Manual executor setup | `CommandBuilder` |
+| Configuration | Manual `saveDefaultConfig()` | Automatic handling |
+
+## Event System Migration
+
+### Traditional Event Listeners
+
+**Before:**
+```java
+public class PlayerListener implements Listener {
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ Player player = event.getPlayer();
+ player.sendMessage("Welcome " + player.getName() + "!");
+
+ if (!player.hasPlayedBefore()) {
+ giveStarterKit(player);
+ }
+ }
+
+ @EventHandler(priority = EventPriority.HIGH)
+ public void onPlayerInteract(PlayerInteractEvent event) {
+ if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
+ Block block = event.getClickedBlock();
+ if (block != null && block.getType() == Material.CHEST) {
+ if (!event.getPlayer().hasPermission("chests.use")) {
+ event.setCancelled(true);
+ event.getPlayer().sendMessage("No permission!");
+ }
+ }
+ }
+ }
+}
+```
+
+**After:**
+```kotlin
+class MyPlugin : KPlugin() {
+ override fun startup() {
+ // Simple event handling
+ listen { event ->
+ val player = event.player
+ player.sendMessage("Welcome ${player.name}!")
+
+ if (!player.hasPlayedBefore()) {
+ giveStarterKit(player)
+ }
+ }
+
+ // Event with priority
+ listen(priority = EventPriority.HIGH) { event ->
+ if (event.action == Action.RIGHT_CLICK_BLOCK) {
+ val block = event.clickedBlock
+ if (block?.type == Material.CHEST) {
+ if (!event.player.hasPermission("chests.use")) {
+ event.isCancelled = true
+ event.player.sendMessage("No permission!")
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+### Event Handler Classes
+
+If you prefer organized event handlers:
+
+**Before:**
+```java
+public class CombatListener implements Listener {
+ private final MyPlugin plugin;
+
+ public CombatListener(MyPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @EventHandler
+ public void onEntityDamage(EntityDamageEvent event) {
+ // Handle damage
+ }
+
+ @EventHandler
+ public void onPlayerDeath(PlayerDeathEvent event) {
+ // Handle death
+ }
+}
+
+// In main plugin class:
+getServer().getPluginManager().registerEvents(new CombatListener(this), this);
+```
+
+**After:**
+```kotlin
+class CombatEventHandler(private val plugin: MyPlugin) : EventHandler() {
+
+ override fun load() {
+ listen { handleDamage(it) }
+ listen { handleDeath(it) }
+ }
+
+ override fun unload() {
+ // Cleanup if needed
+ }
+
+ private fun handleDamage(event: EntityDamageEvent) {
+ // Handle damage
+ }
+
+ private fun handleDeath(event: PlayerDeathEvent) {
+ // Handle death
+ }
+}
+
+// In main plugin class:
+class MyPlugin : KPlugin() {
+ private lateinit var combatHandler: CombatEventHandler
+
+ override fun startup() {
+ combatHandler = CombatEventHandler(this)
+ combatHandler.load()
+ }
+
+ override fun shutdown() {
+ combatHandler.unload()
+ }
+}
+```
+
+## Command System Migration
+
+### Basic Commands
+
+**Before:**
+```java
+public class TestCommand implements CommandExecutor {
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage("Players only!");
+ return true;
+ }
+
+ Player player = (Player) sender;
+
+ if (args.length == 0) {
+ player.sendMessage("Usage: /test ");
+ return true;
+ }
+
+ String message = String.join(" ", args);
+ player.sendMessage("You said: " + message);
+ return true;
+ }
+}
+
+// In plugin.yml:
+commands:
+ test:
+ description: Test command
+ usage: /test
+
+// In main class:
+Objects.requireNonNull(getCommand("test")).setExecutor(new TestCommand());
+```
+
+**After:**
+```kotlin
+class MyPlugin : KPlugin() {
+ override fun startup() {
+ CommandBuilder("test")
+ .description("Test command")
+ .usage("/test ")
+ .playerOnly(true)
+ .argument(stringArgument("message"))
+ .execute { sender, args ->
+ val player = sender as Player
+ val message = args.getString("message")
+ player.sendMessage("You said: $message")
+ }
+ .register()
+ }
+}
+```
+
+### Complex Commands with Sub-commands
+
+**Before:**
+```java
+public class AdminCommand implements CommandExecutor {
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ if (args.length == 0) {
+ sender.sendMessage("Usage: /admin ");
+ return true;
+ }
+
+ String subcommand = args[0].toLowerCase();
+
+ switch (subcommand) {
+ case "ban":
+ if (args.length < 2) {
+ sender.sendMessage("Usage: /admin ban [reason]");
+ return true;
+ }
+
+ Player target = Bukkit.getPlayer(args[1]);
+ if (target == null) {
+ sender.sendMessage("Player not found!");
+ return true;
+ }
+
+ String reason = args.length > 2 ?
+ String.join(" ", Arrays.copyOfRange(args, 2, args.length)) :
+ "Banned by admin";
+
+ target.ban(reason, null, sender.getName());
+ sender.sendMessage("Banned " + target.getName());
+ break;
+
+ case "heal":
+ if (!(sender instanceof Player)) {
+ sender.sendMessage("Players only!");
+ return true;
+ }
+
+ Player player = (Player) sender;
+ player.setHealth(player.getMaxHealth());
+ player.sendMessage("Healed!");
+ break;
+
+ default:
+ sender.sendMessage("Unknown subcommand: " + subcommand);
+ break;
+ }
+
+ return true;
+ }
+}
+```
+
+**After:**
+```kotlin
+class MyPlugin : KPlugin() {
+ override fun startup() {
+ CommandBuilder("admin")
+ .description("Admin commands")
+ .permission("admin.use")
+
+ // /admin ban [reason]
+ .subcommand("ban")
+ .permission("admin.ban")
+ .argument(playerArgument("target"))
+ .argument(stringArgument("reason", optional = true))
+ .execute { sender, args ->
+ val target = args.getPlayer("target")
+ val reason = args.getStringOrDefault("reason", "Banned by admin")
+
+ target.ban(reason, sender.name)
+ sender.sendMessage("Banned ${target.name}")
+ }
+
+ // /admin heal
+ .subcommand("heal")
+ .permission("admin.heal")
+ .playerOnly(true)
+ .execute { sender, _ ->
+ val player = sender as Player
+ player.health = player.maxHealth
+ player.sendMessage("Healed!")
+ }
+
+ .register()
+ }
+}
+```
+
+## GUI System Migration
+
+### Traditional Inventory GUIs
+
+**Before:**
+```java
+public class ShopGUI {
+
+ public static void openShop(Player player) {
+ Inventory inventory = Bukkit.createInventory(null, 27, "Shop");
+
+ // Create items
+ ItemStack sword = new ItemStack(Material.DIAMOND_SWORD);
+ ItemMeta swordMeta = sword.getItemMeta();
+ swordMeta.setDisplayName(ChatColor.RED + "Weapons");
+ swordMeta.setLore(Arrays.asList(ChatColor.GRAY + "Click to browse weapons"));
+ sword.setItemMeta(swordMeta);
+
+ ItemStack close = new ItemStack(Material.BARRIER);
+ ItemMeta closeMeta = close.getItemMeta();
+ closeMeta.setDisplayName(ChatColor.RED + "Close");
+ close.setItemMeta(closeMeta);
+
+ // Set items
+ inventory.setItem(13, sword);
+ inventory.setItem(22, close);
+
+ player.openInventory(inventory);
+ }
+}
+
+// Event handler for clicks
+@EventHandler
+public void onInventoryClick(InventoryClickEvent event) {
+ if (!event.getView().getTitle().equals("Shop")) return;
+
+ event.setCancelled(true);
+
+ if (event.getCurrentItem() == null) return;
+
+ Player player = (Player) event.getWhoClicked();
+ int slot = event.getSlot();
+
+ if (slot == 13) {
+ // Open weapons shop
+ openWeaponsShop(player);
+ } else if (slot == 22) {
+ player.closeInventory();
+ }
+}
+```
+
+**After:**
+```kotlin
+class MyPlugin : KPlugin() {
+
+ fun openShop(player: Player) {
+ val gui = simpleGUI("Shop", 27) {
+
+ item(13, ItemBuilder(Material.DIAMOND_SWORD)
+ .name("&cWeapons")
+ .lore("&7Click to browse weapons")
+ .build()) {
+ openWeaponsShop(player)
+ }
+
+ item(22, ItemBuilder(Material.BARRIER)
+ .name("&cClose")
+ .build()) {
+ player.closeInventory()
+ }
+ }
+
+ player.openInventory(gui)
+ }
+}
+```
+
+## Configuration Migration
+
+### File-based Configuration
+
+**Before:**
+```java
+public class MyPlugin extends JavaPlugin {
+
+ @Override
+ public void onEnable() {
+ saveDefaultConfig();
+
+ String welcomeMessage = getConfig().getString("messages.welcome", "Welcome!");
+ int maxPlayers = getConfig().getInt("limits.max-players", 100);
+ boolean enableFeature = getConfig().getBoolean("features.special-feature", false);
+
+ // Use configuration values...
+ }
+}
+```
+
+**After:**
+```kotlin
+class MyPlugin : KPlugin() {
+
+ private lateinit var config: MyConfig
+
+ override fun startup() {
+ config = loadConfiguration() // Automatic config loading
+
+ val welcomeMessage = config.messages.welcome
+ val maxPlayers = config.limits.maxPlayers
+ val enableFeature = config.features.specialFeature
+
+ // Use configuration values...
+ }
+}
+
+// Type-safe configuration classes
+data class MyConfig(
+ val messages: Messages = Messages(),
+ val limits: Limits = Limits(),
+ val features: Features = Features()
+) {
+ data class Messages(
+ val welcome: String = "Welcome!"
+ )
+
+ data class Limits(
+ val maxPlayers: Int = 100
+ )
+
+ data class Features(
+ val specialFeature: Boolean = false
+ )
+}
+```
+
+## Data Storage Migration
+
+### Player Data Management
+
+**Before:**
+```java
+public class PlayerDataManager {
+ private final Map playerData = new HashMap<>();
+ private final File dataFolder;
+
+ public PlayerDataManager(File dataFolder) {
+ this.dataFolder = dataFolder;
+ }
+
+ public void loadPlayerData(Player player) {
+ File file = new File(dataFolder, "players/" + player.getUniqueId() + ".yml");
+ if (file.exists()) {
+ FileConfiguration config = YamlConfiguration.loadConfiguration(file);
+ PlayerData data = new PlayerData();
+ data.setLevel(config.getInt("level", 1));
+ data.setExperience(config.getInt("experience", 0));
+ data.setMoney(config.getDouble("money", 100.0));
+ playerData.put(player.getUniqueId(), data);
+ }
+ }
+
+ public void savePlayerData(Player player) {
+ PlayerData data = playerData.get(player.getUniqueId());
+ if (data != null) {
+ File file = new File(dataFolder, "players/" + player.getUniqueId() + ".yml");
+ file.getParentFile().mkdirs();
+
+ FileConfiguration config = new YamlConfiguration();
+ config.set("level", data.getLevel());
+ config.set("experience", data.getExperience());
+ config.set("money", data.getMoney());
+
+ try {
+ config.save(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
+```
+
+**After:**
+```kotlin
+class PlayerDataManager(private val plugin: MyPlugin) {
+ private val playerData = mutableMapOf()
+
+ suspend fun loadPlayerData(player: Player): PlayerData {
+ return playerData.computeIfAbsent(player.uniqueId) {
+ loadFromFile(it) ?: PlayerData.createDefault(it)
+ }
+ }
+
+ suspend fun savePlayerData(uuid: UUID, data: PlayerData) {
+ playerData[uuid] = data
+ withContext(Dispatchers.IO) {
+ saveToFile(uuid, data)
+ }
+ }
+
+ private suspend fun loadFromFile(uuid: UUID): PlayerData? = withContext(Dispatchers.IO) {
+ val file = File(plugin.dataFolder, "players/$uuid.json")
+ if (file.exists()) {
+ gson.fromJson(file.readText(), PlayerData::class.java)
+ } else null
+ }
+
+ private suspend fun saveToFile(uuid: UUID, data: PlayerData) = withContext(Dispatchers.IO) {
+ val file = File(plugin.dataFolder, "players/$uuid.json")
+ file.parentFile.mkdirs()
+ file.writeText(gson.toJson(data))
+ }
+}
+
+data class PlayerData(
+ val uuid: UUID,
+ var level: Int = 1,
+ var experience: Int = 0,
+ var money: Double = 100.0
+) {
+ companion object {
+ fun createDefault(uuid: UUID) = PlayerData(uuid)
+ }
+}
+```
+
+## Gradual Migration Strategy
+
+### Phase 1: Setup KPaper
+
+1. **Add KPaper Dependency** to your build file
+2. **Change Base Class** from JavaPlugin to KPlugin
+3. **Update Lifecycle Methods** (onEnable → startup, onDisable → shutdown)
+4. **Test Basic Functionality** to ensure everything still works
+
+### Phase 2: Migrate Events
+
+1. **Start with Simple Events** (like PlayerJoinEvent)
+2. **Gradually Replace** @EventHandler methods with listen()
+3. **Keep Old Listeners** alongside new ones during transition
+4. **Remove Old Listeners** once new ones are tested
+
+### Phase 3: Migrate Commands
+
+1. **Replace Simple Commands** first using CommandBuilder
+2. **Update Complex Commands** with sub-command support
+3. **Add Argument Validation** and type-safe parsing
+4. **Remove Old CommandExecutor** classes
+
+### Phase 4: Modernize GUIs
+
+1. **Replace Manual Inventory Creation** with simpleGUI()
+2. **Simplify Click Handling** with inline callbacks
+3. **Add Advanced Features** like pagination and animations
+4. **Remove Old InventoryClickEvent** handlers
+
+### Phase 5: Enhance with KPaper Features
+
+1. **Add Coroutines** for async operations
+2. **Use Extensions** for cleaner code
+3. **Implement Advanced Features** like custom events
+4. **Optimize Performance** with KPaper utilities
+
+## Common Migration Issues
+
+### Issue 1: Plugin Not Loading
+
+**Problem:** Plugin fails to load after migration to KPlugin
+
+**Solution:**
+```kotlin
+// Ensure you have the correct plugin.yml main class
+main: com.yourpackage.YourPlugin
+
+// And your class extends KPlugin properly
+class YourPlugin : KPlugin() {
+ // Implementation
+}
+```
+
+### Issue 2: Events Not Working
+
+**Problem:** Event listeners from old system still firing alongside new ones
+
+**Solution:**
+```kotlin
+// Remove old listener registration
+// getServer().getPluginManager().registerEvents(new OldListener(), this);
+
+// Keep only KPaper event registration
+listen { /* new handler */ }
+```
+
+### Issue 3: Commands Not Registering
+
+**Problem:** CommandBuilder commands not appearing
+
+**Solution:**
+```kotlin
+// Ensure you call .register() on CommandBuilder
+CommandBuilder("mycommand")
+ .description("My command")
+ .execute { sender, args ->
+ // Command logic
+ }
+ .register() // Don't forget this!
+```
+
+### Issue 4: Configuration Not Loading
+
+**Problem:** Configuration values returning defaults
+
+**Solution:**
+```kotlin
+// Ensure configuration is loaded in startup()
+override fun startup() {
+ val config = loadConfiguration() // Load config first
+ // Then use config values
+}
+```
+
+## Best Practices for Migration
+
+### 1. Test Thoroughly
+
+- Test each migrated component individually
+- Keep backup of working version
+- Use staging server for testing
+- Validate all functionality before deployment
+
+### 2. Maintain Compatibility
+
+- Keep old systems running during migration
+- Provide fallbacks for critical functionality
+- Document breaking changes for users
+- Version your plugin appropriately
+
+### 3. Leverage KPaper Features
+
+- Use type-safe configurations
+- Implement proper error handling
+- Take advantage of async capabilities
+- Use extensions for cleaner code
+
+### 4. Plan the Migration
+
+- Start with non-critical features
+- Migrate related functionality together
+- Update documentation as you go
+- Train team members on new patterns
+
+The migration to KPaper is incremental and can be done at your own pace. Start small, test thoroughly, and gradually adopt more KPaper features as you become comfortable with the API.
\ No newline at end of file
diff --git a/docs/reference/troubleshooting.md b/docs/reference/troubleshooting.md
new file mode 100644
index 0000000..06e452b
--- /dev/null
+++ b/docs/reference/troubleshooting.md
@@ -0,0 +1,691 @@
+# Troubleshooting
+
+This guide helps you resolve common issues when developing with KPaper. If you don't find your issue here, please check our [GitHub Issues](https://github.com/ModLabsCC/KPaper/issues) or create a new one.
+
+## Installation Issues
+
+### KPaper Dependency Not Found
+
+**Error:**
+```
+Could not resolve dependency: cc.modlabs:KPaper:LATEST
+```
+
+**Causes & Solutions:**
+
+1. **Missing Repository**
+ ```kotlin
+ // Add to build.gradle.kts
+ repositories {
+ mavenCentral()
+ maven("https://nexus.modlabs.cc/repository/maven-mirrors/") // Add this
+ }
+ ```
+
+2. **Invalid Version**
+ ```kotlin
+ // Use specific version instead of LATEST
+ dependencies {
+ implementation("cc.modlabs:KPaper:2025.1.1.1234") // Use actual version
+ }
+ ```
+
+3. **Network Issues**
+ - Check internet connection
+ - Try with VPN if corporate firewall blocks access
+ - Use `--refresh-dependencies` flag: `./gradlew build --refresh-dependencies`
+
+### Java Version Compatibility
+
+**Error:**
+```
+Unsupported class file major version 65
+```
+
+**Solution:**
+KPaper requires Java 21+. Update your JDK:
+
+```kotlin
+// In build.gradle.kts
+kotlin {
+ jvmToolchain(21) // Use Java 21
+}
+
+tasks.withType {
+ options.release.set(21)
+}
+```
+
+### Paper Version Mismatch
+
+**Error:**
+```
+NoSuchMethodError or ClassNotFoundException with Paper classes
+```
+
+**Solution:**
+Ensure your Paper version matches KPaper requirements:
+
+```kotlin
+dependencies {
+ paperweight.paperDevBundle("1.21.6-R0.1-SNAPSHOT") // Match KPaper's target version
+ implementation("cc.modlabs:KPaper:LATEST")
+}
+```
+
+## Plugin Loading Issues
+
+### Plugin Not Loading
+
+**Error:**
+```
+Could not load 'plugins/YourPlugin.jar' in folder 'plugins'
+```
+
+**Troubleshooting Steps:**
+
+1. **Check plugin.yml**
+ ```yaml
+ name: YourPlugin
+ version: '1.0.0'
+ main: com.yourpackage.YourPlugin # Must match your class path
+ api-version: '1.21'
+ ```
+
+2. **Verify Main Class**
+ ```kotlin
+ // Must extend KPlugin, not JavaPlugin
+ class YourPlugin : KPlugin() {
+ override fun startup() {
+ // Implementation
+ }
+ }
+ ```
+
+3. **Check Dependencies**
+ ```yaml
+ # In plugin.yml - if you depend on other plugins
+ depend:
+ - SomeRequiredPlugin
+ softdepend:
+ - SomeOptionalPlugin
+ ```
+
+4. **Examine Server Logs**
+ Look for specific error messages in server console.
+
+### KPlugin Instance Error
+
+**Error:**
+```
+The main instance has been modified, even though it has already been set by another plugin!
+```
+
+**Cause:** Multiple KPaper plugins creating conflicting instances.
+
+**Solution:**
+```kotlin
+// Ensure only one plugin extends KPlugin, or use proper isolation
+class YourPlugin : KPlugin() {
+ override fun startup() {
+ // Your plugin code
+ }
+}
+```
+
+## Event System Issues
+
+### Events Not Firing
+
+**Problem:** Event listeners not being called
+
+**Troubleshooting:**
+
+1. **Check Event Registration**
+ ```kotlin
+ // Correct
+ listen { event ->
+ // Handler code
+ }
+
+ // Wrong - missing listen call
+ { event: PlayerJoinEvent ->
+ // This won't work
+ }
+ ```
+
+2. **Verify Event Priority**
+ ```kotlin
+ // If other plugins cancel events, use higher priority
+ listen(priority = EventPriority.HIGH) { event ->
+ // This runs before NORMAL priority listeners
+ }
+ ```
+
+3. **Check Event Cancellation**
+ ```kotlin
+ listen { event ->
+ if (event.isCancelled) {
+ // Event was cancelled by another plugin
+ return@listen
+ }
+ // Your logic here
+ }
+ ```
+
+### Custom Events Not Working
+
+**Problem:** Custom events not being received by listeners
+
+**Solution:**
+```kotlin
+// Ensure your custom event extends KEvent properly
+class MyCustomEvent(val player: Player) : KEvent() {
+ companion object {
+ private val HANDLERS = HandlerList()
+
+ @JvmStatic
+ fun getHandlerList(): HandlerList = HANDLERS
+ }
+
+ override fun getHandlers(): HandlerList = HANDLERS
+}
+
+// Dispatch the event properly
+fun triggerCustomEvent(player: Player) {
+ val event = MyCustomEvent(player)
+ event.callEvent() // Don't forget this
+}
+
+// Listen for the event
+listen { event ->
+ // Handle custom event
+}
+```
+
+## Command Issues
+
+### Commands Not Registering
+
+**Problem:** Commands don't appear in-game or in tab completion
+
+**Troubleshooting:**
+
+1. **Ensure .register() is Called**
+ ```kotlin
+ CommandBuilder("mycommand")
+ .description("My command")
+ .execute { sender, args ->
+ sender.sendMessage("Hello!")
+ }
+ .register() // Must call this!
+ ```
+
+2. **Check Command Name Conflicts**
+ ```kotlin
+ // If another plugin uses the same command name
+ CommandBuilder("mycommand")
+ .aliases("myalias", "mycmd") // Use aliases instead
+ .register()
+ ```
+
+3. **Verify Permission Setup**
+ ```kotlin
+ CommandBuilder("admin")
+ .permission("myplugin.admin") // Make sure this permission exists
+ .execute { sender, args ->
+ // Command logic
+ }
+ .register()
+ ```
+
+### Argument Parsing Errors
+
+**Problem:** Arguments not being parsed correctly
+
+**Solutions:**
+
+1. **Check Argument Order**
+ ```kotlin
+ CommandBuilder("teleport")
+ .argument(playerArgument("target")) // Required arguments first
+ .argument(worldArgument("world", optional = true)) // Optional arguments last
+ .execute { sender, args ->
+ val target = args.getPlayer("target")
+ val world = args.getWorldOrDefault("world", sender.world)
+ }
+ .register()
+ ```
+
+2. **Handle Missing Arguments**
+ ```kotlin
+ CommandBuilder("give")
+ .argument(materialArgument("item"))
+ .argument(intArgument("amount", optional = true))
+ .execute { sender, args ->
+ val material = args.getMaterial("item")
+ val amount = args.getIntOrDefault("amount", 1) // Use default if not provided
+ }
+ .register()
+ ```
+
+3. **Validate Argument Values**
+ ```kotlin
+ CommandBuilder("damage")
+ .argument(intArgument("amount", min = 1, max = 100)) // Add constraints
+ .execute { sender, args ->
+ val amount = args.getInt("amount") // Will be between 1-100
+ }
+ .register()
+ ```
+
+## GUI Issues
+
+### GUI Not Opening
+
+**Problem:** Inventory GUI doesn't open when expected
+
+**Troubleshooting:**
+
+1. **Check Player State**
+ ```kotlin
+ fun openGUI(player: Player) {
+ if (!player.isOnline) return // Player must be online
+
+ // Close existing inventory first
+ player.closeInventory()
+
+ val gui = simpleGUI("My GUI", 27) {
+ // GUI content
+ }
+
+ player.openInventory(gui)
+ }
+ ```
+
+2. **Verify GUI Size**
+ ```kotlin
+ // Size must be multiple of 9, max 54
+ val gui = simpleGUI("My GUI", 27) { // Valid: 9, 18, 27, 36, 45, 54
+ // Content
+ }
+ ```
+
+3. **Check for Errors in GUI Building**
+ ```kotlin
+ val gui = simpleGUI("My GUI", 27) {
+ try {
+ item(0, ItemBuilder(Material.STONE).name("Test").build()) {
+ // Click handler
+ }
+ } catch (e: Exception) {
+ plugin.logger.error("Error building GUI", e)
+ }
+ }
+ ```
+
+### Click Events Not Working
+
+**Problem:** GUI click handlers not responding
+
+**Solutions:**
+
+1. **Ensure Click Handler is Set**
+ ```kotlin
+ val gui = simpleGUI("My GUI", 27) {
+ item(0, ItemBuilder(Material.STONE).name("Click me").build()) {
+ // This block is the click handler - don't leave empty
+ player.sendMessage("Clicked!")
+ }
+ }
+ ```
+
+2. **Check for Event Cancellation**
+ ```kotlin
+ // If you have global inventory click handlers, they might interfere
+ listen { event ->
+ if (event.view.title.equals("My GUI", ignoreCase = true)) {
+ // Don't cancel KPaper GUI events
+ return@listen
+ }
+ event.isCancelled = true
+ }
+ ```
+
+3. **Verify Slot Numbers**
+ ```kotlin
+ val gui = simpleGUI("My GUI", 27) {
+ // Slots are 0-indexed: 0-26 for size 27
+ item(26, item) { /* handler */ } // Last slot in 27-slot GUI
+ // item(27, item) { } // This would be invalid!
+ }
+ ```
+
+## Performance Issues
+
+### High Memory Usage
+
+**Problem:** Plugin consuming excessive memory
+
+**Solutions:**
+
+1. **Clean Up Event Listeners**
+ ```kotlin
+ class MyFeature : EventHandler() {
+ override fun load() {
+ listen { /* handler */ }
+ }
+
+ override fun unload() {
+ // Proper cleanup - KPaper handles this automatically
+ // but you can add custom cleanup here
+ }
+ }
+ ```
+
+2. **Avoid Memory Leaks in GUIs**
+ ```kotlin
+ // Don't store player references in static collections
+ class MyGUI {
+ companion object {
+ private val playerData = mutableMapOf() // Bad - can leak
+ }
+ }
+
+ // Instead, use plugin-scoped collections
+ class MyPlugin : KPlugin() {
+ private val playerData = mutableMapOf() // Good
+
+ override fun shutdown() {
+ playerData.clear() // Clean up on shutdown
+ }
+ }
+ ```
+
+3. **Optimize Data Caching**
+ ```kotlin
+ class DataCache(
+ private val maxSize: Int = 1000,
+ private val expireAfter: Duration = Duration.ofMinutes(30)
+ ) {
+ private val cache = LinkedHashMap>()
+
+ fun get(key: K, loader: () -> V): V {
+ // Implement LRU cache with expiration
+ cleanupExpired()
+
+ val entry = cache[key]
+ if (entry != null && !entry.isExpired()) {
+ return entry.value
+ }
+
+ val value = loader()
+ cache[key] = CacheEntry(value, System.currentTimeMillis())
+
+ // Evict old entries
+ while (cache.size > maxSize) {
+ cache.remove(cache.keys.first())
+ }
+
+ return value
+ }
+ }
+ ```
+
+### Slow GUI Performance
+
+**Problem:** GUIs take long time to open or update
+
+**Solutions:**
+
+1. **Use Async Loading**
+ ```kotlin
+ fun openShop(player: Player) {
+ // Show loading GUI first
+ val loadingGUI = simpleGUI("Loading...", 9) {
+ item(4, ItemBuilder(Material.CLOCK).name("&eLoading...").build())
+ }
+ player.openInventory(loadingGUI)
+
+ // Load data asynchronously
+ launch {
+ val items = loadShopItems() // Heavy operation
+
+ withContext(Dispatchers.Main) {
+ // Update GUI on main thread
+ openShopWithItems(player, items)
+ }
+ }
+ }
+ ```
+
+2. **Cache GUI Elements**
+ ```kotlin
+ object GUICache {
+ private val itemCache = mutableMapOf()
+
+ fun getCachedItem(key: String, builder: () -> ItemStack): ItemStack {
+ return itemCache.computeIfAbsent(key) { builder() }
+ }
+ }
+
+ // Usage
+ val item = GUICache.getCachedItem("shop_sword") {
+ ItemBuilder(Material.DIAMOND_SWORD)
+ .name("&cWeapons")
+ .lore("&7Click to browse weapons!")
+ .build()
+ }
+ ```
+
+## Database and File Issues
+
+### Configuration Not Loading
+
+**Problem:** Configuration values returning defaults
+
+**Solutions:**
+
+1. **Check File Location**
+ ```kotlin
+ override fun startup() {
+ // Ensure config file exists
+ if (!File(dataFolder, "config.yml").exists()) {
+ saveDefaultConfig() // Create default config
+ }
+
+ val config = loadConfiguration()
+ }
+ ```
+
+2. **Verify Configuration Class**
+ ```kotlin
+ // Ensure your config class has proper defaults
+ data class MyConfig(
+ val database: DatabaseConfig = DatabaseConfig(),
+ val messages: Messages = Messages()
+ ) {
+ data class DatabaseConfig(
+ val host: String = "localhost",
+ val port: Int = 3306,
+ val database: String = "minecraft"
+ )
+ }
+ ```
+
+3. **Handle Configuration Errors**
+ ```kotlin
+ override fun startup() {
+ try {
+ val config = loadConfiguration()
+ // Use config
+ } catch (e: Exception) {
+ logger.error("Failed to load configuration", e)
+ // Use defaults or disable plugin
+ server.pluginManager.disablePlugin(this)
+ return
+ }
+ }
+ ```
+
+### Async Operation Errors
+
+**Problem:** Database operations causing server lag or errors
+
+**Solutions:**
+
+1. **Use Proper Coroutine Context**
+ ```kotlin
+ class DatabaseManager {
+ suspend fun savePlayerData(data: PlayerData) {
+ withContext(Dispatchers.IO) { // Use IO dispatcher for database operations
+ // Database save operation
+ database.save(data)
+ }
+ }
+
+ fun savePlayerDataAsync(data: PlayerData) {
+ launch { // This uses the plugin's coroutine scope
+ savePlayerData(data)
+ }
+ }
+ }
+ ```
+
+2. **Handle Database Connection Errors**
+ ```kotlin
+ class DatabaseManager {
+ private var isConnected = false
+
+ suspend fun ensureConnection() {
+ if (!isConnected) {
+ try {
+ database.connect()
+ isConnected = true
+ } catch (e: SQLException) {
+ logger.error("Failed to connect to database", e)
+ throw e
+ }
+ }
+ }
+
+ suspend fun saveData(data: Any) {
+ try {
+ ensureConnection()
+ database.save(data)
+ } catch (e: SQLException) {
+ logger.error("Failed to save data", e)
+ // Implement retry logic or fallback
+ }
+ }
+ }
+ ```
+
+## Common Error Messages
+
+### "Failed to bind to server"
+
+**Cause:** Another instance of the server is already running
+
+**Solution:** Stop the existing server or use a different port
+
+### "Plugin X tried to register command Y but it is already registered"
+
+**Cause:** Command name conflict with another plugin
+
+**Solution:** Use aliases or different command names
+```kotlin
+CommandBuilder("mycommand")
+ .aliases("mycmd", "mc") // Use aliases instead of conflicting name
+ .register()
+```
+
+### "Attempted to place a tile entity where there was no entity tile!"
+
+**Cause:** Trying to access block state on wrong block type
+
+**Solution:** Check block type before accessing state
+```kotlin
+val block = location.block
+if (block.type == Material.CHEST) {
+ val chest = block.state as Chest
+ // Safe to use chest
+}
+```
+
+### "Server thread/WARN]: Plugin attempted to register listener after being disabled"
+
+**Cause:** Trying to register events after plugin shutdown
+
+**Solution:** Check plugin state before registering events
+```kotlin
+override fun startup() {
+ if (!isEnabled) return // Don't register if plugin is disabled
+
+ listen { /* handler */ }
+}
+```
+
+## Getting Help
+
+### Debug Mode
+
+Enable debug logging to get more information:
+
+```kotlin
+class MyPlugin : KPlugin() {
+ override fun startup() {
+ // Enable debug logging
+ logger.level = Level.DEBUG
+
+ logDebug("Plugin started in debug mode")
+ }
+}
+```
+
+### Collecting Information
+
+When reporting issues, include:
+
+1. **KPaper Version**
+2. **Paper Version**
+3. **Java Version**
+4. **Full Error Stack Trace**
+5. **Minimal Reproducible Example**
+6. **Server Log Excerpt**
+
+### Community Support
+
+- **GitHub Issues**: [https://github.com/ModLabsCC/KPaper/issues](https://github.com/ModLabsCC/KPaper/issues)
+- **Discord**: Join the ModLabs Discord server
+- **Documentation**: Check this documentation for examples
+
+### Creating Bug Reports
+
+Use this template for bug reports:
+
+```markdown
+**KPaper Version:** 2025.1.1.1234
+**Paper Version:** 1.21.6-R0.1-SNAPSHOT
+**Java Version:** 21.0.1
+
+**Description:**
+Brief description of the issue
+
+**Steps to Reproduce:**
+1. Step one
+2. Step two
+3. Expected vs actual behavior
+
+**Code Sample:**
+```kotlin
+// Minimal code that reproduces the issue
+```
+
+**Error Log:**
+```
+// Full stack trace here
+```
+```
+
+Remember: Most issues are configuration-related or due to conflicts with other plugins. Always test with minimal setup first!
\ No newline at end of file
From b84351705642b5c93afb99efc56c9f4b20bf72d7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 29 Jul 2025 11:53:14 +0000
Subject: [PATCH 4/4] Fix CommandBuilder documentation to match actual KPaper
API
Co-authored-by: CoasterFreakDE <28011628+CoasterFreakDE@users.noreply.github.com>
---
README.md | 106 ++-
docs/api/commands.md | 1266 +++++++++++++++++---------
docs/getting-started/first-plugin.md | 88 +-
docs/getting-started/migration.md | 147 ++-
4 files changed, 1056 insertions(+), 551 deletions(-)
diff --git a/README.md b/README.md
index 263c880..00ea6e6 100644
--- a/README.md
+++ b/README.md
@@ -74,15 +74,24 @@ class MyPlugin : KPlugin() {
player.inventory.addItem(welcomeItem)
}
- // ⌨️ Commands with fluent API
- CommandBuilder("shop")
- .description("Open the server shop")
- .permission("shop.use")
- .playerOnly(true)
- .execute { sender, _ ->
- openShopGUI(sender as Player)
- }
- .register()
+ // ⌨️ Commands with KPaper integration
+ class ShopCommand : CommandBuilder {
+ override val description = "Open the server shop"
+
+ override fun register() = Commands.literal("shop")
+ .requires { it.sender.hasPermission("shop.use") && it.sender is Player }
+ .executes { ctx ->
+ openShopGUI(ctx.source.sender as Player)
+ Command.SINGLE_SUCCESS
+ }
+ .build()
+ }
+
+ // Register in lifecycle event
+ manager.registerEventHandler(LifecycleEvents.COMMANDS) { event ->
+ val shopCommand = ShopCommand()
+ event.registrar().register(shopCommand.register(), shopCommand.description)
+ }
}
private fun openShopGUI(player: Player) {
@@ -236,39 +245,58 @@ class ShopGUI(private val category: ShopCategory) : KGUI() {
Advanced Commands
```kotlin
-// Complex command with sub-commands and validation
-CommandBuilder("economy")
- .description("Economy management commands")
- .permission("economy.admin")
-
- // /economy balance [player]
- .subcommand("balance")
- .argument(playerArgument("target", optional = true))
- .execute { sender, args ->
- val target = args.getPlayerOrSelf("target", sender)
- val balance = economyAPI.getBalance(target)
- sender.sendMessage("${target.name}'s balance: $${balance}")
- }
+// Complex command with sub-commands and validation
+class EconomyCommand : CommandBuilder {
+ override val description = "Economy management commands"
- // /economy pay
- .subcommand("pay")
- .argument(playerArgument("target"))
- .argument(doubleArgument("amount", min = 0.01))
- .playerOnly(true)
- .execute { sender, args ->
- val player = sender as Player
- val target = args.getPlayer("target")
- val amount = args.getDouble("amount")
+ override fun register() = Commands.literal("economy")
+ .requires { it.sender.hasPermission("economy.admin") }
- if (economyAPI.getBalance(player) < amount) {
- player.sendMessage("&cInsufficient funds!")
- return@execute
- }
+ // /economy balance [player]
+ .then(Commands.literal("balance")
+ .executes { ctx ->
+ val sender = ctx.source.sender as Player
+ val balance = economyAPI.getBalance(sender)
+ sender.sendMessage("Your balance: $$balance")
+ Command.SINGLE_SUCCESS
+ }
+ .then(Commands.argument("target", ArgumentTypes.player())
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val balance = economyAPI.getBalance(target)
+ sender.sendMessage("${target.name}'s balance: $$balance")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
- economyAPI.transfer(player, target, amount)
- player.sendMessage("&aSent $${amount} to ${target.name}")
- target.sendMessage("&aReceived $${amount} from ${player.name}")
- }
+ // /economy pay
+ .then(Commands.literal("pay")
+ .requires { it.sender is Player }
+ .then(Commands.argument("target", ArgumentTypes.player())
+ .then(Commands.argument("amount", DoubleArgumentType.doubleArg(0.01))
+ .executes { ctx ->
+ val player = ctx.source.sender as Player
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val amount = DoubleArgumentType.getDouble(ctx, "amount")
+
+ if (economyAPI.getBalance(player) < amount) {
+ player.sendMessage("§cInsufficient funds!")
+ return@executes Command.SINGLE_SUCCESS
+ }
+
+ economyAPI.transfer(player, target, amount)
+ player.sendMessage("§aSent $$amount to ${target.name}")
+ target.sendMessage("§aReceived $$amount from ${player.name}")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ )
+ .build()
+}
+```
// /economy set
.subcommand("set")
diff --git a/docs/api/commands.md b/docs/api/commands.md
index 3f45d27..8432e3f 100644
--- a/docs/api/commands.md
+++ b/docs/api/commands.md
@@ -1,25 +1,33 @@
# Command Framework
-KPaper's command framework provides a powerful, fluent API for creating commands with automatic argument parsing, validation, and sub-command support.
+KPaper's command framework provides integration with Paper's Brigadier-based command system through the `CommandBuilder` interface, allowing you to create powerful commands with argument parsing, validation, and sub-command support.
## Basic Command Creation
### Simple Commands
-The easiest way to create commands is using the `CommandBuilder`:
+Commands in KPaper are created by implementing the `CommandBuilder` interface:
```kotlin
import cc.modlabs.kpaper.command.CommandBuilder
+import com.mojang.brigadier.Command
+import com.mojang.brigadier.tree.LiteralCommandNode
+import io.papermc.paper.command.brigadier.CommandSourceStack
+import io.papermc.paper.command.brigadier.Commands
+import org.bukkit.entity.Player
-class MyPlugin : KPlugin() {
- override fun startup() {
- // Basic command
- CommandBuilder("hello")
- .description("Say hello to the world")
- .execute { sender, args ->
+class HelloCommand : CommandBuilder {
+
+ override val description: String = "Say hello to the world"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("hello")
+ .executes { ctx ->
+ val sender = ctx.source.sender
sender.sendMessage("Hello, World!")
+ Command.SINGLE_SUCCESS
}
- .register()
+ .build()
}
}
```
@@ -27,312 +35,568 @@ class MyPlugin : KPlugin() {
### Command with Arguments
```kotlin
-// Command with string argument
-CommandBuilder("greet")
- .description("Greet a specific player")
- .argument(stringArgument("name"))
- .execute { sender, args ->
- val name = args.getString("name")
- sender.sendMessage("Hello, $name!")
- }
- .register()
-
-// Command with player argument
-CommandBuilder("teleport")
- .description("Teleport to a player")
- .argument(playerArgument("target"))
- .playerOnly(true)
- .execute { sender, args ->
- val player = sender as Player
- val target = args.getPlayer("target")
-
- player.teleport(target.location)
- player.sendMessage("Teleported to ${target.name}!")
+import com.mojang.brigadier.arguments.StringArgumentType
+import com.mojang.brigadier.arguments.ArgumentType
+import io.papermc.paper.command.brigadier.argument.ArgumentTypes
+
+class GreetCommand : CommandBuilder {
+
+ override val description: String = "Greet a specific player"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("greet")
+ .then(Commands.argument("name", StringArgumentType.word())
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val name = StringArgumentType.getString(ctx, "name")
+ sender.sendMessage("Hello, $name!")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ .build()
+ }
+}
+
+// Command with player argument
+class TeleportCommand : CommandBuilder {
+
+ override val description: String = "Teleport to a player"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("teleport")
+ .then(Commands.argument("target", ArgumentTypes.player())
+ .requires { it.sender is Player }
+ .executes { ctx ->
+ val player = ctx.source.sender as Player
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+
+ player.teleport(target.location)
+ player.sendMessage("Teleported to ${target.name}!")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ .build()
}
- .register()
+}
```
## Argument Types
### Built-in Argument Types
-KPaper provides many built-in argument types with automatic validation:
+KPaper leverages Paper's ArgumentTypes for command arguments with automatic validation:
```kotlin
-import cc.modlabs.kpaper.command.arguments.*
+import com.mojang.brigadier.arguments.*
+import io.papermc.paper.command.brigadier.argument.ArgumentTypes
-CommandBuilder("admin")
- .description("Admin command with various arguments")
-
- // String arguments
- .argument(stringArgument("message"))
-
- // Player arguments (with online validation)
- .argument(playerArgument("target"))
-
- // Numeric arguments
- .argument(intArgument("amount", min = 1, max = 100))
- .argument(doubleArgument("multiplier", min = 0.1, max = 10.0))
-
- // Boolean arguments
- .argument(booleanArgument("force", optional = true))
+class AdminCommand : CommandBuilder {
- // World arguments
- .argument(worldArgument("world"))
+ override val description: String = "Admin command with various arguments"
- // Material arguments
- .argument(materialArgument("item"))
-
- // Location arguments
- .argument(locationArgument("position"))
-
- .execute { sender, args ->
- val message = args.getString("message")
- val target = args.getPlayer("target")
- val amount = args.getInt("amount")
- val multiplier = args.getDouble("multiplier")
- val force = args.getBoolean("force") ?: false
- val world = args.getWorld("world")
- val material = args.getMaterial("item")
- val location = args.getLocation("position")
-
- // Use the arguments...
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("admin")
+ .then(Commands.argument("message", StringArgumentType.greedyString())
+ .then(Commands.argument("target", ArgumentTypes.player())
+ .then(Commands.argument("amount", IntegerArgumentType.integer(1, 100))
+ .then(Commands.argument("multiplier", DoubleArgumentType.doubleArg(0.1, 10.0))
+ .then(Commands.argument("force", BoolArgumentType.bool())
+ .then(Commands.argument("world", ArgumentTypes.world())
+ .then(Commands.argument("item", ArgumentTypes.itemStack())
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val message = StringArgumentType.getString(ctx, "message")
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val amount = IntegerArgumentType.getInteger(ctx, "amount")
+ val multiplier = DoubleArgumentType.getDouble(ctx, "multiplier")
+ val force = BoolArgumentType.getBool(ctx, "force")
+ val world = ArgumentTypes.world().parse(ctx.input).resolve(ctx.source)
+ val itemStack = ArgumentTypes.itemStack().parse(ctx.input).resolve(ctx.source)
+
+ // Use the arguments...
+ sender.sendMessage("Processing admin command with all arguments")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ .build()
}
- .register()
+}
```
-### Optional Arguments
+### Optional Arguments with Defaults
```kotlin
-CommandBuilder("give")
- .description("Give items to players")
- .argument(playerArgument("target", optional = true)) // Defaults to sender
- .argument(materialArgument("item"))
- .argument(intArgument("amount", optional = true, default = 1))
- .execute { sender, args ->
- val target = args.getPlayerOrSelf("target", sender)
- val material = args.getMaterial("item")
- val amount = args.getIntOrDefault("amount", 1)
-
- val item = ItemStack(material, amount)
- target.inventory.addItem(item)
-
- sender.sendMessage("Gave ${amount}x ${material.name} to ${target.name}")
+class GiveCommand : CommandBuilder {
+
+ override val description: String = "Give items to players"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("give")
+ .then(Commands.argument("item", ArgumentTypes.itemStack())
+ .executes { ctx ->
+ // Default: give to sender, amount 1
+ val sender = ctx.source.sender as Player
+ val itemStack = ArgumentTypes.itemStack().parse(ctx.input).resolve(ctx.source)
+
+ sender.inventory.addItem(itemStack)
+ sender.sendMessage("Gave ${itemStack.amount}x ${itemStack.type.name} to yourself")
+ Command.SINGLE_SUCCESS
+ }
+ .then(Commands.argument("target", ArgumentTypes.player())
+ .executes { ctx ->
+ // Give to specific player, amount 1
+ val sender = ctx.source.sender
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val itemStack = ArgumentTypes.itemStack().parse(ctx.input).resolve(ctx.source)
+
+ target.inventory.addItem(itemStack)
+ sender.sendMessage("Gave ${itemStack.amount}x ${itemStack.type.name} to ${target.name}")
+ Command.SINGLE_SUCCESS
+ }
+ .then(Commands.argument("amount", IntegerArgumentType.integer(1))
+ .executes { ctx ->
+ // Give specific amount to specific player
+ val sender = ctx.source.sender
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val baseItem = ArgumentTypes.itemStack().parse(ctx.input).resolve(ctx.source)
+ val amount = IntegerArgumentType.getInteger(ctx, "amount")
+
+ val itemStack = baseItem.clone()
+ itemStack.amount = amount
+
+ target.inventory.addItem(itemStack)
+ sender.sendMessage("Gave ${amount}x ${itemStack.type.name} to ${target.name}")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ )
+ .build()
}
- .register()
+}
```
-### Custom Argument Types
+## Sub-Commands
-Create your own argument types for complex validation:
+### Basic Sub-Commands
```kotlin
-// Custom argument for economy amounts
-fun economyAmountArgument(name: String, min: Double = 0.01): ArgumentType {
- return object : ArgumentType {
- override val name = name
- override val optional = false
-
- override fun parse(input: String): Double? {
- val amount = input.toDoubleOrNull() ?: return null
- return if (amount >= min) amount else null
- }
-
- override fun complete(input: String): List {
- return listOf("10", "100", "1000").filter { it.startsWith(input, ignoreCase = true) }
- }
-
- override fun getUsage(): String = ""
- override fun getDescription(): String = "Amount of money (minimum $min)"
+class EconomyCommand : CommandBuilder {
+
+ override val description: String = "Economy management commands"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("economy")
+ // /economy balance [player]
+ .then(Commands.literal("balance")
+ .executes { ctx ->
+ val sender = ctx.source.sender as Player
+ val balance = economyAPI.getBalance(sender)
+ sender.sendMessage("Your balance: $$balance")
+ Command.SINGLE_SUCCESS
+ }
+ .then(Commands.argument("target", ArgumentTypes.player())
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val balance = economyAPI.getBalance(target)
+ sender.sendMessage("${target.name}'s balance: $$balance")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ // /economy pay
+ .then(Commands.literal("pay")
+ .requires { it.sender is Player }
+ .then(Commands.argument("target", ArgumentTypes.player())
+ .then(Commands.argument("amount", DoubleArgumentType.doubleArg(0.01))
+ .executes { ctx ->
+ val sender = ctx.source.sender as Player
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val amount = DoubleArgumentType.getDouble(ctx, "amount")
+
+ // Payment logic...
+ economyAPI.transfer(sender, target, amount)
+ sender.sendMessage("Paid $$amount to ${target.name}")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ )
+ .build()
}
}
+```
-// Usage
-CommandBuilder("pay")
- .argument(playerArgument("target"))
- .argument(economyAmountArgument("amount", min = 1.0))
- .execute { sender, args ->
- val target = args.getPlayer("target")
- val amount = args.get("amount")!!
-
- // Process payment...
+### Nested Sub-Commands
+
+```kotlin
+class AdminCommand : CommandBuilder {
+
+ override val description: String = "Administration commands"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("admin")
+ .requires { it.sender.hasPermission("admin.use") }
+
+ // /admin player
+ .then(Commands.literal("player")
+
+ // /admin player ban [reason]
+ .then(Commands.literal("ban")
+ .then(Commands.argument("target", ArgumentTypes.player())
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val reason = "Banned by admin"
+
+ target.ban(reason, sender.name)
+ sender.sendMessage("Banned ${target.name} for: $reason")
+ Command.SINGLE_SUCCESS
+ }
+ .then(Commands.argument("reason", StringArgumentType.greedyString())
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val reason = StringArgumentType.getString(ctx, "reason")
+
+ target.ban(reason, sender.name)
+ sender.sendMessage("Banned ${target.name} for: $reason")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ )
+
+ // /admin player unban
+ .then(Commands.literal("unban")
+ .then(Commands.argument("playerName", StringArgumentType.word())
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val playerName = StringArgumentType.getString(ctx, "playerName")
+ val offlinePlayer = sender.server.getOfflinePlayer(playerName)
+
+ offlinePlayer.banList?.pardon(offlinePlayer)
+ sender.sendMessage("Unbanned $playerName")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ )
+
+ // /admin server
+ .then(Commands.literal("server")
+
+ // /admin server restart [delay]
+ .then(Commands.literal("restart")
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val delay = 10
+ scheduleServerRestart(delay)
+ sender.sendMessage("Server restart scheduled in $delay seconds")
+ Command.SINGLE_SUCCESS
+ }
+ .then(Commands.argument("delay", IntegerArgumentType.integer(1))
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val delay = IntegerArgumentType.getInteger(ctx, "delay")
+ scheduleServerRestart(delay)
+ sender.sendMessage("Server restart scheduled in $delay seconds")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ )
+ .build()
}
- .register()
+}
```
-## Sub-Commands
+## Command Features
-### Basic Sub-Commands
+### Permissions and Requirements
```kotlin
-CommandBuilder("economy")
- .description("Economy management commands")
-
- // /economy balance [player]
- .subcommand("balance")
- .description("Check player balance")
- .argument(playerArgument("target", optional = true))
- .execute { sender, args ->
- val target = args.getPlayerOrSelf("target", sender)
- val balance = economyAPI.getBalance(target)
- sender.sendMessage("${target.name}'s balance: $${balance}")
- }
-
- // /economy pay
- .subcommand("pay")
- .description("Pay another player")
- .argument(playerArgument("target"))
- .argument(economyAmountArgument("amount"))
- .playerOnly(true)
- .execute { sender, args ->
- // Payment logic...
- }
-
- .register()
+class TeleportCommand : CommandBuilder {
+
+ override val description: String = "Teleportation commands"
+ override val aliases: List = listOf("tp", "tele")
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("teleport")
+ .requires { it.sender.hasPermission("tp.use") && it.sender is Player }
+
+ .then(Commands.literal("player")
+ .requires { it.sender.hasPermission("tp.player") }
+ .then(Commands.argument("target", ArgumentTypes.player())
+ .executes { ctx ->
+ val player = ctx.source.sender as Player
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+
+ player.teleport(target.location)
+ player.sendMessage("Teleported to ${target.name}!")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+
+ .then(Commands.literal("location")
+ .requires {
+ it.sender.hasPermission("tp.location") &&
+ it.sender is Player &&
+ it.sender.hasPermission("tp.admin")
+ }
+ .then(Commands.argument("x", DoubleArgumentType.doubleArg())
+ .then(Commands.argument("y", DoubleArgumentType.doubleArg())
+ .then(Commands.argument("z", DoubleArgumentType.doubleArg())
+ .executes { ctx ->
+ val player = ctx.source.sender as Player
+ val x = DoubleArgumentType.getDouble(ctx, "x")
+ val y = DoubleArgumentType.getDouble(ctx, "y")
+ val z = DoubleArgumentType.getDouble(ctx, "z")
+
+ val location = Location(player.world, x, y, z)
+ player.teleport(location)
+ player.sendMessage("Teleported to $x, $y, $z")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ )
+ )
+ .build()
+ }
+}
```
-### Nested Sub-Commands
+### Command Aliases
+
+Command aliases are handled through the `aliases` property:
```kotlin
-CommandBuilder("admin")
- .description("Administration commands")
- .permission("admin.use")
-
- // /admin player
- .subcommand("player")
- .description("Player management")
-
- // /admin player ban [reason]
- .subcommand("ban")
- .argument(playerArgument("target"))
- .argument(stringArgument("reason", optional = true))
- .execute { sender, args ->
- val target = args.getPlayer("target")
- val reason = args.getStringOrDefault("reason", "Banned by admin")
-
- target.ban(reason, sender.name)
- sender.sendMessage("Banned ${target.name} for: $reason")
- }
-
- // /admin player unban
- .subcommand("unban")
- .argument(stringArgument("playerName"))
- .execute { sender, args ->
- val playerName = args.getString("playerName")
- val offlinePlayer = server.getOfflinePlayer(playerName)
-
- offlinePlayer.banList?.pardon(offlinePlayer)
- sender.sendMessage("Unbanned $playerName")
- }
+class GameModeCommand : CommandBuilder {
- // /admin server
- .subcommand("server")
- .description("Server management")
-
- // /admin server restart [delay]
- .subcommand("restart")
- .argument(intArgument("delay", optional = true, default = 10))
- .execute { sender, args ->
- val delay = args.getIntOrDefault("delay", 10)
- scheduleServerRestart(delay)
- sender.sendMessage("Server restart scheduled in ${delay} seconds")
- }
+ override val description: String = "Change game mode"
+ override val aliases: List = listOf("gm", "mode")
- .register()
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("gamemode")
+ .then(Commands.argument("mode", StringArgumentType.word())
+ .suggests { _, builder ->
+ listOf("survival", "creative", "adventure", "spectator", "s", "c", "a", "sp", "0", "1", "2", "3")
+ .forEach { builder.suggest(it) }
+ builder.buildFuture()
+ }
+ .executes { ctx ->
+ val sender = ctx.source.sender as Player
+ val modeInput = StringArgumentType.getString(ctx, "mode").lowercase()
+
+ val mode = when (modeInput) {
+ "survival", "s", "0" -> GameMode.SURVIVAL
+ "creative", "c", "1" -> GameMode.CREATIVE
+ "adventure", "a", "2" -> GameMode.ADVENTURE
+ "spectator", "sp", "3" -> GameMode.SPECTATOR
+ else -> {
+ sender.sendMessage("Invalid game mode!")
+ return@executes Command.SINGLE_SUCCESS
+ }
+ }
+
+ sender.gameMode = mode
+ sender.sendMessage("Set your game mode to ${mode.name}")
+ Command.SINGLE_SUCCESS
+ }
+ .then(Commands.argument("target", ArgumentTypes.player())
+ .requires { it.sender.hasPermission("gamemode.others") }
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val target = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val modeInput = StringArgumentType.getString(ctx, "mode").lowercase()
+
+ val mode = when (modeInput) {
+ "survival", "s", "0" -> GameMode.SURVIVAL
+ "creative", "c", "1" -> GameMode.CREATIVE
+ "adventure", "a", "2" -> GameMode.ADVENTURE
+ "spectator", "sp", "3" -> GameMode.SPECTATOR
+ else -> {
+ sender.sendMessage("Invalid game mode!")
+ return@executes Command.SINGLE_SUCCESS
+ }
+ }
+
+ target.gameMode = mode
+ sender.sendMessage("Set ${target.name}'s game mode to ${mode.name}")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ .build()
+ }
+}
```
-## Command Features
+### Tab Completion
-### Permissions and Requirements
+Tab completion is provided through Brigadier's suggestion system:
```kotlin
-CommandBuilder("teleport")
- .description("Teleportation commands")
- .permission("tp.use") // Base permission
- .playerOnly(true) // Only players can use
+class TeamCommand : CommandBuilder {
+
+ override val description: String = "Team management commands"
- .subcommand("player")
- .permission("tp.player") // Additional permission for sub-command
- .argument(playerArgument("target"))
- .execute { sender, args ->
- // Teleport logic...
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("team")
+ .then(Commands.argument("team", StringArgumentType.word())
+ .suggests { _, builder ->
+ getAvailableTeams().forEach { team ->
+ builder.suggest(team)
+ }
+ builder.buildFuture()
+ }
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val teamName = StringArgumentType.getString(ctx, "team")
+
+ if (!isValidTeam(teamName)) {
+ sender.sendMessage("Invalid team: $teamName")
+ return@executes Command.SINGLE_SUCCESS
+ }
+
+ // Team command logic...
+ sender.sendMessage("Selected team: $teamName")
+ Command.SINGLE_SUCCESS
+ }
+ )
+ .build()
}
- .subcommand("location")
- .permission("tp.location")
- .requirement { sender ->
- sender is Player && sender.hasPermission("tp.admin")
- } // Custom requirement check
- .argument(locationArgument("position"))
- .execute { sender, args ->
- // Teleport to location...
+ private fun getAvailableTeams(): List {
+ return listOf("red", "blue", "green", "yellow")
}
- .register()
+ private fun isValidTeam(teamName: String): Boolean {
+ return getAvailableTeams().contains(teamName.lowercase())
+ }
+}
```
-### Command Aliases
+Built-in argument types automatically provide appropriate tab completion:
```kotlin
-CommandBuilder("gamemode")
- .description("Change game mode")
- .aliases("gm", "mode") // Alternative command names
- .argument(stringArgument("mode"))
- .argument(playerArgument("target", optional = true))
- .execute { sender, args ->
- val mode = when (args.getString("mode").lowercase()) {
- "survival", "s", "0" -> GameMode.SURVIVAL
- "creative", "c", "1" -> GameMode.CREATIVE
- "adventure", "a", "2" -> GameMode.ADVENTURE
- "spectator", "sp", "3" -> GameMode.SPECTATOR
- else -> {
- sender.sendMessage("Invalid game mode!")
- return@execute
- }
- }
+class GiveCommand : CommandBuilder {
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("give")
+ .then(Commands.argument("target", ArgumentTypes.player()) // Auto-completes player names
+ .then(Commands.argument("item", ArgumentTypes.itemStack()) // Auto-completes items
+ .executes { ctx ->
+ // Command logic...
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ .build()
+ }
+}
+```
+
+## Command Registration
+
+### Using Paper's Lifecycle System
+
+Commands should be registered during the `COMMANDS` lifecycle event:
+
+```kotlin
+// Command Bootstrapper
+import io.papermc.paper.plugin.bootstrap.BootstrapContext
+import io.papermc.paper.plugin.bootstrap.PluginBootstrap
+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents
+
+class CommandBootstrapper : PluginBootstrap {
+
+ override fun bootstrap(context: BootstrapContext) {
+ val manager = context.lifecycleManager
- val target = args.getPlayerOrSelf("target", sender)
- target.gameMode = mode
- sender.sendMessage("Set ${target.name}'s game mode to ${mode.name}")
+ manager.registerEventHandler(LifecycleEvents.COMMANDS) { event ->
+ // Register individual commands
+ val helloCommand = HelloCommand()
+ event.registrar().register(
+ helloCommand.register(),
+ helloCommand.description,
+ helloCommand.aliases
+ )
+
+ val teleportCommand = TeleportCommand()
+ event.registrar().register(
+ teleportCommand.register(),
+ teleportCommand.description,
+ teleportCommand.aliases
+ )
+ }
}
- .register()
+}
```
-### Tab Completion
+### Bulk Registration with Reflection
-KPaper automatically provides tab completion for built-in argument types:
+For larger projects, you can use reflection to auto-register commands:
```kotlin
-// Tab completion is automatic for:
-CommandBuilder("give")
- .argument(playerArgument("target")) // Completes online player names
- .argument(materialArgument("item")) // Completes material names
- .argument(worldArgument("world")) // Completes world names
- .register()
-
-// Custom completion
-CommandBuilder("team")
- .argument(object : ArgumentType {
- override val name = "team"
- override val optional = false
+import com.google.common.reflect.ClassPath
+import kotlin.reflect.KClass
+import kotlin.reflect.full.primaryConstructor
+
+object CommandRegistration {
+
+ fun registerCommands(commands: Commands) {
+ val commandClasses = loadCommandClasses("com.yourplugin.commands")
- override fun parse(input: String): String? {
- return if (isValidTeam(input)) input else null
+ commandClasses.forEach { clazz ->
+ try {
+ val command = clazz.primaryConstructor?.call() as CommandBuilder
+
+ commands.register(
+ command.register(),
+ command.description,
+ command.aliases
+ )
+
+ logger.info("Registered command: ${clazz.simpleName}")
+ } catch (e: Exception) {
+ logger.error("Failed to register command: ${clazz.simpleName}", e)
+ }
}
- override fun complete(input: String): List {
- return getAvailableTeams().filter {
- it.startsWith(input, ignoreCase = true)
+ logger.info("Registered ${commandClasses.size} commands")
+ }
+
+ private fun loadCommandClasses(packageName: String): List> {
+ val classLoader = this.javaClass.classLoader
+ val allClasses = ClassPath.from(classLoader).allClasses
+ val commandClasses = mutableListOf>()
+
+ for (classInfo in allClasses) {
+ if (classInfo.packageName.startsWith(packageName) && !classInfo.name.contains('$')) {
+ try {
+ val loadedClass = classInfo.load().kotlin
+ if (CommandBuilder::class.isInstance(loadedClass.javaObjectType.getDeclaredConstructor().newInstance())) {
+ commandClasses.add(loadedClass as KClass)
+ }
+ } catch (_: Exception) {
+ // Ignore classes that can't be instantiated
+ }
}
}
- override fun getUsage() = ""
- override fun getDescription() = "Team name"
- })
- .execute { sender, args ->
- // Team command logic...
+ return commandClasses
}
- .register()
+}
```
## Advanced Features
@@ -359,30 +623,36 @@ class CooldownManager {
}
}
-val cooldownManager = CooldownManager()
-
-CommandBuilder("heal")
- .description("Heal yourself")
- .playerOnly(true)
- .requirement { sender ->
- val player = sender as Player
- if (cooldownManager.hasCooldown(player, "heal")) {
- val remaining = cooldownManager.getCooldown(player, "heal") / 1000
- player.sendMessage("&cHeal is on cooldown for ${remaining} seconds!")
- false
- } else {
- true
- }
- }
- .execute { sender, _ ->
- val player = sender as Player
- player.health = player.maxHealth
- player.sendMessage("&aYou have been healed!")
-
- // Set 30 second cooldown
- cooldownManager.setCooldown(player, "heal", 30)
+class HealCommand : CommandBuilder {
+
+ private val cooldownManager = CooldownManager()
+ override val description: String = "Heal yourself"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("heal")
+ .requires { sender ->
+ val player = sender.sender as? Player ?: return@requires false
+
+ if (cooldownManager.hasCooldown(player, "heal")) {
+ val remaining = cooldownManager.getCooldown(player, "heal") / 1000
+ player.sendMessage("§cHeal is on cooldown for ${remaining} seconds!")
+ false
+ } else {
+ true
+ }
+ }
+ .executes { ctx ->
+ val player = ctx.source.sender as Player
+ player.health = player.maxHealth
+ player.sendMessage("§aYou have been healed!")
+
+ // Set 30 second cooldown
+ cooldownManager.setCooldown(player, "heal", 30)
+ Command.SINGLE_SUCCESS
+ }
+ .build()
}
- .register()
+}
```
### Command Usage Tracking
@@ -399,29 +669,47 @@ class CommandStats {
fun getAllStats(): Map = usage.toMap()
}
-val stats = CommandStats()
-
-CommandBuilder("stats")
- .description("View command usage statistics")
- .permission("admin.stats")
- .execute { sender, _ ->
- sender.sendMessage("&6Command Usage Statistics:")
- stats.getAllStats().entries
- .sortedByDescending { it.value }
- .take(10)
- .forEach { (command, count) ->
- sender.sendMessage("&e$command: &f$count uses")
+class StatsCommand : CommandBuilder {
+
+ override val description: String = "View command usage statistics"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("stats")
+ .requires { it.sender.hasPermission("admin.stats") }
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ sender.sendMessage("§6Command Usage Statistics:")
+
+ stats.getAllStats().entries
+ .sortedByDescending { it.value }
+ .take(10)
+ .forEach { (command, count) ->
+ sender.sendMessage("§e$command: §f$count uses")
+ }
+
+ Command.SINGLE_SUCCESS
}
+ .build()
}
- .register()
+}
// Add usage tracking to other commands
-CommandBuilder("teleport")
- .execute { sender, args ->
- stats.recordUsage("teleport")
- // Command logic...
+class TeleportCommandWithStats : CommandBuilder {
+
+ override val description: String = "Teleport to a player"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("teleport")
+ .then(Commands.argument("target", ArgumentTypes.player())
+ .executes { ctx ->
+ stats.recordUsage("teleport")
+ // Command logic...
+ Command.SINGLE_SUCCESS
+ }
+ )
+ .build()
}
- .register()
+}
```
### Dynamic Command Registration
@@ -429,39 +717,67 @@ CommandBuilder("teleport")
```kotlin
class DynamicCommands {
- fun registerWarpCommands(warps: List) {
+ fun registerWarpCommands(commands: Commands, warps: List) {
warps.forEach { warp ->
- CommandBuilder("warp-${warp.name}")
- .description("Teleport to ${warp.name}")
- .playerOnly(true)
- .execute { sender, _ ->
- val player = sender as Player
- player.teleport(warp.location)
- player.sendMessage("Teleported to ${warp.name}!")
+ val warpCommand = object : CommandBuilder {
+ override val description: String = "Teleport to ${warp.name}"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("warp-${warp.name}")
+ .requires { it.sender is Player }
+ .executes { ctx ->
+ val player = ctx.source.sender as Player
+ player.teleport(warp.location)
+ player.sendMessage("Teleported to ${warp.name}!")
+ Command.SINGLE_SUCCESS
+ }
+ .build()
}
- .register()
+ }
+
+ commands.register(
+ warpCommand.register(),
+ warpCommand.description,
+ warpCommand.aliases
+ )
}
}
- fun registerKitCommands(kits: List) {
+ fun registerKitCommands(commands: Commands, kits: List) {
kits.forEach { kit ->
- CommandBuilder("kit-${kit.name}")
- .description("Get the ${kit.name} kit")
- .permission("kits.${kit.name}")
- .playerOnly(true)
- .execute { sender, _ ->
- val player = sender as Player
- giveKit(player, kit)
+ val kitCommand = object : CommandBuilder {
+ override val description: String = "Get the ${kit.name} kit"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("kit-${kit.name}")
+ .requires {
+ it.sender is Player &&
+ it.sender.hasPermission("kits.${kit.name}")
+ }
+ .executes { ctx ->
+ val player = ctx.source.sender as Player
+ giveKit(player, kit)
+ Command.SINGLE_SUCCESS
+ }
+ .build()
}
- .register()
+ }
+
+ commands.register(
+ kitCommand.register(),
+ kitCommand.description,
+ kitCommand.aliases
+ )
}
}
}
-// Register commands dynamically
-val dynamicCommands = DynamicCommands()
-dynamicCommands.registerWarpCommands(loadWarps())
-dynamicCommands.registerKitCommands(loadKits())
+// Register commands dynamically in the lifecycle event
+manager.registerEventHandler(LifecycleEvents.COMMANDS) { event ->
+ val dynamicCommands = DynamicCommands()
+ dynamicCommands.registerWarpCommands(event.registrar(), loadWarps())
+ dynamicCommands.registerKitCommands(event.registrar(), loadKits())
+}
```
## Error Handling
@@ -469,76 +785,110 @@ dynamicCommands.registerKitCommands(loadKits())
### Validation and Error Messages
```kotlin
-CommandBuilder("economy")
- .subcommand("transfer")
- .argument(playerArgument("from"))
- .argument(playerArgument("to"))
- .argument(economyAmountArgument("amount"))
- .execute { sender, args ->
- val from = args.getPlayer("from")
- val to = args.getPlayer("to")
- val amount = args.get("amount")!!
-
- // Validation
- if (from == to) {
- sender.sendMessage("&cCannot transfer money to yourself!")
- return@execute
- }
-
- val fromBalance = economyAPI.getBalance(from)
- if (fromBalance < amount) {
- sender.sendMessage("&c${from.name} doesn't have enough money! (Has: $$fromBalance, Needs: $$amount)")
- return@execute
- }
-
- // Execute transfer
- try {
- economyAPI.transfer(from, to, amount)
- sender.sendMessage("&aTransferred $$amount from ${from.name} to ${to.name}")
-
- // Notify players
- from.sendMessage("&c-$$amount (transferred to ${to.name})")
- to.sendMessage("&a+$$amount (from ${from.name})")
-
- } catch (e: Exception) {
- sender.sendMessage("&cTransfer failed: ${e.message}")
- logger.error("Economy transfer failed", e)
- }
+class EconomyTransferCommand : CommandBuilder {
+
+ override val description: String = "Transfer money between players"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("economy")
+ .then(Commands.literal("transfer")
+ .then(Commands.argument("from", ArgumentTypes.player())
+ .then(Commands.argument("to", ArgumentTypes.player())
+ .then(Commands.argument("amount", DoubleArgumentType.doubleArg(0.01))
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ val from = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val to = ArgumentTypes.player().parse(ctx.input).resolve(ctx.source).singlePlayer
+ val amount = DoubleArgumentType.getDouble(ctx, "amount")
+
+ // Validation
+ if (from == to) {
+ sender.sendMessage("§cCannot transfer money to yourself!")
+ return@executes Command.SINGLE_SUCCESS
+ }
+
+ val fromBalance = economyAPI.getBalance(from)
+ if (fromBalance < amount) {
+ sender.sendMessage("§c${from.name} doesn't have enough money! (Has: $$fromBalance, Needs: $$amount)")
+ return@executes Command.SINGLE_SUCCESS
+ }
+
+ // Execute transfer
+ try {
+ economyAPI.transfer(from, to, amount)
+ sender.sendMessage("§aTransferred $$amount from ${from.name} to ${to.name}")
+
+ // Notify players
+ from.sendMessage("§c-$$amount (transferred to ${to.name})")
+ to.sendMessage("§a+$$amount (from ${from.name})")
+
+ } catch (e: Exception) {
+ sender.sendMessage("§cTransfer failed: ${e.message}")
+ logger.error("Economy transfer failed", e)
+ }
+
+ Command.SINGLE_SUCCESS
+ }
+ )
+ )
+ )
+ )
+ .build()
}
- .register()
+}
```
### Command Exception Handling
```kotlin
-// Global command error handler
-abstract class SafeCommandBuilder(name: String) : CommandBuilder(name) {
+// Create a base class for safe command execution
+abstract class SafeCommandBuilder : CommandBuilder {
- override fun execute(handler: (CommandSender, CommandArguments) -> Unit): CommandBuilder {
- return super.execute { sender, args ->
- try {
- handler(sender, args)
- } catch (e: PlayerNotFoundException) {
- sender.sendMessage("&cPlayer not found: ${e.playerName}")
- } catch (e: InsufficientPermissionException) {
- sender.sendMessage("&cYou don't have permission to do that!")
- } catch (e: InvalidArgumentException) {
- sender.sendMessage("&cInvalid argument: ${e.message}")
- } catch (e: Exception) {
- sender.sendMessage("&cAn error occurred while executing the command.")
- logger.error("Command execution failed", e)
- }
+ protected fun safeExecute(
+ ctx: CommandContext,
+ handler: (CommandContext) -> Int
+ ): Int {
+ return try {
+ handler(ctx)
+ } catch (e: PlayerNotFoundException) {
+ ctx.source.sender.sendMessage("§cPlayer not found: ${e.playerName}")
+ Command.SINGLE_SUCCESS
+ } catch (e: InsufficientPermissionException) {
+ ctx.source.sender.sendMessage("§cYou don't have permission to do that!")
+ Command.SINGLE_SUCCESS
+ } catch (e: InvalidArgumentException) {
+ ctx.source.sender.sendMessage("§cInvalid argument: ${e.message}")
+ Command.SINGLE_SUCCESS
+ } catch (e: Exception) {
+ ctx.source.sender.sendMessage("§cAn error occurred while executing the command.")
+ logger.error("Command execution failed", e)
+ Command.SINGLE_SUCCESS
}
}
}
// Usage
-SafeCommandBuilder("risky-command")
- .execute { sender, args ->
- // Code that might throw exceptions
- performRiskyOperation()
+class RiskyCommand : SafeCommandBuilder() {
+
+ override val description: String = "A command that might fail"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("risky-command")
+ .executes { ctx ->
+ safeExecute(ctx) {
+ // Code that might throw exceptions
+ performRiskyOperation()
+ ctx.source.sender.sendMessage("Operation completed successfully!")
+ Command.SINGLE_SUCCESS
+ }
+ }
+ .build()
}
- .register()
+
+ private fun performRiskyOperation() {
+ // Risky code here
+ }
+}
```
## Best Practices
@@ -546,67 +896,147 @@ SafeCommandBuilder("risky-command")
### 1. Use Descriptive Names and Help
```kotlin
-CommandBuilder("player-management")
- .description("Comprehensive player management system")
- .usage("/player-management [options...]")
- .examples(
- "/player-management ban PlayerName Griefing",
- "/player-management mute PlayerName 10m Spamming",
- "/player-management info PlayerName"
- )
- .register()
+class PlayerManagementCommand : CommandBuilder {
+
+ override val description: String = "Comprehensive player management system"
+
+ override fun register(): LiteralCommandNode {
+ return Commands.literal("player-management")
+ .executes { ctx ->
+ val sender = ctx.source.sender
+ sender.sendMessage("§6Player Management Commands:")
+ sender.sendMessage("§e/player-management ban [reason] - Ban a player")
+ sender.sendMessage("§e/player-management mute