diff options
author | thedarkcolour <30441001+thedarkcolour@users.noreply.github.com> | 2020-05-29 12:46:32 -0700 |
---|---|---|
committer | thedarkcolour <30441001+thedarkcolour@users.noreply.github.com> | 2020-05-29 12:46:32 -0700 |
commit | d86d1dab91376ea944de5e9387a20575b50ccf1c (patch) | |
tree | 8d58a04667de724e3f2c2825c01448b12d7f7216 /src | |
parent | 7f2e66f7dd2efddaf08f329d81c114143bc00cf6 (diff) | |
download | KotlinForForge-d86d1dab91376ea944de5e9387a20575b50ccf1c.tar.gz KotlinForForge-d86d1dab91376ea944de5e9387a20575b50ccf1c.tar.bz2 KotlinForForge-d86d1dab91376ea944de5e9387a20575b50ccf1c.zip |
Kotlin for Forge 1.2.2
Diffstat (limited to 'src')
19 files changed, 526 insertions, 183 deletions
diff --git a/src/example/kotlin/thedarkcolour/kotlinforforge/ExampleMod.kt b/src/example/kotlin/thedarkcolour/kotlinforforge/ExampleMod.kt new file mode 100644 index 0000000..af01493 --- /dev/null +++ b/src/example/kotlin/thedarkcolour/kotlinforforge/ExampleMod.kt @@ -0,0 +1,80 @@ +package thedarkcolour.kotlinforforge + +import net.minecraft.block.Block +import net.minecraftforge.event.RegistryEvent +import net.minecraftforge.eventbus.api.SubscribeEvent +import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.event.server.FMLServerStartingEvent +import thedarkcolour.kotlinforforge.forge.MOD_BUS +import thedarkcolour.kotlinforforge.forge.lazySidedDelegate +import thedarkcolour.kotlinforforge.proxy.ClientProxy +import thedarkcolour.kotlinforforge.proxy.IProxy +import thedarkcolour.kotlinforforge.proxy.ServerProxy + +/** + * Example mod for anyone who'd like to see + * how a mod would be made with Kotlin for Forge. + * + * This mod has a modid of "examplemod", listens + * for the ``RegistryEvent.Register<Block>`` and + * for the ``FMLServerStartingEvent``. + * + * It registers event listeners by adding event listeners + * directly to the event buses KFF provides and + * by using the ``@EventBusSubscriber`` annotation. + */ +@Mod(ExampleMod.ID) +object ExampleMod { + /** + * Your mod's ID + */ + const val ID = "examplemod" + + /** + * The sided proxy. Since we use a lazy sided delegate, + * the supplier parameters are invoked only once. + */ + private val proxy by lazySidedDelegate(::ClientProxy, ::ServerProxy) + + /** + * Example of using the KotlinEventBus + * to register a function reference. + * + * Event classes with a generic type + * should be registered using ``addGenericListener`` + * instead of ``addListener``. + */ + init { + MOD_BUS.addGenericListener(::registerBlocks) + + proxy.modConstruction() + } + + /** + * Handle block registry here. + */ + private fun registerBlocks(event: RegistryEvent.Register<Block>) { + // ... + } + + /** + * Example of an object class using the + * ``@Mod.EventBusSubscriber`` annotation + * to automatically subscribe functions + * to the forge event bus. + * + * Even though the ``Bus.FORGE`` event bus + * is default, I think that it's still + * a good practice to specify the bus explicitly. + */ + @Mod.EventBusSubscriber(modid = ExampleMod.ID, bus = Mod.EventBusSubscriber.Bus.FORGE) + object EventHandler { + /** + * Handles things like registering commands. + */ + @SubscribeEvent + fun onServerStarting(event: FMLServerStartingEvent) { + // ... + } + } +}
\ No newline at end of file diff --git a/src/example/kotlin/thedarkcolour/kotlinforforge/package.md b/src/example/kotlin/thedarkcolour/kotlinforforge/package.md new file mode 100644 index 0000000..c9c1caa --- /dev/null +++ b/src/example/kotlin/thedarkcolour/kotlinforforge/package.md @@ -0,0 +1,7 @@ +# thedarkcolour.kotlinforforge +This package contains an example main mod class +for a mod using Kotlin for Forge. + +## ExampleMod +Your main mod class should be an object declaration. +It must be annotated with the @Mod annotation.
\ No newline at end of file diff --git a/src/example/kotlin/thedarkcolour/kotlinforforge/proxy/Proxies.kt b/src/example/kotlin/thedarkcolour/kotlinforforge/proxy/Proxies.kt new file mode 100644 index 0000000..24c51b7 --- /dev/null +++ b/src/example/kotlin/thedarkcolour/kotlinforforge/proxy/Proxies.kt @@ -0,0 +1,20 @@ +package thedarkcolour.kotlinforforge.proxy + +/** + * Common inheritor of both proxies. + */ +interface IProxy { + fun modConstruction() +} + +class ClientProxy : IProxy { + override fun modConstruction() { + // run client code + } +} + +class ServerProxy : IProxy { + override fun modConstruction() { + // run server code + } +}
\ No newline at end of file diff --git a/src/example/kotlin/thedarkcolour/kotlinforforge/proxy/package.md b/src/example/kotlin/thedarkcolour/kotlinforforge/proxy/package.md new file mode 100644 index 0000000..bb57203 --- /dev/null +++ b/src/example/kotlin/thedarkcolour/kotlinforforge/proxy/package.md @@ -0,0 +1,12 @@ +# thedarkcolour.kotlinforforge.proxy +This package has example proxy classes. +Proxies are used to provide common declarations with sided implementations. + +Forge no longer supports the proxy pattern. +The ``@SidedProxy`` annotation was removed in 1.13+. +This example shows a use case for the ``lazySidedDelegate``. +It is recommended to use the ``runWhenOn`` and ``callWhenOn`` functions +instead of proxies whenever possible. + +In this example, a proxy is instantiated lazily in the ``ExampleMod`` class. +Proxies are not the only use for sided delegates.
\ 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 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 diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index a58fee0..aab060a 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -3,11 +3,9 @@ loaderVersion="[1,)" # IModLanguageProvider version issueTrackerURL="https://github.com/thedarkcolour/Future-MC/issues" # Issues page -#displayURL="https://minecraft.curseforge.com/projects/kotlinforforge" #optional - description=''' Kotlin for Forge. Allows mods to use the Kotlin programming language. -''' # A description +''' # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. [[dependencies.kotlinforforge]] #optional @@ -33,6 +31,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.2.0" # Version of kotlinforforge +version="1.2.2" # Version of kotlinforforge authors="TheDarkColour" # Author credits="Herobrine knows all." # Credits
\ No newline at end of file diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 167eba8..983d48a 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,7 +1,6 @@ { "pack": { - "description": "kotlinforforge resources", - "pack_format": 4, - "_comment": "A pack_format of 4 requires json lang files. Note: we require v4 pack meta for all mods." + "description": "Kotlin for Forge resources", + "pack_format": 4 } }
\ No newline at end of file |