Implement a command system
parent
ca456f0689
commit
e30198fdab
|
@ -2,6 +2,7 @@ package codes.som.hibiscus.mixins;
|
||||||
|
|
||||||
import codes.som.hibiscus.HibiscusMod;
|
import codes.som.hibiscus.HibiscusMod;
|
||||||
import codes.som.hibiscus.events.PlayerTickEvent;
|
import codes.som.hibiscus.events.PlayerTickEvent;
|
||||||
|
import codes.som.hibiscus.events.SendChatEvent;
|
||||||
import net.minecraft.client.network.ClientPlayerEntity;
|
import net.minecraft.client.network.ClientPlayerEntity;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
@ -14,4 +15,12 @@ public abstract class MixinClientPlayerEntity {
|
||||||
private void onPostTick(CallbackInfo ci) {
|
private void onPostTick(CallbackInfo ci) {
|
||||||
HibiscusMod.bus().fire(PlayerTickEvent.INSTANCE);
|
HibiscusMod.bus().fire(PlayerTickEvent.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Inject(method = "sendChatMessage", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void onChatMessage(String message, CallbackInfo ci) {
|
||||||
|
var event = new SendChatEvent(message);
|
||||||
|
HibiscusMod.bus().fire(event);
|
||||||
|
if (event.isCancelled())
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package codes.som.hibiscus
|
||||||
|
|
||||||
|
import net.minecraft.text.LiteralText
|
||||||
|
import net.minecraft.util.Formatting
|
||||||
|
|
||||||
|
object HibiscusLog {
|
||||||
|
private val hibiscusMessagePrefix
|
||||||
|
get() = LiteralText("[H] ").styled { it.withColor(0xFFC1F8L.toInt()) }
|
||||||
|
|
||||||
|
fun info(message: String) {
|
||||||
|
val messageText = LiteralText(message).styled { it.withColor(Formatting.WHITE) }
|
||||||
|
player.sendMessage(hibiscusMessagePrefix.append(messageText), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun error(message: String) {
|
||||||
|
val errorText = LiteralText("Error: ").styled { it.withColor(Formatting.RED) }
|
||||||
|
val messageText = LiteralText(message).styled { it.withColor(Formatting.WHITE) }
|
||||||
|
player.sendMessage(hibiscusMessagePrefix.append(errorText).append(messageText), false)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
package codes.som.hibiscus
|
package codes.som.hibiscus
|
||||||
|
|
||||||
|
import codes.som.hibiscus.api.command.CommandManager
|
||||||
import codes.som.hibiscus.api.event.EventBus
|
import codes.som.hibiscus.api.event.EventBus
|
||||||
|
import codes.som.hibiscus.api.event.EventPhase
|
||||||
import codes.som.hibiscus.events.KeyEvent
|
import codes.som.hibiscus.events.KeyEvent
|
||||||
import codes.som.hibiscus.features.FeaturesRegistry
|
import codes.som.hibiscus.features.FeaturesRegistry
|
||||||
import codes.som.hibiscus.gui.ImGuiScreen
|
import codes.som.hibiscus.gui.ImGuiScreen
|
||||||
|
import codes.som.hibiscus.util.command.ChatCommandListener
|
||||||
import codes.som.hibiscus.util.netmoving.NetworkMovingDispatcher
|
import codes.som.hibiscus.util.netmoving.NetworkMovingDispatcher
|
||||||
import net.fabricmc.api.ModInitializer
|
import net.fabricmc.api.ModInitializer
|
||||||
import org.lwjgl.glfw.GLFW
|
import org.lwjgl.glfw.GLFW
|
||||||
|
@ -18,9 +21,13 @@ object HibiscusMod : ModInitializer {
|
||||||
val bus = EventBus()
|
val bus = EventBus()
|
||||||
|
|
||||||
val features = FeaturesRegistry()
|
val features = FeaturesRegistry()
|
||||||
|
val commands = CommandManager()
|
||||||
|
|
||||||
override fun onInitialize() {
|
override fun onInitialize() {
|
||||||
bus.register(NetworkMovingDispatcher())
|
for (feature in features.getAllFeatures()) {
|
||||||
|
commands.register(feature.createFeatureCommand())
|
||||||
|
}
|
||||||
|
|
||||||
bus.register { event: KeyEvent ->
|
bus.register { event: KeyEvent ->
|
||||||
if (event.key != GLFW_KEY_RIGHT_SHIFT || event.action != GLFW.GLFW_PRESS)
|
if (event.key != GLFW_KEY_RIGHT_SHIFT || event.action != GLFW.GLFW_PRESS)
|
||||||
return@register
|
return@register
|
||||||
|
@ -30,5 +37,8 @@ object HibiscusMod : ModInitializer {
|
||||||
|
|
||||||
mc.setScreen(ImGuiScreen)
|
mc.setScreen(ImGuiScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bus.register(NetworkMovingDispatcher(), EventPhase.AFTER)
|
||||||
|
bus.register(ChatCommandListener())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package codes.som.hibiscus.api.command
|
||||||
|
|
||||||
|
import codes.som.hibiscus.api.command.ExecutableCommand.CommandBranch
|
||||||
|
import codes.som.hibiscus.api.command.parser.ArgumentParser
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
|
||||||
|
open class Command(val name: String) {
|
||||||
|
private val branches = mutableListOf<BranchDeclaration>()
|
||||||
|
private val aliases = mutableListOf<String>()
|
||||||
|
|
||||||
|
fun alias(aliasName: String) {
|
||||||
|
aliases += aliasName
|
||||||
|
}
|
||||||
|
|
||||||
|
fun branch(branchName: String? = null, handler: Function<*>): BranchDeclaration {
|
||||||
|
val params = handler.javaClass.genericInterfaces
|
||||||
|
for (param in params) {
|
||||||
|
if (param is ParameterizedType) {
|
||||||
|
val parameterTypes = mutableListOf<Class<*>>()
|
||||||
|
|
||||||
|
param.actualTypeArguments
|
||||||
|
.filterIndexed { index, _ -> index != param.actualTypeArguments.lastIndex }
|
||||||
|
.filterIsInstance<Class<*>>()
|
||||||
|
.forEach { parameterTypes += it }
|
||||||
|
|
||||||
|
val branchDecl = BranchDeclaration(branchName, handler, parameterTypes.toTypedArray())
|
||||||
|
branches += branchDecl
|
||||||
|
|
||||||
|
return branchDecl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw IllegalStateException("Handler function had no generic parameters!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildCommand(): ExecutableCommand {
|
||||||
|
val command = ExecutableCommand(name, aliases.toTypedArray())
|
||||||
|
command.branches.addAll(
|
||||||
|
branches.asSequence()
|
||||||
|
.map {
|
||||||
|
CommandBranch(
|
||||||
|
it.branchName,
|
||||||
|
it.aliases.toTypedArray(),
|
||||||
|
it.parameterTypes,
|
||||||
|
it.handler,
|
||||||
|
it.typeHints
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return command
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class BranchDeclaration internal constructor(
|
||||||
|
val branchName: String?,
|
||||||
|
val handler: Function<*>,
|
||||||
|
val parameterTypes: Array<Class<*>>
|
||||||
|
) {
|
||||||
|
internal val aliases = mutableListOf<String>()
|
||||||
|
internal val typeHints = mutableMapOf<Int, ArgumentParser<*>>()
|
||||||
|
|
||||||
|
fun alias(aliasName: String): BranchDeclaration {
|
||||||
|
aliases += aliasName
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun typeHint(index: Int, parser: ArgumentParser<*>): BranchDeclaration {
|
||||||
|
typeHints.put(index, parser)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package codes.som.hibiscus.api.command
|
||||||
|
|
||||||
|
enum class CommandContext {
|
||||||
|
MANUAL,
|
||||||
|
KEYBIND,
|
||||||
|
OTHER,
|
||||||
|
}
|
|
@ -0,0 +1,231 @@
|
||||||
|
package codes.som.hibiscus.api.command
|
||||||
|
|
||||||
|
import codes.som.hibiscus.api.command.ExecutableCommand.CommandBranch
|
||||||
|
import codes.som.hibiscus.api.command.context.CommandExecutionContext
|
||||||
|
import codes.som.hibiscus.api.command.exceptions.*
|
||||||
|
import codes.som.hibiscus.api.command.parser.ArgumentParser
|
||||||
|
import codes.som.hibiscus.api.command.utils.PeekableIterator
|
||||||
|
import codes.som.hibiscus.api.command.utils.splitExceptingQuotes
|
||||||
|
|
||||||
|
class CommandManager(private val registerDefaultParsers: Boolean = true) {
|
||||||
|
private val parserRegistry = mutableMapOf<Class<*>, ArgumentParser<*>>()
|
||||||
|
val commands = mutableListOf<ExecutableCommand>()
|
||||||
|
|
||||||
|
var context: CommandContext = CommandContext.OTHER
|
||||||
|
|
||||||
|
init {
|
||||||
|
registerDefaultParsersIfApplicable()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerDefaultParsersIfApplicable() {
|
||||||
|
if (registerDefaultParsers) {
|
||||||
|
addDefaultParsers(parserRegistry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> registerParser(type: Class<T>, parser: ArgumentParser<T>) {
|
||||||
|
parserRegistry.put(type, parser)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyCommand(command: ExecutableCommand) {
|
||||||
|
// TODO: Prevent name collisions for commands / aliases
|
||||||
|
|
||||||
|
command.branches.forEach {
|
||||||
|
it.parameterTypes
|
||||||
|
.filter { it !in parserRegistry }
|
||||||
|
.forEach { throw MissingParserException(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(CommandRegistrationException::class)
|
||||||
|
fun register(declaration: Command) {
|
||||||
|
try {
|
||||||
|
val command = declaration.buildCommand()
|
||||||
|
|
||||||
|
verifyCommand(command)
|
||||||
|
commands += command
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw CommandRegistrationException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(CommandExecutionException::class)
|
||||||
|
fun executeCommand(fullCommand: String) {
|
||||||
|
try {
|
||||||
|
val args = splitExceptingQuotes(fullCommand, true).toList()
|
||||||
|
if (args.isEmpty())
|
||||||
|
throw CommandNotFoundException("")
|
||||||
|
|
||||||
|
val commandName = args[0]
|
||||||
|
|
||||||
|
val matchingCommands = commands.filter { commandMatches(commandName, it) }
|
||||||
|
when {
|
||||||
|
matchingCommands.isEmpty() -> throw CommandNotFoundException(commandName)
|
||||||
|
matchingCommands.size > 1 -> throw CommandIsAmbiguousException(commandName)
|
||||||
|
}
|
||||||
|
|
||||||
|
val command = matchingCommands.first()
|
||||||
|
|
||||||
|
fun isBranchViable(branch: CommandBranch, args: List<String>): Boolean {
|
||||||
|
try {
|
||||||
|
val argObjects = parseArgumentsForBranch(branch, args)
|
||||||
|
if (argObjects.size == branch.parameterTypes.size) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val viableBranches = command.branches.filter { branchMatches(args, it) }.filter { isBranchViable(it, args) }
|
||||||
|
|
||||||
|
val viableNamedBranches = viableBranches.filter { it.name != null }
|
||||||
|
if (viableNamedBranches.size > 1)
|
||||||
|
throw BranchesAreAmbiguousException(commandName, args[1])
|
||||||
|
|
||||||
|
// Loop through named branches first, as branch names have priority over arguments.
|
||||||
|
for (branch in viableNamedBranches) {
|
||||||
|
executeBranch(branch, args)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (branch in viableBranches.filter { it.name == null }) {
|
||||||
|
executeBranch(branch, args)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw NoMatchingBranchesException()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw CommandExecutionException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun completeCommand(fullCommand: String): Array<String> {
|
||||||
|
return completeCommandDuplicatesSpaces(fullCommand)
|
||||||
|
.map {
|
||||||
|
if (it.toCharArray().any { it.isWhitespace() }) "\"$it\"" else it
|
||||||
|
} // Wrap suggestions containing spaces in quotes
|
||||||
|
.toSet() // Remove duplicates
|
||||||
|
.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun completeCommandDuplicatesSpaces(fullCommand: String): List<String> {
|
||||||
|
val args = splitExceptingQuotes(fullCommand, true).toList()
|
||||||
|
|
||||||
|
if (args.isEmpty())
|
||||||
|
return commands.map { it.name }
|
||||||
|
|
||||||
|
if (args.size == 1) {
|
||||||
|
val namesAndAliasesOfCommands = mutableListOf<String>().apply {
|
||||||
|
addAll(commands.map { it.name })
|
||||||
|
commands.forEach { addAll(it.aliases) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return namesAndAliasesOfCommands.filter { it.toLowerCase().startsWith(fullCommand.toLowerCase()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val commandName = args[0]
|
||||||
|
val matchingCommands = commands.filter { commandMatches(commandName, it) }
|
||||||
|
when {
|
||||||
|
matchingCommands.isEmpty() -> return emptyList()
|
||||||
|
matchingCommands.size > 1 -> throw CommandIsAmbiguousException(commandName)
|
||||||
|
}
|
||||||
|
|
||||||
|
val command = matchingCommands.first()
|
||||||
|
|
||||||
|
if (args[1].isBlank())
|
||||||
|
return command.branches.mapNotNull { it.name }
|
||||||
|
|
||||||
|
val matchingBranches = mutableListOf<String>().apply {
|
||||||
|
addAll(command.branches.mapNotNull { it.name })
|
||||||
|
command.branches.forEach { addAll(it.aliases) }
|
||||||
|
}.filter { it.startsWith(args[1]) }
|
||||||
|
|
||||||
|
if (args.size == 2)
|
||||||
|
return matchingBranches
|
||||||
|
|
||||||
|
// TODO: Tab completion for parameters
|
||||||
|
// - Solve ambiguity issues
|
||||||
|
// ????
|
||||||
|
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseArgumentsForBranch(branch: CommandBranch, args: List<String>): Array<Any> {
|
||||||
|
val parsers = mutableListOf<ArgumentParser<*>>()
|
||||||
|
for ((index, parameterType) in branch.parameterTypes.withIndex()) {
|
||||||
|
if (index in branch.typeHints) {
|
||||||
|
parsers.add(branch.typeHints[index]!!)
|
||||||
|
} else {
|
||||||
|
if (parameterType !in parserRegistry)
|
||||||
|
throw MissingParserException(parameterType)
|
||||||
|
|
||||||
|
parsers.add(parserRegistry[parameterType]!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val startIndex = if (branch.name == null) 1 else 2
|
||||||
|
|
||||||
|
if (parsers.isEmpty() && args.size > startIndex) {
|
||||||
|
throw ParsingException(
|
||||||
|
InvalidArgumentCount(
|
||||||
|
branch.name
|
||||||
|
?: "<blank>"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val argsIterator = PeekableIterator(args.listIterator(startIndex))
|
||||||
|
|
||||||
|
val context = CommandExecutionContext(argsIterator, branch.parameterTypes, 0)
|
||||||
|
|
||||||
|
val argumentObjects = mutableListOf<Any>()
|
||||||
|
|
||||||
|
val minArgs = parsers.map { it.minimumAcceptedArguments() }.sum()
|
||||||
|
|
||||||
|
if (args.size - startIndex >= minArgs && args.size - startIndex >= parsers.size) {
|
||||||
|
for ((index, parser) in parsers.withIndex()) {
|
||||||
|
context.currentParameter = index
|
||||||
|
|
||||||
|
try {
|
||||||
|
argumentObjects += parser.parse(context)!!
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw ParsingException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw ParsingException(
|
||||||
|
InvalidArgumentCount(
|
||||||
|
branch.name
|
||||||
|
?: "<blank>"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return argumentObjects.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun executeBranch(branch: CommandBranch, args: List<String>) {
|
||||||
|
val argumentObjects = parseArgumentsForBranch(branch, args)
|
||||||
|
branch.execute(*argumentObjects)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun commandMatches(commandName: String, command: ExecutableCommand): Boolean {
|
||||||
|
return command.name.equals(commandName, ignoreCase = true) ||
|
||||||
|
command.aliases.any { it.equals(commandName, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun branchMatches(args: List<String>, branch: CommandBranch): Boolean {
|
||||||
|
return branch.name == null ||
|
||||||
|
(args.size > 1 && (branch.name.equals(args[1], ignoreCase = true) ||
|
||||||
|
branch.aliases.any { it.equals(args[1], ignoreCase = true) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
commands.clear()
|
||||||
|
parserRegistry.clear()
|
||||||
|
registerDefaultParsersIfApplicable()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package codes.som.hibiscus.api.command
|
||||||
|
|
||||||
|
import codes.som.hibiscus.api.command.parser.*
|
||||||
|
|
||||||
|
fun addDefaultParsers(parserRegistry: MutableMap<Class<*>, ArgumentParser<*>>) {
|
||||||
|
parserRegistry[String::class.java] = StringParser()
|
||||||
|
|
||||||
|
parserRegistry[Int::class.java] = IntParser()
|
||||||
|
parserRegistry[Float::class.java] = FloatParser()
|
||||||
|
parserRegistry[Double::class.java] = DoubleParser()
|
||||||
|
parserRegistry[Short::class.java] = ShortParser()
|
||||||
|
parserRegistry[Long::class.java] = LongParser()
|
||||||
|
parserRegistry[Char::class.java] = CharParser()
|
||||||
|
|
||||||
|
|
||||||
|
// Java interop
|
||||||
|
parserRegistry[java.lang.Integer::class.java] = parserRegistry[Int::class.java]!!
|
||||||
|
parserRegistry[java.lang.Float::class.java] = parserRegistry[Float::class.java]!!
|
||||||
|
parserRegistry[java.lang.Double::class.java] = parserRegistry[Double::class.java]!!
|
||||||
|
parserRegistry[java.lang.Short::class.java] = parserRegistry[Short::class.java]!!
|
||||||
|
parserRegistry[java.lang.Long::class.java] = parserRegistry[Long::class.java]!!
|
||||||
|
parserRegistry[java.lang.Character::class.java] = parserRegistry[Char::class.java]!!
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package codes.som.hibiscus.api.command
|
||||||
|
|
||||||
|
import codes.som.hibiscus.api.command.parser.ArgumentParser
|
||||||
|
|
||||||
|
class ExecutableCommand(val name: String, val aliases: Array<String>) {
|
||||||
|
companion object {
|
||||||
|
fun declare(name: String, init: Command.() -> Unit): Command {
|
||||||
|
val declaration = Command(name)
|
||||||
|
init(declaration)
|
||||||
|
|
||||||
|
return declaration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val branches = mutableListOf<CommandBranch>()
|
||||||
|
|
||||||
|
class CommandBranch(
|
||||||
|
val name: String?,
|
||||||
|
val aliases: Array<String>,
|
||||||
|
val parameterTypes: Array<Class<*>>,
|
||||||
|
private val handler: Function<*>,
|
||||||
|
val typeHints: Map<Int, ArgumentParser<*>>
|
||||||
|
) {
|
||||||
|
fun execute(vararg args: Any) {
|
||||||
|
handler.javaClass.declaredMethods.first { it.name == "invoke" }.apply { this.isAccessible = true }
|
||||||
|
.invoke(handler, *args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package codes.som.hibiscus.api.command.context
|
||||||
|
|
||||||
|
import codes.som.hibiscus.api.command.utils.PeekableIterator
|
||||||
|
|
||||||
|
class CommandExecutionContext(
|
||||||
|
val arguments: PeekableIterator<String>,
|
||||||
|
val parameters: Array<Class<*>>,
|
||||||
|
var currentParameter: Int
|
||||||
|
)
|
|
@ -0,0 +1,11 @@
|
||||||
|
package codes.som.hibiscus.api.command.exceptions
|
||||||
|
|
||||||
|
class CommandExecutionException(cause: Exception) : RuntimeException("Exception while executing command", cause)
|
||||||
|
class CommandRegistrationException(cause: Exception) : RuntimeException("Exception while registering command", cause)
|
||||||
|
|
||||||
|
class CommandNotFoundException(name: String) : RuntimeException("The command '$name' was not found.")
|
||||||
|
class CommandIsAmbiguousException(name: String) : RuntimeException("The command '$name' is ambiguous.")
|
||||||
|
|
||||||
|
class NoMatchingBranchesException : RuntimeException("No branches matching were found")
|
||||||
|
class BranchesAreAmbiguousException(command: String, branchName: String?) :
|
||||||
|
RuntimeException("Branch '$branchName' is ambiguous for command '$command'.")
|
|
@ -0,0 +1,8 @@
|
||||||
|
package codes.som.hibiscus.api.command.exceptions
|
||||||
|
|
||||||
|
class MissingParserException(classToParse: Class<*>) :
|
||||||
|
RuntimeException("No parser found for type: ${classToParse.name}")
|
||||||
|
|
||||||
|
class ParsingException(cause: Exception) : RuntimeException("Exception while parsing", cause)
|
||||||
|
|
||||||
|
class InvalidArgumentCount(branchName: String) : RuntimeException("Invalid amount of arguments for branch: $branchName")
|
|
@ -0,0 +1,10 @@
|
||||||
|
package codes.som.hibiscus.api.command.parser
|
||||||
|
|
||||||
|
import codes.som.hibiscus.api.command.context.CommandExecutionContext
|
||||||
|
|
||||||
|
interface ArgumentParser<out T> {
|
||||||
|
fun parse(context: CommandExecutionContext): T?
|
||||||
|
fun provideSuggestions(context: CommandExecutionContext): List<String> = emptyList()
|
||||||
|
|
||||||
|
fun minimumAcceptedArguments(): Int = 1
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package codes.som.hibiscus.api.command.parser
|
||||||
|
|
||||||
|
import codes.som.hibiscus.api.command.context.CommandExecutionContext
|
||||||
|
|
||||||
|
class StringParser : ArgumentParser<String> {
|
||||||
|
override fun parse(context: CommandExecutionContext): String {
|
||||||
|
return if (context.currentParameter == context.parameters.lastIndex) {
|
||||||
|
buildString {
|
||||||
|
for (arg in context.arguments) {
|
||||||
|
append(arg)
|
||||||
|
|
||||||
|
if (context.arguments.hasNext())
|
||||||
|
append(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.arguments.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IntParser : ArgumentParser<Int> {
|
||||||
|
override fun parse(context: CommandExecutionContext): Int {
|
||||||
|
return context.arguments.next().toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FloatParser : ArgumentParser<Float> {
|
||||||
|
override fun parse(context: CommandExecutionContext): Float {
|
||||||
|
return context.arguments.next().toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DoubleParser : ArgumentParser<Double> {
|
||||||
|
override fun parse(context: CommandExecutionContext): Double {
|
||||||
|
return context.arguments.next().toDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShortParser : ArgumentParser<Short> {
|
||||||
|
override fun parse(context: CommandExecutionContext): Short {
|
||||||
|
return context.arguments.next().toShort()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LongParser : ArgumentParser<Long> {
|
||||||
|
override fun parse(context: CommandExecutionContext): Long {
|
||||||
|
return context.arguments.next().toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CharParser : ArgumentParser<Char> {
|
||||||
|
override fun parse(context: CommandExecutionContext): Char {
|
||||||
|
val chars = context.arguments.next().toCharArray()
|
||||||
|
assert(chars.size == 1)
|
||||||
|
|
||||||
|
return chars.first()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package codes.som.hibiscus.api.command.utils
|
||||||
|
|
||||||
|
class PeekableIterator<T>(internal val wrapped: ListIterator<T>) : Iterator<T> {
|
||||||
|
override fun hasNext(): Boolean {
|
||||||
|
return wrapped.hasNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun next(): T {
|
||||||
|
return wrapped.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun peek(): T {
|
||||||
|
val next = wrapped.next()
|
||||||
|
wrapped.previous()
|
||||||
|
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun previous(): T = wrapped.previous()
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package codes.som.hibiscus.api.command.utils
|
||||||
|
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
fun join(strings: Array<String>, delimiter: String): String {
|
||||||
|
val str = StringBuilder()
|
||||||
|
for (string in strings) {
|
||||||
|
str.append(string)
|
||||||
|
str.append(delimiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.substring(0, str.length - delimiter.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun splitExceptingQuotes(string: String, stripQuotes: Boolean): Array<String> {
|
||||||
|
val list = arrayListOf<String>()
|
||||||
|
val m = Pattern.compile("([^\"]\\S*|\".+?\")\\s*").matcher(string)
|
||||||
|
while (m.find())
|
||||||
|
list.add(if (stripQuotes) m.group(1).replace("\"", "") else m.group(1))
|
||||||
|
|
||||||
|
if (string.endsWith(" "))
|
||||||
|
list.add("")
|
||||||
|
|
||||||
|
return list.toTypedArray()
|
||||||
|
}
|
|
@ -3,25 +3,36 @@ package codes.som.hibiscus.api.event
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
class EventBus {
|
class EventBus {
|
||||||
private val listenerMap = mutableMapOf<Class<out Event>, MutableList<Listener<*>>>()
|
private val allListeners = mutableMapOf<EventPhase, MutableMap<Class<*>, MutableList<Listener<*>>>>()
|
||||||
|
|
||||||
|
inline fun <reified T : Event> register(listener: Listener<T>, phase: EventPhase) {
|
||||||
|
this.register(T::class.java, listener, phase)
|
||||||
|
}
|
||||||
|
|
||||||
inline fun <reified T : Event> register(listener: Listener<T>) {
|
inline fun <reified T : Event> register(listener: Listener<T>) {
|
||||||
this.register(T::class.java, listener)
|
this.register(T::class.java, listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Event> register(type: Class<T>, listener: Listener<T>) {
|
fun <T : Event> register(type: Class<T>, listener: Listener<T>, phase: EventPhase = EventPhase.NORMAL) {
|
||||||
|
val listenerMap = allListeners.getOrPut(phase, ::mutableMapOf)
|
||||||
listenerMap.getOrPut(type, ::CopyOnWriteArrayList).add(listener)
|
listenerMap.getOrPut(type, ::CopyOnWriteArrayList).add(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Event> unregister(listener: Listener<T>) =
|
inline fun <reified T : Event> unregister(listener: Listener<T>, phase: EventPhase = EventPhase.NORMAL) =
|
||||||
unregister(T::class.java, listener)
|
unregister(T::class.java, listener, phase)
|
||||||
|
|
||||||
fun <T : Event> unregister(type: Class<T>, listener: Listener<T>) =
|
fun <T : Event> unregister(type: Class<T>, listener: Listener<T>, phase: EventPhase = EventPhase.NORMAL) {
|
||||||
|
val listenerMap = allListeners[phase] ?: return
|
||||||
listenerMap[type]?.remove(listener)
|
listenerMap[type]?.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
fun <T : Event> fire(event: T) {
|
fun <T : Event> fire(event: T) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
for (phase in EventPhase.values()) {
|
||||||
val listeners = (listenerMap[event.javaClass] ?: return) as List<Listener<T>>
|
val listenerMap = allListeners[phase] ?: continue
|
||||||
listeners.forEach { it.on(event) }
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val listeners = (listenerMap[event.javaClass] ?: continue) as List<Listener<T>>
|
||||||
|
listeners.forEach { it.on(event) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package codes.som.hibiscus.api.event
|
||||||
|
|
||||||
|
enum class EventPhase {
|
||||||
|
BEFORE,
|
||||||
|
NORMAL,
|
||||||
|
AFTER
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package codes.som.hibiscus.api.feature
|
package codes.som.hibiscus.api.feature
|
||||||
|
|
||||||
import codes.som.hibiscus.HibiscusMod
|
import codes.som.hibiscus.HibiscusMod
|
||||||
|
import codes.som.hibiscus.api.command.Command
|
||||||
import codes.som.hibiscus.api.event.*
|
import codes.som.hibiscus.api.event.*
|
||||||
import codes.som.hibiscus.api.feature.values.ValueRegistry
|
import codes.som.hibiscus.api.feature.values.ValueRegistry
|
||||||
|
|
||||||
|
@ -33,5 +34,9 @@ abstract class Feature(val name: String, val category: FeatureCategory) {
|
||||||
open fun onEnable() {}
|
open fun onEnable() {}
|
||||||
open fun onDisable() {}
|
open fun onDisable() {}
|
||||||
|
|
||||||
|
open fun createFeatureCommand(): Command {
|
||||||
|
return FeatureCommand(this)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Module commands
|
// TODO: Module commands
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package codes.som.hibiscus.api.feature
|
||||||
|
|
||||||
|
import codes.som.hibiscus.HibiscusLog
|
||||||
|
import codes.som.hibiscus.HibiscusMod
|
||||||
|
import codes.som.hibiscus.api.command.Command
|
||||||
|
import codes.som.hibiscus.api.command.CommandContext
|
||||||
|
|
||||||
|
class FeatureCommand(feature: Feature) : Command(feature.name.replace(" ", "").lowercase()) {
|
||||||
|
init {
|
||||||
|
branch {
|
||||||
|
feature.enabled = !feature.enabled
|
||||||
|
|
||||||
|
if (HibiscusMod.commands.context == CommandContext.MANUAL) {
|
||||||
|
val state = if (feature.enabled) "enabled" else "disabled"
|
||||||
|
HibiscusLog.info("${feature.name} is now $state.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feature.values.exist()) {
|
||||||
|
for (value in feature.values) {
|
||||||
|
val simplifiedValueName = value.name.toLowerCase().replace(" ", "")
|
||||||
|
|
||||||
|
branch(simplifiedValueName) {
|
||||||
|
HibiscusLog.info("Value of '${value.name}': " + value.getValueAsString())
|
||||||
|
}
|
||||||
|
|
||||||
|
branch(simplifiedValueName) { newValue: String ->
|
||||||
|
try {
|
||||||
|
value.setValueFromString(newValue)
|
||||||
|
HibiscusLog.info("Value of '${value.name}': " + value.getValueAsString())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
HibiscusLog.info("Could not set the value of '${value.name}' to '$newValue'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package codes.som.hibiscus.events
|
package codes.som.hibiscus.events
|
||||||
|
|
||||||
|
import codes.som.hibiscus.api.event.Cancellable
|
||||||
import codes.som.hibiscus.api.event.Event
|
import codes.som.hibiscus.api.event.Event
|
||||||
|
|
||||||
object PlayerTickEvent : Event
|
object PlayerTickEvent : Event
|
||||||
|
class SendChatEvent(val message: String) : Cancellable(), Event
|
||||||
|
|
|
@ -2,6 +2,5 @@ package codes.som.hibiscus.features
|
||||||
|
|
||||||
class FeaturesRegistry {
|
class FeaturesRegistry {
|
||||||
private val features = ALL_FEATURES.map { it() }
|
private val features = ALL_FEATURES.map { it() }
|
||||||
|
|
||||||
fun getAllFeatures() = features.asSequence()
|
fun getAllFeatures() = features.asSequence()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,4 +16,9 @@ class NoFallDamage : Feature("No Fall Damage", FeatureCategory.PLAYER) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createFeatureCommand() =
|
||||||
|
super.createFeatureCommand().apply {
|
||||||
|
alias("nofall")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package codes.som.hibiscus.util.command
|
||||||
|
|
||||||
|
import codes.som.hibiscus.HibiscusLog
|
||||||
|
import codes.som.hibiscus.HibiscusMod
|
||||||
|
import codes.som.hibiscus.api.command.CommandContext
|
||||||
|
import codes.som.hibiscus.api.command.exceptions.CommandExecutionException
|
||||||
|
import codes.som.hibiscus.api.event.TypedListener
|
||||||
|
import codes.som.hibiscus.events.SendChatEvent
|
||||||
|
|
||||||
|
class ChatCommandListener : TypedListener<SendChatEvent>(SendChatEvent::class.java) {
|
||||||
|
override fun on(event: SendChatEvent) {
|
||||||
|
if (event.message.startsWith(".")) {
|
||||||
|
try {
|
||||||
|
HibiscusMod.commands.context = CommandContext.MANUAL
|
||||||
|
HibiscusMod.commands.executeCommand(event.message.substring(1))
|
||||||
|
} catch (e: CommandExecutionException) {
|
||||||
|
// e.printStackTrace()
|
||||||
|
e.cause?.message?.let { HibiscusLog.error(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
event.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue