-
-
Notifications
You must be signed in to change notification settings - Fork 0
Add comprehensive developer documentation with corrected CommandBuilder API examples #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add comprehensive developer documentation with corrected CommandBuilder API examples #8
Conversation
Summary
TL;DR: Initial commit by copilot-swe-agent[bot] outlining the project's plan. You can generate a new summary by commenting @diffy-bot. |
Co-authored-by: CoasterFreakDE <28011628+CoasterFreakDE@users.noreply.github.com>
Diff SummaryOverall Change: README.md Updates:
New Documentation Files (in the docs folder):
Suggestions:
User can generate a new summary by commenting @diffy-bot. |
…leshooting, and core guides Co-authored-by: CoasterFreakDE <28011628+CoasterFreakDE@users.noreply.github.com>
Git Diff SummaryThis patch introduces comprehensive documentation for KPaper covering utilities, plugin development, migration guidelines, and troubleshooting. New Files Added
TL;DRThis patch adds four major documentation files to help developers:
You can generate a new summary by commenting @diffy-bot. |
CoasterFreakDE
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You added functionality to the CommandBuilder interface that currently does not exists.
A command can be used like this:
package com.voidrooms.commands
import cc.modlabs.kpaper.command.CommandBuilder
import cc.modlabs.kpaper.extensions.send
import cc.modlabs.kpaper.extensions.sendSuccessSound
import cc.modlabs.kpaper.file.config.WorldConfig
import cc.modlabs.kpaper.world.toStringLocation
import com.mojang.brigadier.Command
import com.mojang.brigadier.arguments.FloatArgumentType
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.builder.RequiredArgumentBuilder
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.tree.LiteralCommandNode
import com.voidrooms.VoidRoomsBase
import com.voidrooms.data.cache.AreaCache
import com.voidrooms.listeners.area.AreaFlags
import com.voidrooms.listeners.area.getArea
import io.papermc.paper.command.brigadier.CommandSourceStack
import io.papermc.paper.command.brigadier.Commands
import io.papermc.paper.registry.RegistryAccess
import io.papermc.paper.registry.RegistryKey
import org.bukkit.entity.Player
import java.util.*
class AreaCommand : CommandBuilder {
override fun register(): LiteralCommandNode<CommandSourceStack> {
return Commands.literal("area")
.executes { ctx ->
val sender = ctx.source.sender as Player
val area = sender.location.getArea()
if (area != null) {
sender.send("You are standing in ${area.name}")
} else {
sender.send("No area here")
}
Command.SINGLE_SUCCESS
}
.then(Commands.literal("set")
.then(Commands.argument("area", StringArgumentType.word())
.requires { it.sender.hasPermission("commands.build") }
.suggests { _, builder ->
AreaCache.getAreas().forEach {
builder.suggest(it.name)
}
builder.buildFuture()
}
.then(Commands.literal("p1")
.executes { ctx ->
val sender = ctx.source.sender as Player
val areaName = StringArgumentType.getString(ctx, "area").replace(" ", "_").lowercase(Locale.getDefault())
savePoint(sender, areaName, "p1")
Command.SINGLE_SUCCESS
}
)
.then(Commands.literal("p2")
.executes { ctx ->
val sender = ctx.source.sender as Player
val areaName = StringArgumentType.getString(ctx, "area").replace(" ", "_").lowercase(Locale.getDefault())
savePoint(sender, areaName, "p2")
Command.SINGLE_SUCCESS
}
)
.then(Commands.literal("entrysound").then(soundCommandLiteral("entry")))
.then(Commands.literal("exitsound").then(soundCommandLiteral("exit")))
)
)
.then(Commands.literal("flag")
.requires { it.sender.hasPermission("commands.build") }
.then(Commands.literal("add")
.then(Commands.argument("area", StringArgumentType.word())
.suggests { _, builder ->
AreaCache.getAreas().forEach {
builder.suggest(it.name)
}
builder.buildFuture()
}
.then(Commands.argument("flag", StringArgumentType.word())
.suggests { ctx, builder ->
AreaFlags.entries.forEach { builder.suggest(it.name) }
builder.buildFuture()
}
.executes { ctx ->
val sender = ctx.source.sender as Player
val areaName = StringArgumentType.getString(ctx, "area")
val flag = StringArgumentType.getString(ctx, "flag")
addFlag(sender, areaName, flag)
Command.SINGLE_SUCCESS
}
)
)
)
.then(Commands.literal("remove")
.then(Commands.argument("area", StringArgumentType.word())
.suggests { _, builder ->
AreaCache.getAreas().forEach {
builder.suggest(it.name)
}
builder.buildFuture()
}
.then(Commands.argument("flag", StringArgumentType.word())
.suggests { ctx, builder ->
AreaFlags.entries.forEach { builder.suggest(it.name) }
builder.buildFuture()
}
.executes { ctx ->
val sender = ctx.source.sender as Player
val areaName = StringArgumentType.getString(ctx, "area")
val flag = StringArgumentType.getString(ctx, "flag")
removeFlag(sender, areaName, flag)
Command.SINGLE_SUCCESS
}
)
)
)
.then(Commands.literal("list")
.then(Commands.argument("area", StringArgumentType.word())
.suggests { _, builder ->
AreaCache.getAreas().forEach {
builder.suggest(it.name)
}
builder.buildFuture()
}
.executes { ctx ->
val sender = ctx.source.sender as Player
val areaName = StringArgumentType.getString(ctx, "area")
val area = AreaCache.getArea(areaName) ?: return@executes Command.SINGLE_SUCCESS
sender.send("Flags for ${area.name}")
area.flags.forEach { flag ->
sender.send("- ${flag.name}")
}
Command.SINGLE_SUCCESS
}
)
)
)
.then(Commands.literal("reload")
.requires { it.sender.hasPermission("commands.build") }
.executes { ctx ->
val sender = ctx.source.sender as Player
VoidRoomsBase.INSTANCE.reloadConfig()
AreaCache.reloadAreas()
sender.send("Reloaded areas")
Command.SINGLE_SUCCESS
}
)
.build()
}
private fun soundCommandLiteral(type: String): RequiredArgumentBuilder<CommandSourceStack?, String?>? =
Commands.argument("sound", StringArgumentType.word())
.suggests { ctx, builder ->
RegistryAccess.registryAccess().getRegistry(RegistryKey.SOUND_EVENT).forEach { sound ->
builder.suggest(sound.toString())
}
builder.buildFuture()
}
.executes { ctx ->
executeSound(ctx, type)
Command.SINGLE_SUCCESS
}
.then(Commands.argument("volume", FloatArgumentType.floatArg(0.0f, 2.0f))
.executes { ctx ->
executeSound(ctx, type, FloatArgumentType.getFloat(ctx, "volume"))
Command.SINGLE_SUCCESS
}
.then(Commands.argument("pitch", FloatArgumentType.floatArg(0.0f, 2.0f))
.executes { ctx ->
executeSound(ctx, type, FloatArgumentType.getFloat(ctx, "volume"), FloatArgumentType.getFloat(ctx, "pitch"))
Command.SINGLE_SUCCESS
}
)
)
private fun executeSound(
ctx: CommandContext<CommandSourceStack>,
type: String,
volume: Float = 1.0f,
pitch: Float = 1.0f
) {
val sender = ctx.source.sender as Player
val areaName =
StringArgumentType.getString(ctx, "area").replace(" ", "_").lowercase(Locale.getDefault())
val soundKey = StringArgumentType.getString(ctx, "sound")
saveSound(sender, areaName, soundKey, type, volume, pitch)
}
private fun saveSound(player: Player, areaName: String, sound: String, type: String, volume: Float = 1.0f, pitch: Float = 1.0f) {
val worldConfig = WorldConfig(player.world.name)
val oldSound = worldConfig.get("areas.$areaName.$type.name")
worldConfig.set("areas.$areaName.sound.$type.name", sound)
worldConfig.set("areas.$areaName.sound.$type.volume", volume)
worldConfig.set("areas.$areaName.sound.$type.pitch", pitch)
if (oldSound != null) {
player.send("Replaced old sound $oldSound in $areaName")
}
player.send("Set new sound $sound in $areaName with type $type, volume $volume and pitch $pitch")
worldConfig.saveConfig()
AreaCache.reloadAreas()
player.sendSuccessSound()
}
private fun addFlag(player: Player, areaName: String, flag: String) {
val worldConfig = WorldConfig(player.world.name)
val flags = worldConfig.getStringList("areas.$areaName.flags")
if (flags.contains(flag)) {
player.send("<red>Flag $flag already enabled")
return
}
flags.add(flag)
worldConfig.set("areas.$areaName.flags", flags.distinct())
worldConfig.saveConfig()
AreaCache.reloadAreas()
player.sendSuccessSound()
player.send("<green>Enabled Flag $flag in $areaName")
}
private fun removeFlag(player: Player, areaName: String, flag: String) {
val worldConfig = WorldConfig(player.world.name)
val flags = worldConfig.getStringList("areas.$areaName.flags")
if(!flags.remove(flag)) {
player.send("<red>Flag $flag not enabled")
return
}
worldConfig.set("areas.$areaName.flags", flags.distinct())
worldConfig.saveConfig()
AreaCache.reloadAreas()
player.sendSuccessSound()
player.send("<green>Removed Flag $flag in $areaName")
}
private fun savePoint(player: Player, areaName: String, point: String) {
val worldConfig = WorldConfig(player.world.name)
val oldArea = worldConfig.get("areas.$areaName.$point")
worldConfig.set("areas.$areaName.$point", player.location.toStringLocation().toString())
if (oldArea != null) {
player.send("Replaced old area $oldArea with new point $point for $areaName")
}
player.send("Set point: $point for $areaName")
worldConfig.set("areas.$areaName.name", areaName)
worldConfig.saveConfig()
AreaCache.reloadAreas()
player.sendSuccessSound()
}
}and then registered within a Paper Command Bootstrapper:
package com.voidrooms.registration
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
manager.registerEventHandler(LifecycleEvents.COMMANDS) { event ->
RegisterManager.registerCommands(event.registrar())
}
}
}with the help of a RegisterManager like this one:
package com.voidrooms.registration
import cc.modlabs.kpaper.command.CommandBuilder
import com.google.common.reflect.ClassPath
import dev.fruxz.ascend.extension.logging.getFactoryLogger
import io.papermc.paper.command.brigadier.Commands
import org.bukkit.Bukkit
import org.bukkit.event.Listener
import org.bukkit.plugin.Plugin
import kotlin.reflect.KClass
import kotlin.reflect.full.primaryConstructor
object RegisterManager {
private val logger = getFactoryLogger(RegisterManager::class)
private val PACKAGE_NAME = "com.voidrooms"
private fun <T : Any, E : Any> E.loadClassesInPackage(
packageName: String,
clazzType: KClass<T>
): List<KClass<out T>> {
try {
val classLoader = this.javaClass.classLoader
val allClasses = ClassPath.from(classLoader).allClasses
val classes = mutableListOf<KClass<out T>>()
for (classInfo in allClasses) {
if (!classInfo.name.startsWith(PACKAGE_NAME)) continue
if (classInfo.packageName.startsWith(packageName) && !classInfo.name.contains('$')) {
try {
val loadedClass = classInfo.load().kotlin
if (clazzType.isInstance(loadedClass.javaObjectType.getDeclaredConstructor().newInstance())) {
classes.add(loadedClass as KClass<out T>)
}
} catch (_: Exception) {
// Ignore, as this is not a class we need to load
}
}
}
return classes
} catch (exception: Exception) {
logger.error("Failed to load classes", exception)
return emptyList()
}
}
fun registerCommands(commands: Commands) {
val commandClasses = loadClassesInPackage("$PACKAGE_NAME.commands", CommandBuilder::class)
commandClasses.forEach {
val command = it.primaryConstructor?.call() as CommandBuilder
commands.register(
command.register(),
command.description,
command.aliases
)
logger.info("Command ${it.simpleName} registered")
}
logger.info("Registered ${commandClasses.size} minecraft commands")
}
fun registerListeners(plugin: Plugin) {
val listenerClasses = loadClassesInPackage("$PACKAGE_NAME.listeners", Listener::class)
var amountListeners = 0
listenerClasses.forEach {
try {
val listener = it.primaryConstructor?.call() as Listener
Bukkit.getPluginManager().registerEvents(listener, plugin)
amountListeners++
logger.info("Registered listener: ${it.simpleName}")
} catch (e: Exception) {
logger.error("Failed to register listener: ${it.simpleName}", e)
}
}
if (amountListeners == 0) return
plugin.logger.info("Registered $amountListeners listeners")
}
}Co-authored-by: CoasterFreakDE <28011628+CoasterFreakDE@users.noreply.github.com>
You're absolutely right! I had documented a fluent API for CommandBuilder that doesn't exist in KPaper. I've corrected all the documentation to show the actual CommandBuilder interface implementation using Paper's Brigadier API, as demonstrated in your example. The documentation now accurately shows:
Fixed in commit b843517 |
SummaryThis diff updates the documentation across multiple files to align the CommandBuilder examples and API usage with the current KPaper implementation. Key changes include:
User can generate a new summary by commenting @diffy-bot. |
Addresses the issue where "documentation is the code itself, which is not really good for a framework aiming to improve the development speed." This PR adds extensive developer documentation and practical examples to make KPaper much more accessible and developer-friendly.
What's Added
📚 Complete Documentation Structure
docs/directory🚀 Enhanced README
📖 Core Documentation Files
Getting Started:
API Documentation:
Development Guides:
Key Features Documented
The documentation covers all major KPaper capabilities with accurate API examples:
listen<Event>()syntaxCorrected Command Framework Documentation
The command documentation now accurately reflects KPaper's actual API:
This shows the correct pattern of:
Impact
This documentation transforms KPaper from a code-only framework into a developer-friendly library with:
The documentation directly addresses the stated problem by providing detailed guides and examples that significantly improve development speed and reduce the learning curve for KPaper adoption.
💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.