Support reloading features and commands at runtime

This means that if you add a new feature, you do not need to restart the
game to hot-swap it into a running game (provided you're in debug mode)
main
Charlotte Som 2022-02-13 06:01:20 +00:00
parent e28dd79cee
commit c0ec62d186
8 changed files with 88 additions and 15 deletions

View File

@ -1,9 +1,9 @@
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.api.event.EventPhase
import codes.som.hibiscus.api.keybinds.KeybindRegistry import codes.som.hibiscus.api.keybinds.KeybindRegistry
import codes.som.hibiscus.commands.CommandRegistry
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
@ -23,7 +23,7 @@ object HibiscusMod : ModInitializer {
val bus = EventBus() val bus = EventBus()
val features = FeaturesRegistry() val features = FeaturesRegistry()
val commands = CommandManager() val commands = CommandRegistry()
val keybinds = KeybindRegistry() val keybinds = KeybindRegistry()
override fun onInitialize() { override fun onInitialize() {
@ -44,5 +44,15 @@ object HibiscusMod : ModInitializer {
bus.register(NetworkMovingDispatcher(), EventPhase.AFTER) bus.register(NetworkMovingDispatcher(), EventPhase.AFTER)
bus.register(ChatCommandListener()) bus.register(ChatCommandListener())
bus.register(KeybindDispatcher()) bus.register(KeybindDispatcher())
// TODO: Load files
}
fun shutdown() {
// TODO: Save files
for (system in sequenceOf(bus, features, commands, keybinds)) {
system.reset()
}
} }
} }

View File

@ -6,11 +6,12 @@ import codes.som.hibiscus.api.command.exceptions.*
import codes.som.hibiscus.api.command.parser.ArgumentParser import codes.som.hibiscus.api.command.parser.ArgumentParser
import codes.som.hibiscus.api.command.utils.PeekableIterator import codes.som.hibiscus.api.command.utils.PeekableIterator
import codes.som.hibiscus.api.command.utils.splitExceptingQuotes import codes.som.hibiscus.api.command.utils.splitExceptingQuotes
import codes.som.hibiscus.util.Resettable
import java.util.* import java.util.*
class CommandManager(private val registerDefaultParsers: Boolean = true) { abstract class CommandManager(private val registerDefaultParsers: Boolean = true) : Resettable {
private val parserRegistry = mutableMapOf<Class<*>, ArgumentParser<*>>() private val parserRegistry = mutableMapOf<Class<*>, ArgumentParser<*>>()
val commands = mutableListOf<ExecutableCommand>() private val commands = mutableListOf<ExecutableCommand>()
var context: CommandContext = CommandContext.OTHER var context: CommandContext = CommandContext.OTHER
@ -226,7 +227,7 @@ class CommandManager(private val registerDefaultParsers: Boolean = true) {
branch.aliases.any { it.equals(args[1], ignoreCase = true) })) branch.aliases.any { it.equals(args[1], ignoreCase = true) }))
} }
fun reset() { override fun reset() {
commands.clear() commands.clear()
parserRegistry.clear() parserRegistry.clear()
registerDefaultParsersIfApplicable() registerDefaultParsersIfApplicable()

View File

@ -1,8 +1,9 @@
package codes.som.hibiscus.api.event package codes.som.hibiscus.api.event
import codes.som.hibiscus.util.Resettable
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
class EventBus { class EventBus : Resettable {
private val allListeners = mutableMapOf<EventPhase, MutableMap<Class<*>, MutableList<Listener<*>>>>() private val allListeners = mutableMapOf<EventPhase, MutableMap<Class<*>, MutableList<Listener<*>>>>()
inline fun <reified T : Event> register(listener: Listener<T>, phase: EventPhase) { inline fun <reified T : Event> register(listener: Listener<T>, phase: EventPhase) {
@ -35,4 +36,8 @@ class EventBus {
listeners.forEach { it.on(event) } listeners.forEach { it.on(event) }
} }
} }
override fun reset() {
allListeners.clear()
}
} }

View File

@ -1,6 +1,8 @@
package codes.som.hibiscus.api.keybinds package codes.som.hibiscus.api.keybinds
class KeybindRegistry { import codes.som.hibiscus.util.Resettable
class KeybindRegistry : Resettable {
private val keybinds = mutableMapOf<Int, MutableList<String>>() private val keybinds = mutableMapOf<Int, MutableList<String>>()
fun register(key: Int, command: String) { fun register(key: Int, command: String) {
@ -16,7 +18,7 @@ class KeybindRegistry {
fun getBinds(key: Int): List<String> = fun getBinds(key: Int): List<String> =
keybinds.getOrDefault(key, emptyList()) keybinds.getOrDefault(key, emptyList())
fun reset() { override fun reset() {
keybinds.clear() keybinds.clear()
} }
} }

View File

@ -0,0 +1,23 @@
package codes.som.hibiscus.commands
import codes.som.hibiscus.api.command.Command
import codes.som.hibiscus.api.command.CommandManager
fun allCommandClasses(): Array<() -> Command> = arrayOf(
::Reload,
)
class CommandRegistry : CommandManager() {
init {
registerCommands()
}
private fun registerCommands() {
allCommandClasses().map { it() }.forEach(this::register)
}
override fun reset() {
super.reset()
registerCommands()
}
}

View File

@ -0,0 +1,13 @@
package codes.som.hibiscus.commands
import codes.som.hibiscus.HibiscusMod
import codes.som.hibiscus.api.command.Command
class Reload : Command("reload") {
init {
branch {
HibiscusMod.shutdown()
HibiscusMod.onInitialize()
}
}
}

View File

@ -3,13 +3,14 @@ package codes.som.hibiscus.features
import codes.som.hibiscus.api.feature.Feature import codes.som.hibiscus.api.feature.Feature
import codes.som.hibiscus.features.combat.Criticals import codes.som.hibiscus.features.combat.Criticals
import codes.som.hibiscus.features.exploits.AntiGhost import codes.som.hibiscus.features.exploits.AntiGhost
import codes.som.hibiscus.features.exploits.Ghost
import codes.som.hibiscus.features.movement.Flight import codes.som.hibiscus.features.movement.Flight
import codes.som.hibiscus.features.movement.Speed import codes.som.hibiscus.features.movement.Speed
import codes.som.hibiscus.features.overlay.Overlay import codes.som.hibiscus.features.overlay.Overlay
import codes.som.hibiscus.features.exploits.Ghost
import codes.som.hibiscus.features.player.NoFallDamage import codes.som.hibiscus.features.player.NoFallDamage
import codes.som.hibiscus.util.Resettable
val ALL_FEATURES: Array<() -> Feature> = arrayOf( fun allFeatureClasses(): Array<() -> Feature> = arrayOf(
::NoFallDamage, ::NoFallDamage,
::Flight, ::Flight,
::Overlay, ::Overlay,
@ -19,14 +20,27 @@ val ALL_FEATURES: Array<() -> Feature> = arrayOf(
::AntiGhost, ::AntiGhost,
) )
class FeaturesRegistry { class FeaturesRegistry : Resettable {
private val features = ALL_FEATURES.map { it() }.sortedBy { it.name } private val features = mutableListOf<Feature>()
init {
registerFeatures()
}
fun getAllFeatures() = features.asSequence() fun getAllFeatures() = features.asSequence()
inline fun <reified T : Feature> getFeature() = inline fun <reified T : Feature> getFeature() =
getFeature(T::class.java) getFeature(T::class.java)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T : Feature> getFeature(type: Class<T>) = fun <T : Feature> getFeature(type: Class<T>) =
features.first { it.javaClass == type } as T getAllFeatures().first { it.javaClass == type } as T
private fun registerFeatures() {
features.addAll(allFeatureClasses().map { it() })
}
override fun reset() {
features.clear()
registerFeatures()
}
} }

View File

@ -0,0 +1,5 @@
package codes.som.hibiscus.util
interface Resettable {
fun reset()
}