aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/thedarkcolour
diff options
context:
space:
mode:
authorthedarkcolour <30441001+thedarkcolour@users.noreply.github.com>2020-05-29 12:46:32 -0700
committerthedarkcolour <30441001+thedarkcolour@users.noreply.github.com>2020-05-29 12:46:32 -0700
commitd86d1dab91376ea944de5e9387a20575b50ccf1c (patch)
tree8d58a04667de724e3f2c2825c01448b12d7f7216 /src/main/kotlin/thedarkcolour
parent7f2e66f7dd2efddaf08f329d81c114143bc00cf6 (diff)
downloadKotlinForForge-d86d1dab91376ea944de5e9387a20575b50ccf1c.tar.gz
KotlinForForge-d86d1dab91376ea944de5e9387a20575b50ccf1c.tar.bz2
KotlinForForge-d86d1dab91376ea944de5e9387a20575b50ccf1c.zip
Kotlin for Forge 1.2.2
Diffstat (limited to 'src/main/kotlin/thedarkcolour')
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt60
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt14
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt38
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt62
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt16
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt46
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/package.md2
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt169
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/forge/package.md11
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/kotlin/Kotlin.kt85
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/kotlin/package.md6
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/package.md2
-rw-r--r--src/main/kotlin/thedarkcolour/kotlinforforge/webgenerator/WebGenerator.kt68
13 files changed, 403 insertions, 176 deletions
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
index 547918f..01a10c0 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt
@@ -4,56 +4,60 @@ import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.fml.Logging
import net.minecraftforge.fml.ModContainer
import net.minecraftforge.fml.common.Mod
-import net.minecraftforge.fml.loading.FMLEnvironment
import net.minecraftforge.fml.loading.moddiscovery.ModAnnotation
import net.minecraftforge.forgespi.language.ModFileScanData
import org.objectweb.asm.Type
+import thedarkcolour.kotlinforforge.forge.DIST
import thedarkcolour.kotlinforforge.forge.FORGE_BUS
import thedarkcolour.kotlinforforge.forge.MOD_BUS
-import java.util.*
-import java.util.stream.Collectors
+import thedarkcolour.kotlinforforge.kotlin.enumSet
/**
- * Handles [net.minecraftforge.fml.common.Mod.EventBusSubscriber] annotations for object declarations.
+ * Automatically registers `object` classes to
+ * Kotlin for Forge's event buses.
+ *
+ * @see MOD_BUS
+ * @see FORGE_BUS
*/
-public object AutoKotlinEventBusSubscriber {
+object AutoKotlinEventBusSubscriber {
private val EVENT_BUS_SUBSCRIBER: Type = Type.getType(Mod.EventBusSubscriber::class.java)
+ private val DIST_ENUM_HOLDERS = listOf(
+ ModAnnotation.EnumHolder(null, "CLIENT"),
+ ModAnnotation.EnumHolder(null, "DEDICATED_SERVER")
+ )
/**
- * Registers Kotlin objects and companion objects that are annotated with [Mod.EventBusSubscriber]
- * This allows you to declare an object that subscribes to the event bus
- * without making all the [net.minecraftforge.eventbus.api.SubscribeEvent] annotated with [JvmStatic]
+ * Allows the Mod.EventBusSubscriber annotation
+ * to target member functions of an `object` class.
*
- * Example Usage:
+ * You **must** be using an `object` class, or the
+ * EventBusSubscriber annotation will ignore it.
*
- * @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
- * object ExampleSubscriber {
- * @SubscribeEvent
- * public fun onItemRegistry(event: RegistryEvent.Register<Item>) {
- * println("Look! We're in items!")
- * }
- * }
+ * Personally, I am against using [Mod.EventBusSubscriber]
+ * because it makes
+ *
+ * @sample thedarkcolour.kotlinforforge.ExampleMod
*/
- public fun inject(mod: ModContainer, scanData: ModFileScanData, classLoader: ClassLoader) {
+ 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}")
- val data: ArrayList<ModFileScanData.AnnotationData> = scanData.annotations.stream()
- .filter { annotationData ->
- EVENT_BUS_SUBSCRIBER == annotationData.annotationType
- }
- .collect(Collectors.toList()) as ArrayList<ModFileScanData.AnnotationData>
+
+ val data = scanData.annotations.filter { annotationData ->
+ EVENT_BUS_SUBSCRIBER == annotationData.annotationType
+ }
+
data.forEach { annotationData ->
- val sidesValue: List<ModAnnotation.EnumHolder> = annotationData.annotationData.getOrDefault("value", listOf(ModAnnotation.EnumHolder(null, "CLIENT"), ModAnnotation.EnumHolder(null, "DEDICATED_SERVER"))) as List<ModAnnotation.EnumHolder>
- val sides: EnumSet<Dist> = sidesValue.stream().map { eh -> Dist.valueOf(eh.value) }
- .collect(Collectors.toCollection { EnumSet.noneOf(Dist::class.java) })
+ val sidesValue = annotationData.annotationData.getOrDefault("value", DIST_ENUM_HOLDERS) as List<ModAnnotation.EnumHolder>
+ val sides = enumSet<Dist>().plus(sidesValue.map { eh -> Dist.valueOf(eh.value) })
val modid = annotationData.annotationData.getOrDefault("modid", mod.modId)
- val busTargetHolder: ModAnnotation.EnumHolder = annotationData.annotationData.getOrDefault("bus", ModAnnotation.EnumHolder(null, "FORGE")) as ModAnnotation.EnumHolder
+ val busTargetHolder = annotationData.annotationData.getOrDefault("bus", ModAnnotation.EnumHolder(null, "FORGE")) as ModAnnotation.EnumHolder
val busTarget = Mod.EventBusSubscriber.Bus.valueOf(busTargetHolder.value)
val ktObject = Class.forName(annotationData.classType.className, true, classLoader).kotlin.objectInstance
- if (ktObject != null && mod.modId == modid && sides.contains(FMLEnvironment.dist)) {
+
+ if (ktObject != null && mod.modId == modid && DIST in sides) {
try {
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)
} else {
FORGE_BUS.register(ktObject)
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
index 51840d9..6a60e4b 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt
@@ -1,11 +1,7 @@
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,)".
@@ -13,12 +9,4 @@ import thedarkcolour.kotlinforforge.forge.MOD_BUS
* Make sure to use [KotlinModLoadingContext] instead of [FMLJavaModLoadingContext].
*/
@Mod("kotlinforforge")
-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
+object KotlinForForge \ 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 d393cbd..a06c9a3 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt
@@ -2,35 +2,39 @@ package thedarkcolour.kotlinforforge
import net.minecraftforge.fml.Logging
import net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider
-import net.minecraftforge.forgespi.language.ILifecycleEvent
import net.minecraftforge.forgespi.language.IModInfo
import net.minecraftforge.forgespi.language.IModLanguageProvider
import net.minecraftforge.forgespi.language.ModFileScanData
import java.util.function.Consumer
-import java.util.function.Supplier
-import java.util.stream.Collectors
-public class KotlinLanguageProvider : FMLJavaModLanguageProvider() {
+/**
+ * Reuse a bit of code from FMLJavaModLanguageProvider
+ */
+class KotlinLanguageProvider : FMLJavaModLanguageProvider() {
+ override fun name() = "kotlinforforge"
+
override fun getFileVisitor(): Consumer<ModFileScanData> {
- 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"]}") }
- .map { data -> KotlinModTarget(data.classType.className, data.annotationData["value"] as String) }
- .collect(Collectors.toMap({ target: KotlinModTarget -> target.modId }, { it }, { a, _ -> a }))
- scanResult.addLanguageLoader(target)
+ return Consumer { scanData ->
+ val id2TargetMap = scanData.annotations.filter { data ->
+ data.annotationType == MODANNOTATION
+ }.map { data ->
+ val modid = data.annotationData["value"] as String
+ val modClass = data.classType.className
+ LOGGER.debug(Logging.SCAN, "Found @Mod class $modClass with mod id $modid")
+ modid to KotlinModTarget(modClass)
+ }.toMap()
+
+ scanData.addLanguageLoader(id2TargetMap)
}
}
- override fun <R : ILifecycleEvent<R>?> consumeLifecycleEvent(consumeEvent: Supplier<R>?) {}
-
- override fun name(): String = "kotlinforforge"
-
- public class KotlinModTarget constructor(private val className: String, val modId: String) : IModLanguageProvider.IModLanguageLoader {
+ class KotlinModTarget constructor(private val className: 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)
+ val constructor = ktContainer.declaredConstructors[0]
+
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 9802d90..e86f66a 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt
@@ -5,13 +5,19 @@ import net.minecraftforge.eventbus.api.BusBuilder
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.LifecycleEventProvider.LifecycleEvent
+import net.minecraftforge.fml.Logging
+import net.minecraftforge.fml.ModContainer
+import net.minecraftforge.fml.ModLoadingException
+import net.minecraftforge.fml.ModLoadingStage
import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.forgespi.language.IModInfo
import net.minecraftforge.forgespi.language.ModFileScanData
import thedarkcolour.kotlinforforge.eventbus.KotlinEventBus
+import thedarkcolour.kotlinforforge.kotlin.supply
import java.util.function.Consumer
-import java.util.function.Supplier
+
+typealias LifeCycleEventListener = (LifecycleEvent) -> Unit
/**
* Functions as [net.minecraftforge.fml.javafmlmod.FMLModContainer] for Kotlin
@@ -22,27 +28,34 @@ class KotlinModContainer(private val info: IModInfo, private val className: Stri
init {
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)
- triggerMap[ModLoadingStage.COMMON_SETUP] = dummy().andThen(::fireEvent).andThen(::afterEvent)
- triggerMap[ModLoadingStage.SIDED_SETUP] = dummy().andThen(::fireEvent).andThen(::afterEvent)
- triggerMap[ModLoadingStage.ENQUEUE_IMC] = dummy().andThen(::fireEvent).andThen(::afterEvent)
- 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)
+ triggerMap[ModLoadingStage.CONSTRUCT] = createTrigger(::constructMod, ::afterEvent)
+ triggerMap[ModLoadingStage.CREATE_REGISTRIES] = createTrigger(::fireEvent, ::afterEvent)
+ triggerMap[ModLoadingStage.LOAD_REGISTRIES] = createTrigger(::fireEvent, ::afterEvent)
+ triggerMap[ModLoadingStage.COMMON_SETUP] = createTrigger(::fireEvent, ::afterEvent)
+ triggerMap[ModLoadingStage.SIDED_SETUP] = createTrigger(::fireEvent, ::afterEvent)
+ triggerMap[ModLoadingStage.ENQUEUE_IMC] = createTrigger(::fireEvent, ::afterEvent)
+ triggerMap[ModLoadingStage.PROCESS_IMC] = createTrigger(::fireEvent, ::afterEvent)
+ triggerMap[ModLoadingStage.COMPLETE] = createTrigger(::fireEvent, ::afterEvent)
+ triggerMap[ModLoadingStage.GATHERDATA] = createTrigger(::fireEvent, ::afterEvent)
eventBus = KotlinEventBus(BusBuilder.builder().setExceptionHandler(::onEventFailed).setTrackPhases(false))
- val ctx = KotlinModLoadingContext(this)
- contextExtension = Supplier { ctx }
+ contextExtension = supply(KotlinModLoadingContext(this))
}
- private fun dummy(): Consumer<LifecycleEventProvider.LifecycleEvent> = Consumer {}
+ private inline fun createTrigger(
+ crossinline consumerA: LifeCycleEventListener,
+ crossinline consumerB: LifeCycleEventListener,
+ ): Consumer<LifecycleEvent> {
+ return Consumer { event ->
+ consumerA(event)
+ consumerB(event)
+ }
+ }
private fun onEventFailed(iEventBus: IEventBus, event: Event, iEventListeners: Array<IEventListener>, i: Int, throwable: Throwable) {
LOGGER.error(EventBusErrorMessage(event, i, iEventListeners, throwable))
}
- private fun fireEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
+ private fun fireEvent(lifecycleEvent: LifecycleEvent) {
val event = lifecycleEvent.getOrBuildEvent(this)
LOGGER.debug(Logging.LOADING, "Firing event for modid $modId : $event")
@@ -55,16 +68,16 @@ class KotlinModContainer(private val info: IModInfo, private val className: Stri
}
}
- private fun afterEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
+ private fun afterEvent(lifecycleEvent: LifecycleEvent) {
if (currentState == ModLoadingStage.ERROR) {
LOGGER.error(Logging.LOADING, "An error occurred while dispatching event ${lifecycleEvent.fromStage()} to $modId")
}
}
- private fun constructMod(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {
+ private fun constructMod(lifecycleEvent: LifecycleEvent) {
val modClass: Class<*>
try {
- modClass = Class.forName(className, true, classLoader)
+ modClass = Class.forName(className, false, 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)
@@ -91,14 +104,17 @@ class KotlinModContainer(private val info: IModInfo, private val className: Stri
}
}
- override fun matches(mod: Any?) = mod == modInstance
+ override fun dispatchConfigEvent(event: ModConfig.ModConfigEvent) {
+ eventBus.post(event)
+ }
+
+ override fun matches(mod: Any?): Boolean {
+ return 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 78aaef7..8d0029f 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt
@@ -5,10 +5,10 @@ import thedarkcolour.kotlinforforge.eventbus.KotlinEventBus
import thedarkcolour.kotlinforforge.forge.LOADING_CONTEXT
/**
- * Functions as [net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext] for Kotlin
+ * Mod loading context for mods made with Kotlin for Forge.
*/
class KotlinModLoadingContext constructor(private val container: KotlinModContainer) {
- /**
+ /** @since 1.2.1
* @see thedarkcolour.kotlinforforge.forge.MOD_BUS
*/
fun getKEventBus(): KotlinEventBus {
@@ -16,15 +16,23 @@ class KotlinModLoadingContext constructor(private val container: KotlinModContai
}
/** @since 1.2.1
- * @see thedarkcolour.kotlinforforge.forge.MOD_BUS
- *
* Required to make mods that use an older version of KFF work.
+ *
+ * @see thedarkcolour.kotlinforforge.forge.MOD_BUS
*/
+ @Deprecated(
+ message = "Use the KotlinEventBus version. This will be an error in Kotlin for Forge 1.3",
+ replaceWith = ReplaceWith("getKEventBus()"),
+ level = DeprecationLevel.WARNING,
+ )
fun getEventBus(): IEventBus {
return container.eventBus
}
companion object {
+ /**
+ * Returns the [KotlinModLoadingContext] for the current mod
+ */
fun get(): KotlinModLoadingContext {
return LOADING_CONTEXT.extension()
}
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt
index 7323f23..7bf575e 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/KotlinEventBus.kt
@@ -17,7 +17,7 @@ import java.util.concurrent.atomic.AtomicInteger
import java.util.function.Consumer
/** @since 1.2.0
- * Fixes [IEventBus.addListener] for Kotlin SAM interfaces.
+ * Fixes [addListener] and [addGenericListener] for Kotlin KCallable.
*/
open class KotlinEventBus(builder: BusBuilder, synthetic: Boolean = false) : IEventBus, IEventExceptionHandler {
@Suppress("LeakingThis")
@@ -45,7 +45,7 @@ open class KotlinEventBus(builder: BusBuilder, synthetic: Boolean = false) : IEv
}
}
- protected fun registerClass(clazz: Class<*>) {
+ private fun registerClass(clazz: Class<*>) {
for (method in clazz.methods) {
if (Modifier.isStatic(method.modifiers) && method.isAnnotationPresent(SubscribeEvent::class.java)) {
registerListener(clazz, method, method)
@@ -53,7 +53,7 @@ open class KotlinEventBus(builder: BusBuilder, synthetic: Boolean = false) : IEv
}
}
- protected fun registerObject(target: Any) {
+ private fun registerObject(target: Any) {
val classes = HashSet<Class<*>>()
typesFor(target.javaClass, classes)
Arrays.stream(target.javaClass.methods).filter { m ->
@@ -194,6 +194,8 @@ open class KotlinEventBus(builder: BusBuilder, synthetic: Boolean = false) : IEv
/**
* Add a consumer listener for a [GenericEvent] subclass with generic type [F].
+ * Despite being a new addition in Kotlin for Forge 1.2.x,
+ * this function is backwards compatible with Kotlin for Forge 1.1.x and 1.0.x.
*
* @param consumer Callback to invoke when a matching event is received
* @param T The [GenericEvent] subclass to listen for
@@ -247,6 +249,7 @@ open class KotlinEventBus(builder: BusBuilder, synthetic: Boolean = false) : IEv
addListener(priority, passGenericCancelled(genericClassFilter, receiveCancelled), consumer)
}
+ @Suppress("UNCHECKED_CAST")
private fun <T : Event> addListener(priority: EventPriority, filter: (T) -> Boolean, consumer: Consumer<T>) {
val eventType = reflectKotlinSAM(consumer) as Class<T>?
@@ -272,22 +275,26 @@ open class KotlinEventBus(builder: BusBuilder, synthetic: Boolean = false) : IEv
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
+ when {
+ clazz.simpleName.contains("$\$Lambda$") -> {
+ return TypeResolver.resolveRawArgument(Consumer::class.java, consumer.javaClass)
+ }
+ 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
+ else -> return null
+ }
}
private fun <T : GenericEvent<out F>, F> passGenericCancelled(genericClassFilter: Class<F>, receiveCancelled: Boolean): (T) -> Boolean = { event ->
@@ -314,6 +321,9 @@ open class KotlinEventBus(builder: BusBuilder, synthetic: Boolean = false) : IEv
addListener(priority, passGenericCancelled(genericClassFilter, receiveCancelled), eventType, consumer)
}
+ /**
+ * Removes the specified
+ */
override fun unregister(any: Any?) {
val list = listeners.remove(any) ?: return
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/package.md b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/package.md
new file mode 100644
index 0000000..a9f209d
--- /dev/null
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/eventbus/package.md
@@ -0,0 +1,2 @@
+# thedarkcolour.kotlinforforge.eventbus
+This package contains classes related to the Kotlin for Forge EventBus. \ 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 fe8ee42..a792863 100644
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt
@@ -1,22 +1,32 @@
package thedarkcolour.kotlinforforge.forge
+import net.minecraft.client.Minecraft
+import net.minecraft.util.ResourceLocation
import net.minecraftforge.api.distmarker.Dist
+import net.minecraftforge.api.distmarker.OnlyIn
import net.minecraftforge.common.ForgeConfigSpec
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.eventbus.EventBus
import net.minecraftforge.fml.ModLoadingContext
import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.fml.loading.FMLEnvironment
+import net.minecraftforge.registries.*
import thedarkcolour.kotlinforforge.KotlinModLoadingContext
+import thedarkcolour.kotlinforforge.LOGGER
import thedarkcolour.kotlinforforge.eventbus.KotlinEventBus
import thedarkcolour.kotlinforforge.eventbus.KotlinEventBusWrapper
+import java.util.function.Consumer
+import java.util.function.Predicate
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
/** @since 1.0.0
- * The forge EventBus wrapped in a [KEventBus].
- * Many events that occur during the game are fired on this bus.
+ * The Forge [EventBus].
+ * Many game events are fired on this bus.
*
* @since 1.2.0
* This event bus supports [EventBus.addListener]
+ * and [EventBus.addGenericListener]
* for Kotlin SAM interfaces.
*
* Examples:
@@ -27,11 +37,12 @@ import thedarkcolour.kotlinforforge.eventbus.KotlinEventBusWrapper
val FORGE_BUS = KotlinEventBusWrapper(MinecraftForge.EVENT_BUS as EventBus)
/** @since 1.0.0
- * The mod-specific EventBus.
+ * The mod-specific [EventBus].
* Setup events are typically fired on this bus.
*
* @since 1.2.0
* This event bus supports [EventBus.addListener]
+ * and [EventBus.addGenericListener]
* for Kotlin SAM interfaces.
*
* Examples:
@@ -58,6 +69,14 @@ val LOADING_CONTEXT: ModLoadingContext
*/
val DIST: Dist = FMLEnvironment.dist
+/** @since 1.2.2
+ * The instance of Minecraft.
+ * Make sure to only call this on the client side.
+ */
+val MINECRAFT: Minecraft
+ @OnlyIn(Dist.CLIENT)
+ inline get() = Minecraft.getInstance()
+
/** @since 1.0.0
* An alternative to [net.minecraftforge.fml.DistExecutor.callWhenOn]
* that inlines the callable.
@@ -76,7 +95,7 @@ inline fun <T> callWhenOn(dist: Dist, toRun: () -> T): T? {
/** @since 1.0.0
* An alternative to [net.minecraftforge.fml.DistExecutor.runWhenOn]
- * that uses Kotlin functions instead of Java functional interfaces.
+ * that inlines the runnable.
*/
inline fun runWhenOn(dist: Dist, toRun: () -> Unit) {
if (DIST == dist) {
@@ -86,7 +105,7 @@ inline fun runWhenOn(dist: Dist, toRun: () -> Unit) {
/** @since 1.0.0
* An alternative to [net.minecraftforge.fml.DistExecutor.runForDist]
- * that inlines the method call.
+ * that inlines the function call.
*/
inline fun <T> runForDist(clientTarget: () -> T, serverTarget: () -> T): T {
return when (DIST) {
@@ -104,4 +123,144 @@ fun registerConfig(type: ModConfig.Type, spec: ForgeConfigSpec, fileName: String
} else {
LOADING_CONTEXT.registerConfig(type, spec, fileName)
}
+}
+
+/** @since 1.2.2
+ * Sided delegate with lazy values. This works well with proxies.
+ * It is safe to use client-only types for [clientValue]
+ * and server-only types for [serverValue].
+ *
+ * @param clientValue the value of this property on the client side.
+ * @param serverValue the value of this property on the server side.
+ * @param T the common type of both values. It is recommended to not use [Any] when possible.
+ *
+ * @see sidedDelegate if you'd like a sided value that is computed each time it is accessed
+ */
+fun <T> lazySidedDelegate(clientValue: () -> T, serverValue: () -> T): ReadOnlyProperty<Any?, T> {
+ return LazySidedDelegate(clientValue, serverValue)
+}
+
+/** @since 1.2.2
+ * Sided delegate with values that are evaluated each time they are accessed.
+ * It is safe to use client-only types for [clientValue]
+ * and server-only types for [serverValue].
+ *
+ * @param clientValue the value of this property on the client side.
+ * @param serverValue the value of this property on the server side.
+ * @param T the common type of both values. It is recommended to not use [Any] when possible.
+ */
+fun <T> sidedDelegate(clientValue: () -> T, serverValue: () -> T): ReadOnlyProperty<Any?, T> {
+ return SidedDelegate(clientValue, serverValue)
+}
+
+/** @since 1.2.2
+ * Creates a new [ObjectHolderDelegate] with the specified [registryName].
+ *
+ * This delegate serves as an alternative to using the
+ * `@ObjectHolder` annotation, making it easier to use in Kotlin.
+ */
+inline fun <reified T : IForgeRegistryEntry<T>> objectHolder(registryName: ResourceLocation): ReadOnlyProperty<Any?, T> {
+ return ObjectHolderDelegate(registryName, RegistryManager.ACTIVE.getRegistry(T::class.java) as ForgeRegistry<T>)
+}
+
+/** @since 1.2.2
+ * Creates a new [ObjectHolderDelegate].
+ * This overload uses a string instead of a ResourceLocation.
+ *
+ * This delegate serves as an alternative to using the
+ * `@ObjectHolder` annotation, making it easier to use in Kotlin.
+ */
+inline fun <reified T : IForgeRegistryEntry<T>> objectHolder(registryName: String): ReadOnlyProperty<Any?, T> {
+ return ObjectHolderDelegate(
+ registryName = GameData.checkPrefix(registryName, true),
+ registry = RegistryManager.ACTIVE.getRegistry(T::class.java) as ForgeRegistry<T>
+ )
+}
+
+/** @since 1.2.2
+ * Lazy sided delegate.
+ * Values are initialized lazily.
+ */
+private class LazySidedDelegate<T>(clientValue: () -> T, serverValue: () -> T) : ReadOnlyProperty<Any?, T> {
+ private val clientValue by lazy(clientValue)
+ private val serverValue by lazy(serverValue)
+
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T {
+ return when (DIST) {
+ Dist.CLIENT -> clientValue
+ Dist.DEDICATED_SERVER -> serverValue
+ }
+ }
+}
+
+/** @since 1.2.2
+ * Sided delegate for things like proxies,
+ * or just a null checker for values that only exist on one side.
+ * Values are computed each time they are accessed.
+ */
+private class SidedDelegate<T>(private val clientValue: () -> T, private val serverValue: () -> T) : ReadOnlyProperty<Any?, T> {
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T {
+ return when (DIST) {
+ Dist.CLIENT -> clientValue()
+ Dist.DEDICATED_SERVER -> serverValue()
+ }
+ }
+}
+
+/** @since 1.2.2
+ * An alternative to the `@ObjectHolder` annotation.
+ *
+ * This property delegate is for people who would like to avoid
+ * using annotations all over their non-static Kotlin code.
+ *
+ * This class has proper implementations of
+ * [copy], [hashCode], [equals], and [toString].
+ *
+ * @param T the type of object this delegates to
+ * @property registryName the registry name of the object this delegate references
+ * @property registry the registry the object of this delegate is in
+ * @property value the current value of this object holder.
+ */
+data class ObjectHolderDelegate<T : IForgeRegistryEntry<T>>(
+ private val registryName: ResourceLocation,
+ private val registry: ForgeRegistry<T>,
+) : ReadOnlyProperty<Any?, T>, Consumer<Predicate<ResourceLocation>> {
+ /**
+ * Should be initialized by [accept]. If you don't register
+ * a value for [registryName] during the appropriate registry event
+ * then reading this property is unsafe.
+ */
+ private lateinit var value: T
+
+ init {
+ ObjectHolderRegistry.addHandler(this)
+ }
+
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T {
+ return value
+ }
+
+ /**
+ * Refreshes the value of this ObjectHolder.
+ *
+ * This **does not** account for dummy entries.
+ *
+ * If the [registry] no longer contains [registryName],
+ * the value will remain unchanged.
+ */
+ override fun accept(filter: Predicate<ResourceLocation>) {
+ if (!filter.test(registry.registryName)) {
+ return
+ }
+
+ if (registry.containsKey(registryName)) {
+ val tempValue = registry.getValue(registryName)
+
+ if (tempValue != null) {
+ value = tempValue
+ } else {
+ LOGGER.debug("Unable to lookup value for $this, likely just mod options.")
+ }
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/forge/package.md b/src/main/kotlin/thedarkcolour/kotlinforforge/forge/package.md
new file mode 100644
index 0000000..2fbc66c
--- /dev/null
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/forge/package.md
@@ -0,0 +1,11 @@
+# thedarkcolour.kotlinforforge.forge
+This package contains useful constants and utility functions that improve Forge's compatibility with Kotlin.
+## Constants
+There are constants for the mod event bus, the Forge event bus, Kotlin mod loading context, and
+the current distribution of Minecraft.
+## Functions
+There are inline functions that serve as alternatives to the static methods in DistExecutor which inline the lambdas
+passed in as the parameters. There is also a function to create a configuration file for your mod.
+## Property Delegates
+There are functions to create lazy sided delegates (read only) and to create non-lazy sided delegates (read only) whose values
+are evaluated each time they are accessed.
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/kotlin/Kotlin.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/kotlin/Kotlin.kt
new file mode 100644
index 0000000..faf4d20
--- /dev/null
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/kotlin/Kotlin.kt
@@ -0,0 +1,85 @@
+package thedarkcolour.kotlinforforge.kotlin
+
+import java.util.*
+import java.util.function.Supplier
+
+/**
+ * Returns a supplier that always returns the same value.
+ */
+inline fun <T> supply(value: T): Supplier<T> {
+ return Supplier { value }
+}
+
+/**
+ * Returns an empty new [EnumMap].
+ */
+inline fun <reified K : Enum<K>, V> enumMapOf(): MutableMap<K, V> {
+ return EnumMap(K::class.java)
+}
+
+/**
+ * Returns an new [EnumMap] with the specified contents, given as a list of pairs
+ * where the first component is the key and the second is the value.
+ */
+inline fun <reified K : Enum<K>, V> enumMapOf(vararg pairs: Pair<K, V>): MutableMap<K, V> {
+ return EnumMap<K, V>(K::class.java).apply { putAll(pairs) }
+}
+
+/**
+ * Returns an empty [EnumSet] with the specified element type.
+ */
+inline fun <reified E : Enum<E>> enumSet(): EnumSet<E> {
+ return EnumSet.noneOf(E::class.java)
+}
+
+/**
+ * Creates an enum set initially containing the specified element.
+ *
+ * Overloads of this method exist to initialize an enum set with
+ * one through five elements. A sixth overloading is provided that
+ * uses the varargs feature. This overloading may be used to create
+ * an enum set initially containing an arbitrary number of elements, but
+ * is likely to run slower than the overloads that do not use varargs.
+ */
+inline fun <E : Enum<E>> enumSetOf(e: E): EnumSet<E> {
+ return EnumSet.of(e)
+}
+
+/**
+ * @see enumSetOf
+ */
+inline fun <E : Enum<E>> enumSetOf(e1: E, e2: E): EnumSet<E> {
+ return EnumSet.of(e1, e2)
+}
+
+/**
+ * @see enumSetOf
+ */
+inline fun <E : Enum<E>> enumSetOf(e1: E, e2: E, e3: E): EnumSet<E> {
+ return EnumSet.of(e1, e2, e3)
+}
+
+/**
+ * @see enumSetOf
+ */
+inline fun <E : Enum<E>> enumSetOf(e1: E, e2: E, e3: E, e4: E): EnumSet<E> {
+ return EnumSet.of(e1, e2, e3, e4)
+}
+
+/**
+ * @see enumSetOf
+ */
+inline fun <E : Enum<E>> enumSetOf(e1: E, e2: E, e3: E, e4: E, e5: E): EnumSet<E> {
+ return EnumSet.of(e1, e2, e3, e4, e5)
+}
+
+/**
+ * Creates an enum set initially containing the specified elements.
+ * This factory, whose parameter list uses the varargs feature, may
+ * be used to create an enum set initially containing an arbitrary
+ * number of elements, but it is likely to run slower than the overloads
+ * that do not use varargs.
+ */
+inline fun <E : Enum<E>> enumSetOf(first: E, vararg rest: E): EnumSet<E> {
+ return EnumSet.of(first, *rest)
+} \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/kotlin/package.md b/src/main/kotlin/thedarkcolour/kotlinforforge/kotlin/package.md
new file mode 100644
index 0000000..094648a
--- /dev/null
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/kotlin/package.md
@@ -0,0 +1,6 @@
+# thedarkcolour.kotlinforforge.kotlin
+Since 1.2.2, Kotlin for Forge includes a few extra functions for creating
+collections that are less common than those in ``Collections.kt``.
+
+This package contains various utility functions that aren't really
+related to Minecraft or Minecraft forge, but are still useful in some cases. \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/package.md b/src/main/kotlin/thedarkcolour/kotlinforforge/package.md
new file mode 100644
index 0000000..c912ba2
--- /dev/null
+++ b/src/main/kotlin/thedarkcolour/kotlinforforge/package.md
@@ -0,0 +1,2 @@
+# thedarkcolour.kotlinforforge
+This package contains general classes and objects used to register Kotlin for Forge mods \ No newline at end of file
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/webgenerator/WebGenerator.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/webgenerator/WebGenerator.kt
deleted file mode 100644
index fb0dc56..0000000
--- a/src/main/kotlin/thedarkcolour/kotlinforforge/webgenerator/WebGenerator.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-@file:JvmName("WebGenerator")
-
-package thedarkcolour.kotlinforforge.webgenerator
-
-import org.apache.commons.io.FileUtils
-import org.jsoup.Jsoup
-import org.jsoup.nodes.Attribute
-import org.jsoup.nodes.Attributes
-import org.jsoup.nodes.Document
-import org.jsoup.nodes.Element
-import org.jsoup.parser.Tag
-import java.io.File
-import java.nio.charset.Charset
-
-fun main() = run()
-
-fun run() {
- // val v = Files.newDirectoryStream(File("C:\\Things\\mods\\thedarkcolour.kotlinforforge\\thedarkcolour\\thedarkcolour.kotlinforforge").toPath())
- // val mavenMetadata = File("C:\\Things\\mods\\thedarkcolour.kotlinforforge\\thedarkcolour\\thedarkcolour.kotlinforforge\\maven-metadata.xml")
-
- //val webHtml = Jsoup.parse(File("..\\KotlinForForge\\thedarkcolour\\thedarkcolour.kotlinforforge\\web.html"), null).childNodes()[0]
- //webHtml.childNodes()[2].childNodes()[5].childNodes().filterIsInstance<Element>().forEach(::println)
-
- val thedarkcolour = File("C:\\Things\\mods\\KotlinForForge\\thedarkcolour")
-
- val web = File("C:\\Things\\mods\\KotlinForForge\\thedarkcolour\\web.html")
- val webHtml = Jsoup.parse(web, "UTF-8")
-
- for (file in thedarkcolour.listFiles()!!) {
- if (file.isDirectory) {
- val pre = webHtml.getElementsByAttributeValue("href", "../index.html")
- .parents()
- .first()
- val attr = Attributes().put(Attribute("href", file.absolutePath.replace("${thedarkcolour.absolutePath}\\", "") + "/web.html"))
-
- if (pre.getElementsByAttributeValue("href", attr.get("href")).isEmpty()) {
- pre.appendChild(Element(Tag.valueOf("a"), webHtml.baseUri(), attr))
-
- val innerWeb = File("${file.absolutePath}\\web.html")
- innerWeb.createNewFile()
- }
- }
- }
-
- FileUtils.writeStringToFile(web, webHtml.outerHtml(), Charset.defaultCharset())
-
- /*
- <body>
- <h1>Index of /thedarkcolour.kotlinforforge/</h1>
- <hr>
- <pre><a href="../web.html">../</a>
- <a href="1.0.0/web.html">1.0.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>
- </pre>
- <hr>
- </body>
- */
-}
-
-fun getPre(doc: Document): Element {
- return doc.getElementsByAttributeValue("href", "../web.html")
- .parents()
- .first() ?: doc.getElementsByAttributeValue("href", "../index.html")
- .parents()
- .first()
-} \ No newline at end of file