Files
2009scape/Server/src/main/core/plugin/ClassScanner.kt
T
Ceikry a100affda2 Repository reorganisation
Unified kotlin and java into just src/main
Unified the rs09 and core packages
Took all content out of the core package, and placed it into the new content package
Reorganised all source code relating to content to be easier to find and explore
2023-01-26 13:59:28 +00:00

206 lines
8.4 KiB
Kotlin

package core.plugin
import core.api.*
import core.game.activity.ActivityManager
import core.game.activity.ActivityPlugin
import core.game.node.entity.Entity
import core.game.node.entity.player.info.login.LoginConfiguration
import core.game.node.entity.player.link.quest.Quest
import core.game.node.entity.player.link.quest.QuestRepository
import core.game.world.map.Location
import core.game.world.map.zone.MapZone
import core.game.world.map.zone.ZoneBuilder
import io.github.classgraph.ClassGraph
import io.github.classgraph.ClassInfo
import io.github.classgraph.ScanResult
import core.game.bots.PlayerScripts
import core.game.interaction.InteractionListener
import core.game.interaction.InterfaceListener
import core.game.node.entity.player.info.login.PlayerSaveParser
import core.game.node.entity.player.info.login.PlayerSaver
import core.tools.SystemLogger
import core.tools.SystemLogger.logStartup
import core.game.world.GameWorld
import java.util.function.Consumer
/**
* A class used to reflectively scan the classpath and load classes
* @author Ceikry
*/
object ClassScanner {
var disabledPlugins = HashMap<String, Boolean>()
/**
* The amount of content interfaces loaded.
*/
var amountLoaded = 0
private set
var numPlugins = 0
private set
/**
* The currently loaded plugin names.
*/
private var loadedPlugins: MutableList<String>? = ArrayList()
/**
* The last loaded plugin.
*/
private val lastLoaded: String? = null
lateinit var scanResults: ScanResult
@JvmStatic fun scanClasspath() {
scanResults = ClassGraph().enableClassInfo().enableAnnotationInfo().scan()
}
/**
* Scan the classpath for reflection-loaded content classes such as listeners, "plugins", etc
*/
@JvmStatic
fun loadPureInterfaces() {
try {
loadContentInterfacesFrom(scanResults)
logStartup("Loaded $amountLoaded content interfaces.")
} catch (t: Throwable) {
SystemLogger.logErr(this::class.java, "Error initializing Plugins -> " + t.localizedMessage + " for file -> " + lastLoaded)
t.printStackTrace()
} catch (e: Exception) {
SystemLogger.logErr(this::class.java, "Error initializing Plugins -> " + e.localizedMessage + " for file -> " + lastLoaded)
e.printStackTrace()
}
}
private fun loadContentInterfacesFrom(scanResults: ScanResult) {
scanResults.getClassesImplementing("core.api.ContentInterface").filter { !it.isAbstract }.forEach {
try {
val clazz = it.loadClass().newInstance()
if(clazz is LoginListener) GameWorld.loginListeners.add(clazz)
if(clazz is LogoutListener) GameWorld.logoutListeners.add(clazz)
if(clazz is TickListener) GameWorld.tickListeners.add(clazz)
if(clazz is StartupListener) GameWorld.startupListeners.add(clazz)
if(clazz is ShutdownListener) GameWorld.shutdownListeners.add(clazz)
if(clazz is PersistWorld) GameWorld.worldPersists.add(clazz)
if(clazz is InteractionListener) clazz.defineListeners().also { clazz.defineDestinationOverrides() }
if(clazz is InterfaceListener) clazz.defineInterfaceListeners()
if(clazz is Commands) clazz.defineCommands()
if(clazz is PersistPlayer) {
PlayerSaver.contentHooks.add(clazz)
PlayerSaveParser.contentHooks.add(clazz)
}
if(clazz is MapArea)
{
val zone = object : MapZone(clazz.javaClass.simpleName + "MapArea", true, *clazz.getRestrictions()){
override fun enter(e: Entity?): Boolean {
clazz.areaEnter(e ?: return super.enter(null))
return super.enter(e)
}
override fun leave(e: Entity?, logout: Boolean): Boolean {
clazz.areaLeave(e ?: return super.leave(null, logout), logout)
return super.leave(e, logout)
}
override fun move(e: Entity?, from: Location?, to: Location?): Boolean {
if(e != null && from != null && to != null) clazz.entityStep(e, to, from)
return super.move(e, from, to)
}
}
for(border in clazz.defineAreaBorders()) zone.register(border)
ZoneBuilder.configure(zone)
SystemLogger.logInfo(this::class.java, "Configured zone: ${clazz.javaClass.simpleName + "MapArea"}")
MapArea.zoneMaps[clazz.javaClass.simpleName + "MapArea"] = zone
}
amountLoaded++
} catch (e: Exception) {
SystemLogger.logErr(this::class.java, "Error loading content: ${it.simpleName}, ${e.localizedMessage}")
e.printStackTrace()
}
}
}
@JvmStatic
fun loadSideEffectfulPlugins() {
loadedPlugins!!.clear()
loadPluginsFrom(scanResults)
logStartup("We still have $numPlugins legacy plugins being loaded.")
}
private fun loadPluginsFrom(scanResults: ScanResult) {
scanResults.getClassesWithAnnotation("core.plugin.Initializable").forEach(Consumer { p: ClassInfo ->
try {
val clazz = p.loadClass().getDeclaredConstructor().newInstance()
if (clazz is Plugin<*>) {
definePlugin(clazz)
}
} catch (t: Throwable) {
SystemLogger.logErr(this::class.java, "Failed to load plugin ${p.name}.");
if (t is NoSuchMethodException && p.superclass.simpleName == core.game.dialogue.DialoguePlugin::class.simpleName) {
SystemLogger.logErr(this::class.java,
"Make sure the constructor signature matches " +
"`${p.simpleName}(player: Player? = null) : DialoguePlugin(player)'."
)
}
t.printStackTrace()
}
})
scanResults.getClassesWithAnnotation("core.game.bots.PlayerCompatible").forEach { res ->
val description = res.getAnnotationInfo("core.game.bots.ScriptDescription").parameterValues[0].value as Array<String>
val identifier = res.getAnnotationInfo("core.game.bots.ScriptIdentifier").parameterValues[0].value.toString()
val name = res.getAnnotationInfo("core.game.bots.ScriptName").parameterValues[0].value.toString()
PlayerScripts.identifierMap[identifier] = PlayerScripts.PlayerScript(identifier, description, name, res.loadClass())
}
}
/**
* Defines a list of plugins.
* @param plugins the plugins.
*/
@JvmStatic
fun definePlugins(vararg plugins: Plugin<*>) {
val pluginsLength = plugins.size
for (i in 0 until pluginsLength) {
val p = plugins[i]
definePlugin(p)
}
}
/**
* Defines the plugin.
* @param plugin The plugin.
*/
@JvmStatic
fun definePlugin(plugin: Plugin<*>) {
try {
var manifest = plugin.javaClass.getAnnotation(PluginManifest::class.java)
if (manifest == null) {
manifest = plugin.javaClass.superclass.getAnnotation(PluginManifest::class.java)
} else {
if (disabledPlugins[manifest.name] != null) {
return
}
}
if (manifest == null || manifest.type == PluginType.ACTION) {
plugin.newInstance(null)
} else {
when (manifest.type) {
PluginType.DIALOGUE -> (plugin as core.game.dialogue.DialoguePlugin).init()
PluginType.ACTIVITY -> ActivityManager.register(plugin as ActivityPlugin)
PluginType.LOGIN -> LoginConfiguration.getLoginPlugins().add(plugin as Plugin<Any?>)
PluginType.QUEST -> {
plugin.newInstance(null)
QuestRepository.register(plugin as Quest)
}
else -> SystemLogger.logWarn(this::class.java, "Unknown Manifest: " + manifest.type)
}
}
numPlugins++
} catch (e: Throwable) {
e.printStackTrace()
}
}
}