aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--build.gradle9
-rw-r--r--gradle.properties2
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt6
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt17
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt4
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt51
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt17
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt2
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt373
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBusWrapper.kt52
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt33
-rw-r--r--src/main/resources/META-INF/mods.toml2
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jarbin0 -> 13341 bytes
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar.md51
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar.sha11
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jarbin0 -> 91792 bytes
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar.md51
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar.sha11
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom59
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom.md51
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom.sha11
-rw-r--r--thedarkcolour/kotlinforforge/1.2.0/web.html15
-rw-r--r--thedarkcolour/kotlinforforge/maven-metadata.xml3
-rw-r--r--thedarkcolour/kotlinforforge/maven-metadata.xml.md52
-rw-r--r--thedarkcolour/kotlinforforge/maven-metadata.xml.sha12
-rw-r--r--thedarkcolour/kotlinforforge/web.html1
27 files changed, 602 insertions, 61 deletions
diff --git a/README.md b/README.md
index fe29422..620aecc 100644
--- a/README.md
+++ b/README.md
@@ -8,9 +8,12 @@ Makes Kotlin forge-friendly by doing the following:
To implement in your project, paste the following into your build.gradle:
```groovy
buildscript {
+ repositories {
+ maven { url = "https://dl.bintray.com/kotlin/kotlin-eap" }
+ }
dependencies {
// Make sure to use the correct version
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.70"
}
}
@@ -40,7 +43,7 @@ compileKotlin {
}
}
```
-Then, add the following to your mods.toml file:
+Then, change the following to your mods.toml file:
```toml
modLoader="kotlinforforge"
# Change this if you require a certain version of KotlinForForge
diff --git a/build.gradle b/build.gradle
index 20d3c4c..aa1db38 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,7 @@ buildscript {
repositories {
maven { url = 'https://files.minecraftforge.net/maven' }
maven { url = "https://maven.tterrag.com/" }
+ maven { url = "https://dl.bintray.com/kotlin/kotlin-eap" }
jcenter()
mavenCentral()
}
@@ -11,14 +12,13 @@ buildscript {
}
}
plugins {
- id "org.jetbrains.kotlin.jvm" version "1.3.70"
id "com.github.johnrengelman.shadow" version "4.0.4"
}
apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'kotlin'
-version = '1.1.0'
+version = '1.2.0'
group = 'thedarkcolour.kotlinforforge'
archivesBaseName = 'kotlinforforge'
@@ -60,10 +60,15 @@ minecraft {
}
repositories {
+ mavenCentral()
maven {
name = "Yarn Mappings"
url = "https://maven.tterrag.com/"
}
+ maven {
+ name = "Kotlin Early Access"
+ url = "https://dl.bintray.com/kotlin/kotlin-eap"
+ }
}
dependencies {
diff --git a/gradle.properties b/gradle.properties
index 7a70982..c4f4ba4 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,6 +2,6 @@
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
-kotlin_version=1.3.70
+kotlin_version=1.4-M1
coroutines_version = 1.3.4
annotations_version = 19.0.0 \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
index fa43f57..547918f 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
@@ -35,7 +35,7 @@ public object AutoKotlinEventBusSubscriber {
* }
*/
public fun inject(mod: ModContainer, scanData: ModFileScanData, classLoader: ClassLoader) {
- logger.debug(Logging.LOADING, "Attempting to inject @EventBusSubscriber kotlin objects in to the event bus for ${mod.modId}")
+ LOGGER.debug(Logging.LOADING, "Attempting to inject @EventBusSubscriber kotlin objects in to the event bus for ${mod.modId}")
val data: ArrayList<ModFileScanData.AnnotationData> = scanData.annotations.stream()
.filter { annotationData ->
EVENT_BUS_SUBSCRIBER == annotationData.annotationType
@@ -51,7 +51,7 @@ public object AutoKotlinEventBusSubscriber {
val ktObject = Class.forName(annotationData.classType.className, true, classLoader).kotlin.objectInstance
if (ktObject != null && mod.modId == modid && sides.contains(FMLEnvironment.dist)) {
try {
- logger.debug(Logging.LOADING, "Auto-subscribing kotlin object ${annotationData.classType.className} to $busTarget")
+ LOGGER.debug(Logging.LOADING, "Auto-subscribing kotlin object ${annotationData.classType.className} to $busTarget")
if (busTarget == Mod.EventBusSubscriber.Bus.MOD) {
// Gets the correct mod loading context
MOD_BUS.register(ktObject)
@@ -59,7 +59,7 @@ public object AutoKotlinEventBusSubscriber {
FORGE_BUS.register(ktObject)
}
} catch (e: Throwable) {
- logger.fatal(Logging.LOADING, "Failed to load mod class ${annotationData.classType} for @EventBusSubscriber annotation", e)
+ LOGGER.fatal(Logging.LOADING, "Failed to load mod class ${annotationData.classType} for @EventBusSubscriber annotation", e)
throw RuntimeException(e)
}
}
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
index e5ec289..51840d9 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
@@ -1,9 +1,24 @@
package thedarkcolour.kotlinforforge
+import net.minecraft.block.Block
+import net.minecraft.block.material.Material
+import net.minecraftforge.event.RegistryEvent
import net.minecraftforge.fml.common.Mod
+import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext
+import thedarkcolour.kotlinforforge.forge.MOD_BUS
/**
* Set 'modLoader' in mods.toml to "kotlinforforge" and loaderVersion to "[1,)".
+ *
+ * Make sure to use [KotlinModLoadingContext] instead of [FMLJavaModLoadingContext].
*/
@Mod("kotlinforforge")
-public object KotlinForForge \ No newline at end of file
+object KotlinForForge {
+ init {
+ MOD_BUS.addGenericListener(::registerBlocks)
+ }
+
+ private fun registerBlocks(event: RegistryEvent.Register<Block>) {
+ event.registry.register(Block(Block.Properties.create(Material.ROCK)).setRegistryName("bruh"))
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt
index 61705ef..d393cbd 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt
@@ -15,7 +15,7 @@ public class KotlinLanguageProvider : FMLJavaModLanguageProvider() {
return Consumer { scanResult ->
val target = scanResult.annotations.stream()
.filter { data -> data.annotationType == MODANNOTATION }
- .peek { data -> logger.debug(Logging.SCAN, "Found @Mod class ${data.classType.className} with id ${data.annotationData["value"]}") }
+ .peek { data -> LOGGER.debug(Logging.SCAN, "Found @Mod class ${data.classType.className} with id ${data.annotationData["value"]}") }
.map { data -> KotlinModTarget(data.classType.className, data.annotationData["value"] as String) }
.collect(Collectors.toMap({ target: KotlinModTarget -> target.modId }, { it }, { a, _ -> a }))
scanResult.addLanguageLoader(target)
@@ -29,7 +29,7 @@ public class KotlinLanguageProvider : FMLJavaModLanguageProvider() {
public class KotlinModTarget constructor(private val className: String, val modId: String) : IModLanguageProvider.IModLanguageLoader {
override fun <T> loadMod(info: IModInfo, modClassLoader: ClassLoader, modFileScanResults: ModFileScanData): T {
val ktContainer = Class.forName("thedarkcolour.kotlinforforge.KotlinModContainer", true, Thread.currentThread().contextClassLoader)
- logger.debug(Logging.LOADING, "Loading KotlinModContainer from classloader ${Thread.currentThread().contextClassLoader} - got ${ktContainer.classLoader}}")
+ LOGGER.debug(Logging.LOADING, "Loading KotlinModContainer from classloader ${Thread.currentThread().contextClassLoader} - got ${ktContainer.classLoader}}")
val constructor = ktContainer.declaredConstructors[0]//(IModInfo::class.java, String::class.java, ClassLoader::class.java, ModFileScanData::class.java)!!
return constructor.newInstance(info, className, modClassLoader, modFileScanResults) as T
}
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt
index 0ea150f..9802d90 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt
@@ -6,21 +6,22 @@ import net.minecraftforge.eventbus.api.Event
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.eventbus.api.IEventListener
import net.minecraftforge.fml.*
+import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.forgespi.language.IModInfo
import net.minecraftforge.forgespi.language.ModFileScanData
-import java.util.*
+import thedarkcolour.kotlinforforge.eventbus.KotlinEventBus
import java.util.function.Consumer
import java.util.function.Supplier
/**
* Functions as [net.minecraftforge.fml.javafmlmod.FMLModContainer] for Kotlin
*/
-public class KotlinModContainer(private val info: IModInfo, private val className: String, private val classLoader: ClassLoader, private val scanData: ModFileScanData) : ModContainer(info) {
+class KotlinModContainer(private val info: IModInfo, private val className: String, private val classLoader: ClassLoader, private val scanData: ModFileScanData) : ModContainer(info) {
private lateinit var modInstance: Any
- public val eventBus: IEventBus
+ val eventBus: KotlinEventBus
init {
- logger.debug(Logging.LOADING, "Creating KotlinModContainer instance for {} with classLoader {} & {}", className, classLoader, javaClass.classLoader)
+ LOGGER.debug(Logging.LOADING, "Creating KotlinModContainer instance for {} with classLoader {} & {}", className, classLoader, javaClass.classLoader)
triggerMap[ModLoadingStage.CONSTRUCT] = dummy().andThen(::constructMod).andThen(::afterEvent)
triggerMap[ModLoadingStage.CREATE_REGISTRIES] = dummy().andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.LOAD_REGISTRIES] = dummy().andThen(::fireEvent).andThen(::afterEvent)
@@ -30,8 +31,7 @@ public class KotlinModContainer(private val info: IModInfo, private val classNam
triggerMap[ModLoadingStage.PROCESS_IMC] = dummy().andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.COMPLETE] = dummy().andThen(::fireEvent).andThen(::afterEvent)
triggerMap[ModLoadingStage.GATHERDATA] = dummy().andThen(::fireEvent).andThen(::afterEvent)
- eventBus = BusBuilder.builder().setExceptionHandler(::onEventFailed).setTrackPhases(false).build()
- configHandler = Optional.of(Consumer { event -> eventBus.post(event) })
+ eventBus = KotlinEventBus(BusBuilder.builder().setExceptionHandler(::onEventFailed).setTrackPhases(false))
val ctx = KotlinModLoadingContext(this)
contextExtension = Supplier { ctx }
}
@@ -39,25 +39,25 @@ public class KotlinModContainer(private val info: IModInfo, private val classNam
private fun dummy(): Consumer<LifecycleEventProvider.LifecycleEvent> = Consumer {}
private fun onEventFailed(iEventBus: IEventBus, event: Event, iEventListeners: Array<IEventListener>, i: Int, throwable: Throwable) {
- logger.error(EventBusErrorMessage(event, i, iEventListeners, throwable))
+ LOGGER.error(EventBusErrorMessage(event, i, iEventListeners, throwable))
}
private fun fireEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
val event = lifecycleEvent.getOrBuildEvent(this)
- logger.debug(Logging.LOADING, "Firing event for modid $modId : $event")
+ LOGGER.debug(Logging.LOADING, "Firing event for modid $modId : $event")
try {
eventBus.post(event)
- logger.debug(Logging.LOADING, "Fired event for modid $modId : $event")
+ LOGGER.debug(Logging.LOADING, "Fired event for modid $modId : $event")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING,"An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
+ LOGGER.error(Logging.LOADING,"An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.errorduringevent", throwable)
}
}
private fun afterEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
if (currentState == ModLoadingStage.ERROR) {
- logger.error(Logging.LOADING, "An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
+ LOGGER.error(Logging.LOADING, "An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
}
}
@@ -65,41 +65,40 @@ public class KotlinModContainer(private val info: IModInfo, private val classNam
val modClass: Class<*>
try {
modClass = Class.forName(className, true, classLoader)
- logger.debug(Logging.LOADING, "Loaded kotlin modclass ${modClass.name} with ${modClass.classLoader}")
+ LOGGER.debug(Logging.LOADING, "Loaded kotlin modclass ${modClass.name} with ${modClass.classLoader}")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING, "Failed to load kotlin class $className", throwable)
+ LOGGER.error(Logging.LOADING, "Failed to load kotlin class $className", throwable)
throw ModLoadingException(info, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmodclass", throwable)
}
try {
- logger.debug(Logging.LOADING, "Loading mod instance ${getModId()} of type ${modClass.name}")
+ LOGGER.debug(Logging.LOADING, "Loading mod instance ${getModId()} of type ${modClass.name}")
modInstance = modClass.kotlin.objectInstance ?: modClass.newInstance()
- logger.debug(Logging.LOADING, "Loaded mod instance ${getModId()} of type ${modClass.name}")
+ LOGGER.debug(Logging.LOADING, "Loaded mod instance ${getModId()} of type ${modClass.name}")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING, "Failed to create mod instance. ModID: ${getModId()}, class ${modClass.name}", throwable)
+ LOGGER.error(Logging.LOADING, "Failed to create mod instance. ModID: ${getModId()}, class ${modClass.name}", throwable)
throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.failedtoloadmod", throwable, modClass)
}
try {
- logger.debug(Logging.LOADING, "Injecting Automatic Kotlin event subscribers for ${getModId()}")
+ LOGGER.debug(Logging.LOADING, "Injecting Automatic Kotlin event subscribers for ${getModId()}")
// Inject into object EventBusSubscribers
AutoKotlinEventBusSubscriber.inject(this, scanData, modClass.classLoader)
- logger.debug(Logging.LOADING, "Completed Automatic Kotlin event subscribers for ${getModId()}")
+ LOGGER.debug(Logging.LOADING, "Completed Automatic Kotlin event subscribers for ${getModId()}")
} catch (throwable: Throwable) {
- logger.error(Logging.LOADING, "Failed to register Automatic Kotlin subscribers. ModID: ${getModId()}, class ${modClass.name}", throwable)
+ LOGGER.error(Logging.LOADING, "Failed to register Automatic Kotlin subscribers. ModID: ${getModId()}, class ${modClass.name}", throwable)
throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.failedtoloadmod", throwable, modClass)
}
}
- override fun matches(mod: Any?): Boolean {
- return mod == modInstance
- }
-
- override fun getMod(): Any {
- return modInstance
- }
+ override fun matches(mod: Any?) = mod == modInstance
+ override fun getMod() = modInstance
override fun acceptEvent(e: Event) {
eventBus.post(e)
}
+
+ override fun dispatchConfigEvent(event: ModConfig.ModConfigEvent) {
+ eventBus.post(event)
+ }
} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt
index af42d16..44aac2d 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt
@@ -1,19 +1,22 @@
package thedarkcolour.kotlinforforge
-import net.minecraftforge.eventbus.api.IEventBus
-import net.minecraftforge.fml.ModLoadingContext
+import thedarkcolour.kotlinforforge.eventbus.KotlinEventBus
+import thedarkcolour.kotlinforforge.forge.LOADING_CONTEXT
/**
* Functions as [net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext] for Kotlin
*/
-public class KotlinModLoadingContext constructor(private val container: KotlinModContainer) {
- public fun getEventBus(): IEventBus {
+class KotlinModLoadingContext constructor(private val container: KotlinModContainer) {
+ /**
+ * @see thedarkcolour.kotlinforforge.forge.MOD_BUS
+ */
+ fun getEventBus(): KotlinEventBus {
return container.eventBus
}
- public companion object {
- public fun get(): KotlinModLoadingContext {
- return ModLoadingContext.get().extension()
+ companion object {
+ fun get(): KotlinModLoadingContext {
+ return LOADING_CONTEXT.extension()
}
}
} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt
index 8276903..3724657 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/Logger.kt
@@ -8,4 +8,4 @@ import org.apache.logging.log4j.LogManager
* Kept here instead of [KotlinForForge] because logger is used
* before [KotlinModContainer] should initialize.
*/
-internal val logger = LogManager.getLogger() \ No newline at end of file
+internal val LOGGER = LogManager.getLogger() \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt
new file mode 100644
index 0000000..7323f23
--- /dev/null
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt
@@ -0,0 +1,373 @@
+package thedarkcolour.kotlinforforge.eventbus
+
+import net.jodah.typetools.TypeResolver
+import net.minecraftforge.eventbus.ASMEventHandler
+import net.minecraftforge.eventbus.EventBus
+import net.minecraftforge.eventbus.ListenerList
+import net.minecraftforge.eventbus.api.*
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.MarkerManager
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import java.util.*
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.function.Consumer
+
+/** @since 1.2.0
+ * Fixes [IEventBus.addListener] for Kotlin SAM interfaces.
+ */
+open class KotlinEventBus(builder: BusBuilder, synthetic: Boolean = false) : IEventBus, IEventExceptionHandler {
+ @Suppress("LeakingThis")
+ private val exceptionHandler = builder.exceptionHandler ?: this
+ private val trackPhases = builder.trackPhases
+ @Volatile
+ private var shutdown = builder.isStartingShutdown
+ protected open val busID = MAX_ID.getAndIncrement()
+ protected open val listeners = ConcurrentHashMap<Any, MutableList<IEventListener>>()
+
+ init {
+ // see companion object
+ if (!synthetic) {
+ RESIZE_LISTENER_LIST(busID + 1)
+ }
+ }
+
+ override fun register(target: Any) {
+ if (!listeners.containsKey(target)) {
+ if (target.javaClass == Class::class.java) {
+ registerClass(target as Class<*>)
+ } else {
+ registerObject(target)
+ }
+ }
+ }
+
+ protected fun registerClass(clazz: Class<*>) {
+ for (method in clazz.methods) {
+ if (Modifier.isStatic(method.modifiers) && method.isAnnotationPresent(SubscribeEvent::class.java)) {
+ registerListener(clazz, method, method)
+ }
+ }
+ }
+
+ protected fun registerObject(target: Any) {
+ val classes = HashSet<Class<*>>()
+ typesFor(target.javaClass, classes)
+ Arrays.stream(target.javaClass.methods).filter { m ->
+ !Modifier.isStatic(m.modifiers)
+ }.forEach { m ->
+ classes.map { c ->
+ getDeclMethod(c, m)
+ }.firstOrNull { rm ->
+ rm?.isAnnotationPresent(SubscribeEvent::class.java) == true
+ }?.let { rm ->
+ registerListener(target, m, rm)
+ }
+ }
+ }
+
+ private fun typesFor(clz: Class<*>, visited: MutableSet<Class<*>>) {
+ if (clz.superclass == null) return
+ typesFor(clz.superclass, visited)
+ Arrays.stream(clz.interfaces).forEach { typesFor(it, visited) }
+ visited.add(clz)
+ }
+
+ private fun getDeclMethod(clz: Class<*>, m: Method): Method? {
+ return try {
+ clz.getDeclaredMethod(m.name, *m.parameterTypes)
+ } catch (nse: NoSuchMethodException) {
+ null
+ }
+ }
+
+ private fun registerListener(target: Any, f: Method, real: Method) {
+ val params: Array<Class<*>> = f.parameterTypes
+
+ if (params.size != 1) {
+ throw IllegalArgumentException("""
+ Function $f has @SubscribeEvent annotation.
+ It has ${params.size} value parameters,
+ but event handler functions require1 value parameter.
+ """.trimIndent()
+ )
+ }
+
+ val type = params[0]
+
+ if (!Event::class.java.isAssignableFrom(type)) {
+ throw IllegalArgumentException("""
+ Function $f has @SubscribeEvent annotation,
+ but takes an argument that is not an Event subtype : $type
+ """.trimIndent())
+ }
+
+ register(type, target, real)
+ }
+
+ private fun register(type: Class<*>, target: Any, f: Method) {
+ try {
+ val asm = ASMEventHandler(target, f, IGenericEvent::class.java.isAssignableFrom(type))
+
+ addToListeners(target, type, asm.priority, asm)
+ } catch (e: IllegalAccessException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ } catch (e: InstantiationException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ } catch (e: NoSuchMethodException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ } catch (e: InvocationTargetException) {
+ LOGGER.error(EVENT_BUS, "Error registering event handler: $type $f", e)
+ }
+ }
+
+ protected open fun addToListeners(target: Any, eventType: Class<*>, priority: EventPriority, listener: IEventListener) {
+ val listenerList = EventListenerHelper.getListenerList(eventType)
+ listenerList.register(busID, priority, listener)
+ val others = listeners.computeIfAbsent(target) { Collections.synchronizedList(ArrayList()) }
+ others.add(listener)
+ }
+
+ /**
+ * Add a consumer listener with default [EventPriority.NORMAL] and not receiving cancelled events.
+ *
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(consumer: Consumer<T>) {
+ addListener(EventPriority.NORMAL, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and not receiving cancelled events.
+ *
+ * @param priority [EventPriority] for this listener
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(priority: EventPriority, consumer: Consumer<T>) {
+ addListener(priority, false, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and potentially cancelled events.
+ *
+ * @param priority [EventPriority] for this listener
+ * @param receiveCancelled Indicate if this listener should receive events that have been [Cancelable] cancelled
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(priority: EventPriority, receiveCancelled: Boolean, consumer: Consumer<T>) {
+ addListener(priority, passCancelled(receiveCancelled), consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and potentially cancelled events.
+ *
+ * Use this method when one of the other methods fails to determine the concrete [Event] subclass that is
+ * intended to be subscribed to.
+ *
+ * @param priority [EventPriority] for this listener
+ * @param receiveCancelled Indicate if this listener should receive events that have been [Cancelable] cancelled
+ * @param eventType The concrete [Event] subclass to subscribe to
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [Event] subclass to listen for
+ */
+ override fun <T : Event> addListener(priority: EventPriority, receiveCancelled: Boolean, eventType: Class<T>, consumer: Consumer<T>) {
+ addListener(priority, passCancelled(receiveCancelled), eventType, consumer)
+ }
+
+ private fun <T : Event> addListener(priority: EventPriority, filter: (T) -> Boolean, eventType: Class<T>, consumer: Consumer<T>) {
+ addToListeners(consumer, eventType, priority) { e ->
+ if (filter(e as T)) {
+ consumer.accept(e)
+ }
+ }
+ }
+
+ private fun passCancelled(receiveCancelled: Boolean): (Event) -> Boolean = { event ->
+ receiveCancelled || !event.isCancelable || !event.isCanceled
+ }
+
+ /**
+ * Add a consumer listener for a [GenericEvent] subclass with generic type [F].
+ *
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ inline fun <T : GenericEvent<out F>, reified F> addGenericListener(consumer: Consumer<T>) {
+ addGenericListener(F::class.java, consumer)
+ }
+
+ /**
+ * Add a consumer listener for a [GenericEvent] subclass, filtered to only be called for the specified
+ * filter [Class].
+ *
+ * @param genericClassFilter A [Class] which the [GenericEvent] should be filtered for
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ override fun <T : GenericEvent<out F>, F> addGenericListener(genericClassFilter: Class<F>, consumer: Consumer<T>) {
+ addGenericListener(genericClassFilter, EventPriority.NORMAL, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and not receiving cancelled events,
+ * for a [GenericEvent] subclass, filtered to only be called for the specified
+ * filter [Class].
+ *
+ * @param genericClassFilter A [Class] which the [GenericEvent] should be filtered for
+ * @param priority [EventPriority] for this listener
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ override fun <T : GenericEvent<out F>, F> addGenericListener(genericClassFilter: Class<F>, priority: EventPriority, consumer: Consumer<T>) {
+ addGenericListener(genericClassFilter, priority, false, consumer)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and potentially cancelled events,
+ * for a [GenericEvent] subclass, filtered to only be called for the specified
+ * filter [Class].
+ *
+ * @param genericClassFilter A [Class] which the [GenericEvent] should be filtered for
+ * @param priority [EventPriority] for this listener
+ * @param receiveCancelled Indicate if this listener should receive events that have been [Cancelable] cancelled
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ override fun <T : GenericEvent<out F>, F> addGenericListener(genericClassFilter: Class<F>, priority: EventPriority, receiveCancelled: Boolean, consumer: Consumer<T>) {
+ addListener(priority, passGenericCancelled(genericClassFilter, receiveCancelled), consumer)
+ }
+
+ private fun <T : Event> addListener(priority: EventPriority, filter: (T) -> Boolean, consumer: Consumer<T>) {
+ val eventType = reflectKotlinSAM(consumer) as Class<T>?
+
+ if (eventType == null) {
+ LOGGER.error(EVENT_BUS, "Failed to resolve handler for \"$consumer\"")
+ throw IllegalStateException("Failed to resolve KFunction event type: $consumer")
+ }
+ if (eventType == Event::class.java) {
+ LOGGER.warn(EVENT_BUS, """
+ Attempting to add a Lambda listener with computed generic type of Event.
+ Are you sure this is what you meant? NOTE : there are complex lambda forms where
+ the generic type information is erased and cannot be recovered at runtime.
+ """.trimIndent())
+ }
+
+ addListener(priority, filter, eventType, consumer)
+ }
+
+ /**
+ * Fixes issue that crashes when trying to register Kotlin SAM interface
+ * for a [Consumer] using the Java [IEventBus.addListener] method
+ */
+ private fun reflectKotlinSAM(consumer: Consumer<*>): Class<*>? {
+ val clazz = consumer.javaClass
+
+ if (clazz.simpleName.contains("$\$Lambda$")) {
+ return TypeResolver.resolveRawArgument(Consumer::class.java, consumer.javaClass)
+ } else if (clazz.simpleName.contains("\$sam$")) {
+ try {
+ val functionField = clazz.getDeclaredField("function")
+ functionField.isAccessible = true
+ val function = functionField[consumer]
+
+ // Function should have two type parameters (parameter type and return type)
+ return TypeResolver.resolveRawArguments(kotlin.jvm.functions.Function1::class.java, function.javaClass)[0]
+ } catch (e: NoSuchFieldException) {
+ // Kotlin SAM interfaces compile to classes with a "function" field
+ LOGGER.log(Level.FATAL, "Tried to register invalid Kotlin SAM interface: Missing 'function' field")
+ throw e
+ }
+ } else return null
+ }
+
+ private fun <T : GenericEvent<out F>, F> passGenericCancelled(genericClassFilter: Class<F>, receiveCancelled: Boolean): (T) -> Boolean = { event ->
+ event.genericType == genericClassFilter && (receiveCancelled || !event.isCancelable || !event.isCanceled)
+ }
+
+ /**
+ * Add a consumer listener with the specified [EventPriority] and potentially cancelled events,
+ * for a [GenericEvent] subclass, filtered to only be called for the specified
+ * filter [Class].
+ *
+ * Use this method when one of the other methods fails to determine the concrete [GenericEvent] subclass that is
+ * intended to be subscribed to.
+ *
+ * @param genericClassFilter A [Class] which the [GenericEvent] should be filtered for
+ * @param priority [EventPriority] for this listener
+ * @param receiveCancelled Indicate if this listener should receive events that have been [Cancelable] cancelled
+ * @param eventType The concrete [GenericEvent] subclass to subscribe to
+ * @param consumer Callback to invoke when a matching event is received
+ * @param T The [GenericEvent] subclass to listen for
+ * @param F The [Class] to filter the [GenericEvent] for
+ */
+ override fun <T : GenericEvent<out F>, F> addGenericListener(genericClassFilter: Class<F>, priority: EventPriority, receiveCancelled: Boolean, eventType: Class<T>, consumer: Consumer<T>) {
+ addListener(priority, passGenericCancelled(genericClassFilter, receiveCancelled), eventType, consumer)
+ }
+
+ override fun unregister(any: Any?) {
+ val list = listeners.remove(any) ?: return
+
+ for (listener in list) {
+ ListenerList.unregisterAll(busID, listener)
+ }
+ }
+
+ override fun post(event: Event): Boolean {
+ if (shutdown) return false
+
+ val listeners = event.listenerList.getListeners(busID)
+
+ for (index in listeners.indices) {
+ try {
+ if (!trackPhases && listeners[index]::class.java == EventPriority::class.java) {
+ continue
+ } else {
+ listeners[index].invoke(event)
+ }
+ } catch (throwable: Throwable) {
+ exceptionHandler.handleException(this, event, listeners, index, throwable)
+ throw throwable
+ }
+ }
+
+ return event.isCancelable && event.isCanceled
+ }
+
+ override fun handleException(bus: IEventBus, event: Event, listeners: Array<out IEventListener>, index: Int, throwable: Throwable) {
+ LOGGER.error(EVENT_BUS)
+ }
+
+ override fun shutdown() {
+ LOGGER.fatal(EVENT_BUS, "KotlinEventBus $busID shutting down - future events will not be posted.", Exception("stacktrace"))
+ }
+
+ override fun start() {
+ shutdown = false
+ }
+
+ companion object {
+ private val LOGGER = LogManager.getLogger()
+ private val EVENT_BUS = MarkerManager.getMarker("EVENTBUS")
+ private val MAX_ID: AtomicInteger
+ private val RESIZE_LISTENER_LIST: (Int) -> Unit
+
+ init {
+ val maxIDField = EventBus::class.java.getDeclaredField("maxID")
+ maxIDField.isAccessible = true
+ MAX_ID = maxIDField.get(null) as AtomicInteger
+ val resizeMethod = ListenerList::class.java.getDeclaredMethod("resize", Int::class.java)
+ resizeMethod.isAccessible = true
+ RESIZE_LISTENER_LIST = { max -> resizeMethod.invoke(null, max) }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBusWrapper.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBusWrapper.kt
new file mode 100644
index 0000000..b3cb9d5
--- /dev/null
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBusWrapper.kt
@@ -0,0 +1,52 @@
+package thedarkcolour.kotlinforforge.eventbus
+
+import net.minecraftforge.eventbus.EventBus
+import net.minecraftforge.eventbus.api.BusBuilder
+import net.minecraftforge.eventbus.api.IEventBus
+import net.minecraftforge.eventbus.api.IEventExceptionHandler
+import net.minecraftforge.eventbus.api.IEventListener
+import thedarkcolour.kotlinforforge.forge.FORGE_BUS
+import java.util.concurrent.ConcurrentHashMap
+
+/** @since 1.2.0
+ * Fixes [IEventBus.addListener] for Kotlin SAM interfaces
+ * when using [FORGE_BUS].
+ */
+class KotlinEventBusWrapper(private val parent: EventBus) : KotlinEventBus(BusBuilder()
+ .setExceptionHandler(getExceptionHandler(parent))
+ .setTrackPhases(getTrackPhases(parent))
+ .also { if (getShutdown(parent)) it.startShutdown() }
+) {
+ override val busID = getBusID(parent)
+ override val listeners = getListeners(parent)
+
+ // reflection stuff
+ companion object {
+ private val GET_BUS_ID = EventBus::class.java.getDeclaredField("busID").also { it.isAccessible = true }
+ private val GET_LISTENERS = EventBus::class.java.getDeclaredField("listeners").also { it.isAccessible = true }
+ private val GET_EXCEPTION_HANDLER = EventBus::class.java.getDeclaredField("exceptionHandler").also { it.isAccessible = true }
+ private val GET_TRACK_PHASES = EventBus::class.java.getDeclaredField("trackPhases").also { it.isAccessible = true }
+ private val GET_SHUTDOWN = EventBus::class.java.getDeclaredField("shutdown").also { it.isAccessible = true }
+
+ fun getBusID(eventBus: EventBus): Int {
+ return GET_BUS_ID[eventBus] as Int
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ fun getListeners(eventBus: EventBus): ConcurrentHashMap<Any, MutableList<IEventListener>> {
+ return GET_LISTENERS[eventBus] as ConcurrentHashMap<Any, MutableList<IEventListener>>
+ }
+
+ fun getExceptionHandler(eventBus: EventBus): IEventExceptionHandler {
+ return GET_EXCEPTION_HANDLER[eventBus] as IEventExceptionHandler
+ }
+
+ fun getTrackPhases(eventBus: EventBus): Boolean {
+ return GET_TRACK_PHASES[eventBus] as Boolean
+ }
+
+ fun getShutdown(eventBus: EventBus): Boolean {
+ return GET_SHUTDOWN[eventBus] as Boolean
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt
index d712c75..e0b9cd8 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt
@@ -3,34 +3,43 @@ package thedarkcolour.kotlinforforge.forge
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.common.ForgeConfigSpec
import net.minecraftforge.common.MinecraftForge
-import net.minecraftforge.eventbus.api.IEventBus
+import net.minecraftforge.eventbus.EventBus
import net.minecraftforge.fml.ModLoadingContext
import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.fml.loading.FMLEnvironment
import thedarkcolour.kotlinforforge.KotlinModLoadingContext
+import thedarkcolour.kotlinforforge.eventbus.KotlinEventBus
+import thedarkcolour.kotlinforforge.eventbus.KotlinEventBusWrapper
/** @since 1.0.0
- * The forge EventBus.
+ * The forge EventBus wrapped in a [KEventBus].
* Many events that occur during the game are fired on this bus.
*
+ * @since 1.2.0
+ * This event bus supports [EventBus.addListener]
+ * for Kotlin SAM interfaces.
+ *
* Examples:
* @see net.minecraftforge.event.entity.player.PlayerEvent
* @see net.minecraftforge.event.entity.living.LivingEvent
* @see net.minecraftforge.event.world.BlockEvent
*/
-public val FORGE_BUS: IEventBus
- inline get() = MinecraftForge.EVENT_BUS
+val FORGE_BUS = KotlinEventBusWrapper(MinecraftForge.EVENT_BUS as EventBus)
/** @since 1.0.0
* The mod-specific EventBus.
* Setup events are typically fired on this bus.
*
+ * @since 1.2.0
+ * This event bus supports [EventBus.addListener]
+ * for Kotlin SAM interfaces.
+ *
* Examples:
* @see net.minecraftforge.fml.event.lifecycle.InterModProcessEvent
* @see net.minecraftforge.event.AttachCapabilitiesEvent
* @see net.minecraftforge.event.RegistryEvent
*/
-public val MOD_BUS: IEventBus
+val MOD_BUS: KotlinEventBus
inline get() = KotlinModLoadingContext.get().getEventBus()
/** @since 1.0.0
@@ -38,22 +47,22 @@ public val MOD_BUS: IEventBus
*
* Used in place of [net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext]
*/
-public val MOD_CONTEXT: KotlinModLoadingContext
+val MOD_CONTEXT: KotlinModLoadingContext
inline get() = KotlinModLoadingContext.get()
-public val LOADING_CONTEXT: ModLoadingContext
+val LOADING_CONTEXT: ModLoadingContext
inline get() = ModLoadingContext.get()
/** @since 1.0.0
* The current [Dist] of this environment.
*/
-public val DIST: Dist = FMLEnvironment.dist
+val DIST: Dist = FMLEnvironment.dist
/** @since 1.0.0
* An alternative to [net.minecraftforge.fml.DistExecutor.callWhenOn]
* that inlines the callable.
*/
-public inline fun <T> callWhenOn(dist: Dist, toRun: () -> T): T? {
+inline fun <T> callWhenOn(dist: Dist, toRun: () -> T): T? {
return if (DIST == dist) {
try {
toRun()
@@ -69,7 +78,7 @@ public inline fun <T> callWhenOn(dist: Dist, toRun: () -> T): T? {
* An alternative to [net.minecraftforge.fml.DistExecutor.runWhenOn]
* that uses Kotlin functions instead of Java functional interfaces.
*/
-public inline fun runWhenOn(dist: Dist, toRun: () -> Unit) {
+inline fun runWhenOn(dist: Dist, toRun: () -> Unit) {
if (DIST == dist) {
toRun()
}
@@ -79,7 +88,7 @@ public inline fun runWhenOn(dist: Dist, toRun: () -> Unit) {
* An alternative to [net.minecraftforge.fml.DistExecutor.runForDist]
* that inlines the method call.
*/
-public inline fun <T> runForDist(clientTarget: () -> T, serverTarget: () -> T): T {
+inline fun <T> runForDist(clientTarget: () -> T, serverTarget: () -> T): T {
return when (DIST) {
Dist.CLIENT -> clientTarget()
Dist.DEDICATED_SERVER -> serverTarget()
@@ -89,7 +98,7 @@ public inline fun <T> runForDist(clientTarget: () -> T, serverTarget: () -> T):
/** @since 1.0.0
* Registers a config.
*/
-public fun registerConfig(type: ModConfig.Type, spec: ForgeConfigSpec, fileName: String? = null) {
+fun registerConfig(type: ModConfig.Type, spec: ForgeConfigSpec, fileName: String? = null) {
if (fileName == null) {
LOADING_CONTEXT.registerConfig(type, spec)
} else {
diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml
index 7f4e0be..a58fee0 100644
--- a/src/main/resources/META-INF/mods.toml
+++ b/src/main/resources/META-INF/mods.toml
@@ -33,6 +33,6 @@ Kotlin for Forge. Allows mods to use the Kotlin programming language.
[[mods]] #mandatory
displayName="Kotlin for Forge" # Name of mod
modId="kotlinforforge" # Modid
-version="1.0.1" # Version of kotlinforforge
+version="1.2.0" # Version of kotlinforforge
authors="TheDarkColour" # Author
credits="Herobrine knows all." # Credits \ No newline at end of file
diff --git a/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar
new file mode 100644
index 0000000..76079c8
--- /dev/null
+++ b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar
Binary files differ
diff --git a/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar.md5 b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar.md5
new file mode 100644
index 0000000..443462a
--- /dev/null
+++ b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar.md5
@@ -0,0 +1 @@
+4e115fab0fcdc1339b4f38436159e4dc \ No newline at end of file
diff --git a/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar.sha1 b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar.sha1
new file mode 100644
index 0000000..c1a15bc
--- /dev/null
+++ b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0-sources.jar.sha1
@@ -0,0 +1 @@
+0761a02e0ab886dbf69e0c17934bf25c94955df9 \ No newline at end of file
diff --git a/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar
new file mode 100644
index 0000000..dfb36e2
--- /dev/null
+++ b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar
Binary files differ
diff --git a/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar.md5 b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar.md5
new file mode 100644
index 0000000..2e232dc
--- /dev/null
+++ b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar.md5
@@ -0,0 +1 @@
+001b7d99ebbc493f9987215e33e4e3d4 \ No newline at end of file
diff --git a/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar.sha1 b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar.sha1
new file mode 100644
index 0000000..2575efb
--- /dev/null
+++ b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.jar.sha1
@@ -0,0 +1 @@
+9daa903f836e7f80c123ee9ed85440c9c816e836 \ No newline at end of file
diff --git a/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom
new file mode 100644
index 0000000..c814125
--- /dev/null
+++ b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>thedarkcolour</groupId>
+ <artifactId>kotlinforforge</artifactId>
+ <version>1.2.0</version>
+ <repositories>
+ <repository>
+ <id>kt-eap</id>
+ <name>Kotlin Early Access</name>
+ <url>https://dl.bintray.com/kotlin/kotlin-eap</url>
+ </repository>
+ </repositories>
+ <dependencies>
+ <dependency>
+ <groupId>org.jetbrains.kotlin</groupId>
+ <artifactId>kotlin-stdlib</artifactId>
+ <version>1.4-M1</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains.kotlin</groupId>
+ <artifactId>kotlin-stdlib-jdk7</artifactId>
+ <version>1.4-M1</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains.kotlin</groupId>
+ <artifactId>kotlin-stdlib-jdk8</artifactId>
+ <version>1.4-M1</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains.kotlin</groupId>
+ <artifactId>kotlin-reflect</artifactId>
+ <version>1.4-M1</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains</groupId>
+ <artifactId>annotations</artifactId>
+ <version>19.0.0</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains.kotlinx</groupId>
+ <artifactId>kotlinx-coroutines-core</artifactId>
+ <version>1.3.4</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jetbrains.kotlinx</groupId>
+ <artifactId>kotlinx-coroutines-jdk8</artifactId>
+ <version>1.3.4</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom.md5 b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom.md5
new file mode 100644
index 0000000..691a4aa
--- /dev/null
+++ b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom.md5
@@ -0,0 +1 @@
+60d0272477df4f437bab3b43b7ab230a \ No newline at end of file
diff --git a/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom.sha1 b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom.sha1
new file mode 100644
index 0000000..aae0ec9
--- /dev/null
+++ b/thedarkcolour/kotlinforforge/1.2.0/kotlinforforge-1.2.0.pom.sha1
@@ -0,0 +1 @@
+631bbc3d5ac36dd7b132ac159b70f8be423d31a1 \ No newline at end of file
diff --git a/thedarkcolour/kotlinforforge/1.2.0/web.html b/thedarkcolour/kotlinforforge/1.2.0/web.html
new file mode 100644
index 0000000..8a13055
--- /dev/null
+++ b/thedarkcolour/kotlinforforge/1.2.0/web.html
@@ -0,0 +1,15 @@
+<html>
+<head><title>Index of /kotlinforforge/</title></head>
+<body>
+<h1>Index of /kotlinforforge/</h1><hr><pre><a href="../web.html">../</a>
+<a href="kotlinforforge-1.2.0-sources.jar">kotlinforforge-1.1.0-sources.jar</a>
+<a href="kotlinforforge-1.2.0-sources.jar.sha1">kotlinforforge-1.1.0-sources.jar.sha1</a>
+<a href="kotlinforforge-1.2.0-sources.jar.md5">kotlinforforge-1.1.0-sources.jar.md5</a>
+<a href="kotlinforforge-1.2.0.jar">kotlinforforge-1.1.0.jar</a>
+<a href="kotlinforforge-1.2.0.jar.sha1">kotlinforforge-1.1.0.jar.sha1</a>
+<a href="kotlinforforge-1.2.0.jar.md5">kotlinforforge-1.1.0.jar.md5</a>
+<a href="kotlinforforge-1.2.0.pom">kotlinforforge-1.1.0.pom</a>
+<a href="kotlinforforge-1.2.0.pom.sha1">kotlinforforge-1.1.0.pom.sha1</a>
+<a href="kotlinforforge-1.2.0.pom.md5">kotlinforforge-1.1.0.pom.md5</a>
+</pre><hr></body>
+</html>
diff --git a/thedarkcolour/kotlinforforge/maven-metadata.xml b/thedarkcolour/kotlinforforge/maven-metadata.xml
index 0c04ebe..c4f6ff0 100644
--- a/thedarkcolour/kotlinforforge/maven-metadata.xml
+++ b/thedarkcolour/kotlinforforge/maven-metadata.xml
@@ -3,11 +3,12 @@
<groupId>thedarkcolour</groupId>
<artifactId>kotlinforforge</artifactId>
<versioning>
- <release>1.1.0</release>
+ <release>1.2.0</release>
<versions>
<version>1.0.0</version>
<version>1.0.1</version>
<version>1.1.0</version>
+ <version>1.2.0</version>
</versions>
</versioning>
</metadata>
diff --git a/thedarkcolour/kotlinforforge/maven-metadata.xml.md5 b/thedarkcolour/kotlinforforge/maven-metadata.xml.md5
index 6130c88..07eb843 100644
--- a/thedarkcolour/kotlinforforge/maven-metadata.xml.md5
+++ b/thedarkcolour/kotlinforforge/maven-metadata.xml.md5
@@ -1 +1 @@
-3d45a5c05f2ff300b8b07861993468b4 \ No newline at end of file
+f583f067a62b1a9a739d97c05c75f92d \ No newline at end of file
diff --git a/thedarkcolour/kotlinforforge/maven-metadata.xml.sha1 b/thedarkcolour/kotlinforforge/maven-metadata.xml.sha1
index 23c4a72..6c497dc 100644
--- a/thedarkcolour/kotlinforforge/maven-metadata.xml.sha1
+++ b/thedarkcolour/kotlinforforge/maven-metadata.xml.sha1
@@ -1 +1 @@
-0db1ef4d548912f6d6301560ef28c786fe401f30 \ No newline at end of file
+867d0acce00f1d6434d23dd45b4bf9fa3a93e18a \ No newline at end of file
diff --git a/thedarkcolour/kotlinforforge/web.html b/thedarkcolour/kotlinforforge/web.html
index 9aada19..6cc6723 100644
--- a/thedarkcolour/kotlinforforge/web.html
+++ b/thedarkcolour/kotlinforforge/web.html
@@ -5,6 +5,7 @@
<a href="1.0.0/web.html">1.0.0</a>
<a href="1.0.1/web.html">1.0.1</a>
<a href="1.1.0/web.html">1.1.0</a>
+<a href="1.2.0/web.html">1.2.0</a>
<a href="maven-metadata.xml">maven-metadata.xml</a>
<a href="maven-metadata.xml.md5">maven-metadata.xml.md5</a>
<a href="maven-metadata.xml.sha1">maven-metadata.xml.sha1</a>