Rewrite for 1.13 (wip)

This commit is contained in:
autaut03 2019-02-12 21:20:49 +02:00
parent 22eb8db077
commit 2cc508fd38
17 changed files with 360 additions and 173 deletions

View File

@ -1,37 +0,0 @@
package net.shadowfacts.forgelin.preloader;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
import java.util.Map;
/**
* @author shadowfacts
*/
public class ForgelinPlugin implements IFMLLoadingPlugin {
@Override
public String[] getASMTransformerClass() {
return new String[0];
}
@Override
public String getModContainerClass() {
return null;
}
@Override
public String getSetupClass() {
return "net.shadowfacts.forgelin.preloader.ForgelinSetup";
}
@Override
public void injectData(Map<String, Object> data) {
}
@Override
public String getAccessTransformerClass() {
return null;
}
}

View File

@ -1,28 +0,0 @@
package net.shadowfacts.forgelin.preloader;
import net.minecraftforge.fml.relauncher.IFMLCallHook;
import java.util.Map;
/**
* @author shadowfacts
*/
public class ForgelinSetup implements IFMLCallHook {
@Override
public void injectData(Map<String, Object> data) {
ClassLoader loader = (ClassLoader)data.get("classLoader");
try {
loader.loadClass("net.shadowfacts.forgelin.KotlinAdapter");
} catch (ClassNotFoundException e) {
// this should never happen
throw new RuntimeException("Couldn't find Forgelin langague adapter, this shouldn't be happening", e);
}
}
@Override
public Void call() throws Exception {
return null;
}
}

View File

@ -0,0 +1,108 @@
package net.shadowfacts.forgelin
import net.minecraftforge.eventbus.EventBusErrorMessage
import net.minecraftforge.eventbus.api.Event
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.eventbus.api.IEventExceptionHandler
import net.minecraftforge.eventbus.api.IEventListener
import net.minecraftforge.fml.*
import net.minecraftforge.fml.Logging.LOADING
import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.forgespi.language.IModInfo
import net.minecraftforge.forgespi.language.ModFileScanData
import org.apache.logging.log4j.LogManager
import java.util.*
import java.util.function.Consumer
class FMLKotlinModContainer(
private val info: IModInfo,
private val className: String,
private val modClassLoader: ClassLoader,
private val scanResults: ModFileScanData
) : ModContainer(info) {
private val log = LogManager.getLogger()
val eventBus: IEventBus
private var mod: Any? = null
private val modClass: Class<*>
init {
log.debug(LOADING, "Creating FMLModContainer instance for {} with classLoader {} & {}", className, modClassLoader, javaClass.classLoader)
triggerMap[ModLoadingStage.CONSTRUCT] = dummy().andThen(::beforeEvent).andThen(::constructMod).andThen(::afterEvent)
triggerMap[ModLoadingStage.CREATE_REGISTRIES] = dummy().andThen(::beforeEvent).andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.LOAD_REGISTRIES] = dummy().andThen(::beforeEvent).andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.COMMON_SETUP] = dummy().andThen(::beforeEvent).andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.SIDED_SETUP] = dummy().andThen(::beforeEvent).andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.ENQUEUE_IMC] = dummy().andThen(::beforeEvent).andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.PROCESS_IMC] = dummy().andThen(::beforeEvent).andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.COMPLETE] = dummy().andThen(::beforeEvent).andThen(::fireEvent).andThen(::afterEvent)
eventBus = IEventBus.create(::onEventFailed)
configHandler = Optional.of<Consumer<ModConfig.ModConfigEvent>>(Consumer { event -> eventBus.post(event) })
try {
// Here, we won't init the class, meaning static {} blocks (init {} in kotlin) won't get triggered
// but we will still have to do it later, on CONSTRUCT phase.
modClass = Class.forName(className, false, modClassLoader)
log.debug(LOADING, "Loaded modclass {} with {}", modClass.name, modClass.classLoader)
} catch (e: Throwable) {
log.error(LOADING, "Failed to load class {}", className, e)
throw ModLoadingException(info, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmodclass", e)
}
}
private fun dummy(): Consumer<LifecycleEventProvider.LifecycleEvent> = Consumer {}
private fun onEventFailed(iEventBus: IEventBus, event: Event, iEventListeners: Array<IEventListener>, i: Int, throwable: Throwable) {
log.error(EventBusErrorMessage(event, i, iEventListeners, throwable))
}
private fun beforeEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
FMLKotlinModLoadingContext.get().activeContainer = this
ModThreadContext.get().activeContainer = this
}
private fun fireEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
val event = lifecycleEvent.getOrBuildEvent(this)
log.debug(LOADING, "Firing event for modid {} : {}", this.getModId(), event)
try {
eventBus.post(event)
log.debug(LOADING, "Fired event for modid {} : {}", this.getModId(), event)
} catch (e: Throwable) {
log.error(LOADING, "Caught exception during event {} dispatch for modid {}", event, this.getModId(), e)
throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.errorduringevent", e)
}
}
private fun afterEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
ModThreadContext.get().activeContainer = null
FMLKotlinModLoadingContext.get().activeContainer = null
if (currentState == ModLoadingStage.ERROR) {
log.error(LOADING, "An error occurred while dispatching event {} to {}", lifecycleEvent.fromStage(), getModId())
}
}
private fun constructMod(event: LifecycleEventProvider.LifecycleEvent) {
try {
log.debug(LOADING, "Loading mod instance {} of type {}", getModId(), modClass.name)
// Now we can load the class, so that static {} block gets called
Class.forName(className)
// Then we check whether it's a kotlin object and return it, or if not we create a new instance of kotlin class.
this.mod = modClass.kotlin.objectInstance ?: modClass.newInstance()
log.debug(LOADING, "Loaded mod instance {} of type {}", getModId(), modClass.name)
} catch (e: Throwable) {
log.error(LOADING, "Failed to create mod instance. ModID: {}, class {}", getModId(), modClass.name, e)
throw ModLoadingException(modInfo, event.fromStage(), "fml.modloading.failedtoloadmod", e, modClass)
}
log.debug(LOADING, "Injecting Automatic event subscribers for {}", getModId())
AutomaticEventSubscriber.inject(this, this.scanResults, this.modClass.classLoader)
//ForgelinAutomaticEventSubscriber.subscribeAutomatic(FMLKotlinModLoadingContext.get().activeContainer, event.asmData, FMLCommonHandler.instance().side)
log.debug(LOADING, "Completed Automatic event subscribers for {}", getModId())
}
override fun matches(mod: Any): Boolean = mod === this.mod
override fun getMod(): Any? = mod
}

View File

@ -0,0 +1,37 @@
package net.shadowfacts.forgelin
import net.minecraftforge.fml.Logging.SCAN
import net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider
import net.minecraftforge.forgespi.language.ILifecycleEvent
import net.minecraftforge.forgespi.language.IModLanguageProvider
import net.minecraftforge.forgespi.language.ModFileScanData
import org.apache.logging.log4j.LogManager
import java.util.function.Consumer
import java.util.function.Supplier
import java.util.stream.Collectors
/**
* Forge {@link ILanguageAdapter} for Kotlin
* Usage: Set the {@code modLanguageAdapter} field in your {@code @Mod} annotation to {@code net.shadowfacts.forgelin.FMLKotlinModLanguageProvider}
* @author shadowfacts
*/
class FMLKotlinModLanguageProvider : IModLanguageProvider {
private val log = LogManager.getLogger()
override fun name(): String {
return "kotlinfml"
}
override fun getFileVisitor(): Consumer<ModFileScanData> {
return Consumer { scanResult ->
val modTargetMap = scanResult.annotations.stream()
.filter { ad -> ad.annotationType == FMLJavaModLanguageProvider.MODANNOTATION }
.peek { ad -> log.debug(SCAN, "Found @Mod class {} with id {}", ad.classType.className, ad.annotationData["value"]) }
.map { ad -> FMLKotlinModTarget(ad.classType.className, ad.annotationData["value"] as String) }
.collect(Collectors.toMap(java.util.function.Function<FMLKotlinModTarget, String> { it.modId }, java.util.function.Function.identity<FMLKotlinModTarget>()))
scanResult.addLanguageLoader(modTargetMap)
}
}
override fun <R : ILifecycleEvent<R>?> consumeLifecycleEvent(consumeEvent: Supplier<R>?) {}
}

View File

@ -0,0 +1,31 @@
package net.shadowfacts.forgelin
import net.minecraftforge.common.ForgeConfigSpec
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.fml.ExtensionPoint
import net.minecraftforge.fml.config.ModConfig
import java.util.function.Supplier
object FMLKotlinModLoadingContext {
private val context = ThreadLocal.withInitial { FMLKotlinModLoadingContext }
var activeContainer: FMLKotlinModContainer? = null
val modEventBus: IEventBus
get() = activeContainer!!.eventBus
fun get(): FMLKotlinModLoadingContext {
return context.get()
}
fun <T> registerExtensionPoint(point: ExtensionPoint<T>, extension: Supplier<T>) {
activeContainer!!.registerExtensionPoint(point, extension)
}
fun registerConfig(type: ModConfig.Type, spec: ForgeConfigSpec) {
activeContainer!!.addConfig(ModConfig(type, spec, activeContainer!!))
}
fun registerConfig(type: ModConfig.Type, spec: ForgeConfigSpec, fileName: String) {
activeContainer!!.addConfig(ModConfig(type, spec, activeContainer, fileName))
}
}

View File

@ -0,0 +1,27 @@
package net.shadowfacts.forgelin
import net.minecraftforge.fml.Logging.LOADING
import net.minecraftforge.forgespi.language.IModInfo
import net.minecraftforge.forgespi.language.IModLanguageProvider
import net.minecraftforge.forgespi.language.ModFileScanData
import org.apache.logging.log4j.LogManager
import java.lang.reflect.InvocationTargetException
class FMLKotlinModTarget(private val className: String, val modId: String) : IModLanguageProvider.IModLanguageLoader {
private val log = LogManager.getLogger()
override fun <T> loadMod(info: IModInfo, modClassLoader: ClassLoader, modFileScanResults: ModFileScanData): T {
// This language class is loaded in the system level classloader - before the game even starts
// So we must treat container construction as an arms length operation, and load the container
// in the classloader of the game - the context classloader is appropriate here.
try {
val fmlContainer = Class.forName("net.shadowfacts.forgelin.FMLKotlinModContainer", true, Thread.currentThread().contextClassLoader)
log.debug(LOADING, "Loading FMLKotlinModContainer from classloader {} - got {}", Thread.currentThread().contextClassLoader, fmlContainer.classLoader)
val constructor = fmlContainer.getConstructor(IModInfo::class.java, String::class.java, ClassLoader::class.java, ModFileScanData::class.java)
return constructor.newInstance(info, className, modClassLoader, modFileScanResults) as T
} catch (e: ReflectiveOperationException) {
log.fatal(LOADING, "Unable to load FMLKotlinModContainer, wut?", e)
throw e
}
}
}

View File

@ -1,25 +1,6 @@
package net.shadowfacts.forgelin
import net.minecraftforge.fml.common.FMLCommonHandler
import net.minecraftforge.fml.common.Loader
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.common.Mod.EventHandler
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent
/**
* @author shadowfacts
*/
@Mod(modid = Forgelin.MOD_ID, name = Forgelin.NAME, version = Forgelin.VERSION, acceptableRemoteVersions = "*", acceptedMinecraftVersions = "*", modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter")
object Forgelin {
const val MOD_ID = "forgelin"
const val NAME = "Forgelin"
const val VERSION = "@VERSION@"
@EventHandler
fun onPreInit(event: FMLPreInitializationEvent) {
Loader.instance().modList.forEach {
ForgelinAutomaticEventSubscriber.subscribeAutomatic(it, event.asmData, FMLCommonHandler.instance().side)
}
}
}
@Mod("forgelin")
object Forgelin

View File

@ -1,7 +1,7 @@
package net.shadowfacts.forgelin
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.Loader
/*import net.minecraftforge.fml.common.Loader
import net.minecraftforge.fml.common.LoaderException
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.common.ModContainer
@ -9,14 +9,14 @@ import net.minecraftforge.fml.common.discovery.ASMDataTable
import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData
import net.minecraftforge.fml.common.discovery.asm.ModAnnotation.EnumHolder
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.Side*/
import org.apache.logging.log4j.LogManager
import java.lang.reflect.Modifier
import java.util.EnumSet
import kotlin.reflect.full.companionObjectInstance
object ForgelinAutomaticEventSubscriber {
private val DEFAULT_SUBSCRIPTION_SIDES = EnumSet.allOf(Side::class.java)
/*private val DEFAULT_SUBSCRIPTION_SIDES = EnumSet.allOf(Side::class.java)
private val LOGGER = LogManager.getLogger(ForgelinAutomaticEventSubscriber::class.java)
private val unregistered = mutableSetOf<Class<*>>()
@ -34,8 +34,8 @@ object ForgelinAutomaticEventSubscriber {
for (containedMod in containedMods) {
val containedModId = containedMod.annotationInfo["modid"] as String
if (containedMod.annotationInfo["modLanguageAdapter"] != KotlinAdapter::class.qualifiedName) {
LOGGER.debug("Skipping @EventBusSubscriber injection for {} since it does not use KotlinAdapter", containedModId)
if (containedMod.annotationInfo["modLanguageAdapter"] != FMLKotlinModLanguageProvider::class.qualifiedName) {
LOGGER.debug("Skipping @EventBusSubscriber injection for {} since it does not use FMLKotlinModLanguageProvider", containedModId)
continue
}
@ -108,5 +108,5 @@ object ForgelinAutomaticEventSubscriber {
return targetSides
}
return DEFAULT_SUBSCRIPTION_SIDES
}
}*/
}

View File

@ -1,41 +0,0 @@
package net.shadowfacts.forgelin
import net.minecraftforge.fml.common.FMLModContainer
import net.minecraftforge.fml.common.ILanguageAdapter
import net.minecraftforge.fml.common.ModContainer
import net.minecraftforge.fml.relauncher.Side
import org.apache.logging.log4j.LogManager
import java.lang.reflect.Field
import java.lang.reflect.Method
/**
* Forge {@link ILanguageAdapter} for Kotlin
* Usage: Set the {@code modLanguageAdapter} field in your {@code @Mod} annotation to {@code net.shadowfacts.forgelin.KotlinAdapter}
* @author shadowfacts
*/
class KotlinAdapter : ILanguageAdapter {
private val log = LogManager.getLogger("KotlinAdapter")
override fun supportsStatics(): Boolean {
return false
}
override fun setProxy(target: Field, proxyTarget: Class<*>, proxy: Any) {
log.debug("Setting proxy: ${target.declaringClass.simpleName}.${target.name} -> $proxy")
// objectInstance is not null if it's a Kotlin object, so set the value on the object
// if it is null, set the value on the static field
target.set(proxyTarget.kotlin.objectInstance, proxy)
}
override fun getNewInstance(container: FMLModContainer, objectClass: Class<*>, classLoader: ClassLoader, factoryMarkedAnnotation: Method?): Any {
log.debug("FML has asked for ${objectClass.simpleName} to be constructed")
return objectClass.kotlin.objectInstance ?: objectClass.newInstance()
}
override fun setInternalProxies(mod: ModContainer?, side: Side?, loader: ClassLoader?) {
// Nothing to do; FML's got this covered for Kotlin
}
}

View File

@ -0,0 +1,52 @@
# This is an example mods.toml file. It contains the data relating to the loading mods.
# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
# The overall format is standard TOML format, v0.5.0.
# Note that there are a couple of TOML lists in this file.
# Find more information on toml format here: https://github.com/toml-lang/toml
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
modLoader="kotlinfml" #mandatory
# A version range to match for said mod loader - for regular FML @Mod it will be the forge version
loaderVersion="[24,)" #mandatory (24 is current forge version)
# A URL to refer people to when problems occur with this mod
issueTrackerURL="https://github.com/shadowfacts/Forgelin/issues" #optional
# A URL for the "homepage" for this mod, displayed in the mod UI
displayURL="https://github.com/shadowfacts/Forgelin" #optional
# A file name (in the root of the mod JAR) containing a logo for display
logoFile="examplemod.png" #optional
# A text field displayed in the mod UI
credits="Emberwalker, for the original Forgelin" #optional
# A text field displayed in the mod UI
authors="shadowfacts" #optional
# A list of mods - how many allowed here is determined by the individual mod loader
[[mods]] #mandatory
# The modid of the mod
modId="forgelin" #mandatory
# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it
version="${file.jarVersion}" #mandatory
# A display name for the mod
displayName="Shadowfacts' Forgelin" #mandatory
# A URL to query for updates for this mod. See the JSON update specification <here>
#updateJSONURL="" #optional
# The description text for the mod (multi line!) (#mandatory)
description='''
Kotlin provider for Forge.
'''
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
[[dependencies.forgelin]] #optional
# the modid of the dependency
modId="forge" #mandatory
# Does this dependency have to exist - if not, ordering below must be specified
mandatory=true #mandatory
# The version range of the dependency
versionRange="[24,)" #mandatory
# An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory
ordering="NONE"
# Side this dependency is applied on - BOTH, CLIENT or SERVER
side="BOTH"
# Here's another dependency
[[dependencies.forgelin]]
modId="minecraft"
mandatory=true
versionRange="[1.13]"
ordering="NONE"
side="BOTH"

View File

@ -0,0 +1 @@
net.shadowfacts.forgelin.FMLKotlinModLanguageProvider

View File

@ -1,14 +0,0 @@
[
{
"modid": "forgelin",
"name": "Shadowfacts' Forgelin",
"version": "$version",
"mcversion": "$mcversion",
"description": "Kotlin helper library for Forge.",
"credits": "Emberwalker, for the original Forgelin",
"url": "https://github.com/shadowfacts/Forgelin",
"updateUrl": "",
"authorList": [ "shadowfacts" ],
"screenshots": []
}
]

View File

@ -0,0 +1,7 @@
{
"pack": {
"description": "Forgelin resources",
"pack_format": 4,
"_comment": "Without this file, Forge crashes on startup."
}
}

View File

@ -1,26 +0,0 @@
package net.shadowfacts.forgelin
import net.minecraftforge.event.entity.player.PlayerInteractEvent
import net.minecraftforge.event.entity.player.PlayerInteractEvent.RightClickBlock
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.common.Mod.EventBusSubscriber
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
@Mod(modid = AutomaticKtSubscriberTest.MODID, modLanguageAdapter = "net.shadowfacts.forgelin.KotlinAdapter")
object AutomaticKtSubscriberTest {
const val MODID = "ktsubtest"
@EventBusSubscriber(modid = AutomaticKtSubscriberTest.MODID)
object EventSubscriber {
@SubscribeEvent
fun onRightClickBlock(event: RightClickBlock) {
println("Automatic KT subscriber: Right click ${event.pos}")
}
@JvmStatic
@SubscribeEvent
fun onRightClickItem(event: PlayerInteractEvent.RightClickItem) {
println("Right click item")
}
}
}

View File

@ -0,0 +1,50 @@
package net.shadowfacts.forgelin
import net.minecraftforge.event.entity.EntityJoinWorldEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.common.Mod
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
@Mod("forgelin_test")
object ForgelinTest {
val logger: Logger = LogManager.getLogger()
init {
// You either need to specify generic type explicitly and use a consumer
FMLKotlinModLoadingContext.get().modEventBus.addListener<FMLCommonSetupEvent> { setup(it) }
// use a consumer with parameter types specified
FMLKotlinModLoadingContext.get().modEventBus.addListener { event: FMLCommonSetupEvent -> setup2(event) }
// or just register whole object and mark needed method with SubscribeEvent annotations.
FMLKotlinModLoadingContext.get().modEventBus.register(this)
}
fun setup(event: FMLCommonSetupEvent) {
logger.info("HELLO from setup")
}
fun setup2(event: FMLCommonSetupEvent) {
logger.info("HELLO from setup2")
}
@SubscribeEvent
fun setup3(event: FMLCommonSetupEvent) {
logger.info("HELLO from setup3")
}
@Mod.EventBusSubscriber
object EventSubscriber {
// doesn't work
@SubscribeEvent
fun testNonStatic(event: EntityJoinWorldEvent) {
logger.info("HELLO from testNonStatic")
}
@JvmStatic
@SubscribeEvent
fun testStatic(event: EntityJoinWorldEvent) {
logger.info("HELLO from testStatic")
}
}
}

View File

@ -0,0 +1,32 @@
modLoader="kotlinfml"
loaderVersion="[24,)"
issueTrackerURL="https://github.com/shadowfacts/Forgelin/issues"
displayURL="https://github.com/shadowfacts/Forgelin"
logoFile="examplemod.png"
credits="Emberwalker, for the original Forgelin"
authors="shadowfacts"
[[mods]]
modId="forgelin_test"
version="${file.jarVersion}"
displayName="Shadowfacts' Forgelin Test"
[[dependencies.forgelin_test]]
modId="forge"
mandatory=true
versionRange="[24,)"
ordering="NONE"
side="BOTH"
[[dependencies.forgelin_test]]
modId="minecraft"
mandatory=true
versionRange="[1.13]"
ordering="NONE"
side="BOTH"
[[dependencies.forgelin_test]]
modId="forgelin"
mandatory=true
ordering="AFTER"
side="BOTH"

View File

@ -0,0 +1,7 @@
{
"pack": {
"description": "Forgelin test resources",
"pack_format": 4,
"_comment": "Without this file, Forge crashes on startup."
}
}