Implement a command system
This commit is contained in:
parent
ca456f0689
commit
e30198fdab
23 changed files with 645 additions and 10 deletions
|
@ -2,6 +2,7 @@ package codes.som.hibiscus.mixins;
|
|||
|
||||
import codes.som.hibiscus.HibiscusMod;
|
||||
import codes.som.hibiscus.events.PlayerTickEvent;
|
||||
import codes.som.hibiscus.events.SendChatEvent;
|
||||
import net.minecraft.client.network.ClientPlayerEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
@ -14,4 +15,12 @@ public abstract class MixinClientPlayerEntity {
|
|||
private void onPostTick(CallbackInfo ci) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
20
src/main/kotlin/codes/som/hibiscus/HibiscusLog.kt
Normal file
20
src/main/kotlin/codes/som/hibiscus/HibiscusLog.kt
Normal file
|
@ -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
|
||||
|
||||
import codes.som.hibiscus.api.command.CommandManager
|
||||
import codes.som.hibiscus.api.event.EventBus
|
||||
import codes.som.hibiscus.api.event.EventPhase
|
||||
import codes.som.hibiscus.events.KeyEvent
|
||||
import codes.som.hibiscus.features.FeaturesRegistry
|
||||
import codes.som.hibiscus.gui.ImGuiScreen
|
||||
import codes.som.hibiscus.util.command.ChatCommandListener
|
||||
import codes.som.hibiscus.util.netmoving.NetworkMovingDispatcher
|
||||
import net.fabricmc.api.ModInitializer
|
||||
import org.lwjgl.glfw.GLFW
|
||||
|
@ -18,9 +21,13 @@ object HibiscusMod : ModInitializer {
|
|||
val bus = EventBus()
|
||||
|
||||
val features = FeaturesRegistry()
|
||||
val commands = CommandManager()
|
||||
|
||||
override fun onInitialize() {
|
||||
bus.register(NetworkMovingDispatcher())
|
||||
for (feature in features.getAllFeatures()) {
|
||||
commands.register(feature.createFeatureCommand())
|
||||
}
|
||||
|
||||
bus.register { event: KeyEvent ->
|
||||
if (event.key != GLFW_KEY_RIGHT_SHIFT || event.action != GLFW.GLFW_PRESS)
|
||||
return@register
|
||||
|
@ -30,5 +37,8 @@ object HibiscusMod : ModInitializer {
|
|||
|
||||
mc.setScreen(ImGuiScreen)
|
||||
}
|
||||
|
||||
bus.register(NetworkMovingDispatcher(), EventPhase.AFTER)
|
||||
bus.register(ChatCommandListener())
|
||||
}
|
||||
}
|
||||
|
|
72
src/main/kotlin/codes/som/hibiscus/api/command/Command.kt
Normal file
72
src/main/kotlin/codes/som/hibiscus/api/command/Command.kt
Normal file
|
@ -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,
|
||||
}
|
231
src/main/kotlin/codes/som/hibiscus/api/command/CommandManager.kt
Normal file
231
src/main/kotlin/codes/som/hibiscus/api/command/CommandManager.kt
Normal file
|
@ -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
|
||||
|
||||
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>) {
|
||||
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)
|
||||
}
|
||||
|
||||
inline fun <reified T : Event> unregister(listener: Listener<T>) =
|
||||
unregister(T::class.java, listener)
|
||||
inline fun <reified T : Event> unregister(listener: Listener<T>, phase: EventPhase = EventPhase.NORMAL) =
|
||||
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)
|
||||
}
|
||||
|
||||
fun <T : Event> fire(event: T) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val listeners = (listenerMap[event.javaClass] ?: return) as List<Listener<T>>
|
||||
listeners.forEach { it.on(event) }
|
||||
for (phase in EventPhase.values()) {
|
||||
val listenerMap = allListeners[phase] ?: continue
|
||||
|
||||
@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
|
||||
|
||||
import codes.som.hibiscus.HibiscusMod
|
||||
import codes.som.hibiscus.api.command.Command
|
||||
import codes.som.hibiscus.api.event.*
|
||||
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 onDisable() {}
|
||||
|
||||
open fun createFeatureCommand(): Command {
|
||||
return FeatureCommand(this)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
import codes.som.hibiscus.api.event.Cancellable
|
||||
import codes.som.hibiscus.api.event.Event
|
||||
|
||||
object PlayerTickEvent : Event
|
||||
class SendChatEvent(val message: String) : Cancellable(), Event
|
||||
|
|
|
@ -2,6 +2,5 @@ package codes.som.hibiscus.features
|
|||
|
||||
class FeaturesRegistry {
|
||||
private val features = ALL_FEATURES.map { it() }
|
||||
|
||||
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 a new issue