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)
This commit is contained in:
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
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.api.keybinds.KeybindRegistry
import codes.som.hibiscus.commands.CommandRegistry
import codes.som.hibiscus.events.KeyEvent
import codes.som.hibiscus.features.FeaturesRegistry
import codes.som.hibiscus.gui.ImGuiScreen
@ -23,7 +23,7 @@ object HibiscusMod : ModInitializer {
val bus = EventBus()
val features = FeaturesRegistry()
val commands = CommandManager()
val commands = CommandRegistry()
val keybinds = KeybindRegistry()
override fun onInitialize() {
@ -44,5 +44,15 @@ object HibiscusMod : ModInitializer {
bus.register(NetworkMovingDispatcher(), EventPhase.AFTER)
bus.register(ChatCommandListener())
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.utils.PeekableIterator
import codes.som.hibiscus.api.command.utils.splitExceptingQuotes
import codes.som.hibiscus.util.Resettable
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<*>>()
val commands = mutableListOf<ExecutableCommand>()
private val commands = mutableListOf<ExecutableCommand>()
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) }))
}
fun reset() {
override fun reset() {
commands.clear()
parserRegistry.clear()
registerDefaultParsersIfApplicable()

View file

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

View file

@ -1,6 +1,8 @@
package codes.som.hibiscus.api.keybinds
class KeybindRegistry {
import codes.som.hibiscus.util.Resettable
class KeybindRegistry : Resettable {
private val keybinds = mutableMapOf<Int, MutableList<String>>()
fun register(key: Int, command: String) {
@ -16,7 +18,7 @@ class KeybindRegistry {
fun getBinds(key: Int): List<String> =
keybinds.getOrDefault(key, emptyList())
fun reset() {
override fun reset() {
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.features.combat.Criticals
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.Speed
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.util.Resettable
val ALL_FEATURES: Array<() -> Feature> = arrayOf(
fun allFeatureClasses(): Array<() -> Feature> = arrayOf(
::NoFallDamage,
::Flight,
::Overlay,
@ -19,14 +20,27 @@ val ALL_FEATURES: Array<() -> Feature> = arrayOf(
::AntiGhost,
)
class FeaturesRegistry {
private val features = ALL_FEATURES.map { it() }.sortedBy { it.name }
class FeaturesRegistry : Resettable {
private val features = mutableListOf<Feature>()
init {
registerFeatures()
}
fun getAllFeatures() = features.asSequence()
inline fun <reified T : Feature> getFeature() =
getFeature(T::class.java)
@Suppress("UNCHECKED_CAST")
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()
}