This branch is for Minecraft versions 1.14.4+. Versions until 1.12.2 can be found in the master branch.
TOC
This is a project to enable modding with Minecraft Forge in Kotlin. This language provider embeds the Kotlin runtime libraries so you don't have to. Out of the box Boxlin supports the same code written with the builtin FML Java language provider. The only difference is that you must replace FMLJavaModLoadingContext
with BoxlinContext
.
Boxlin also provides you with the ability to use Kotlin objects as a replacement for classes in a lot of cases. For example the mod entry can be an object. What I also worked on is the ability to have a function as your entry point.
The convenience of delegates has been provided to configuration entries. More on that here.
If you are just looking for the final jar file you can look on Curse Forge or in the releases tab above.
First of all you have to include and apply the Kotlin gradle plugin. I might look something like this.
buildscript {
repositories {
mavenCentral()
maven { url = 'https://files.minecraftforge.net/maven' }
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '4.+', changing: true
}
}
apply plugin: 'net.minecraftforge.gradle'
// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.5.10
}
Then we have to a a maven repository from where this project is hosted and add Kotlin and Boxlin as a dependency.
repositories {
maven { url = 'https://maven.ocpu.me' }
}
def mcVersion = "1.15.2"
dependencies {
implementation "io.opencubes:boxlin:3.3.0-$mcVersion"
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41'
implementation 'org.jetbrains.kotlin:kotlin-reflect:1.3.41' // Optional
}
Warning: After version 3.1.0 the Minecraft version is a part of the version number.
To use Boxlin we only have to change our mods.toml
file with specifying the modLoader
key boxlin
, and the loaderVersion
key [3,)
.
modLoader="boxlin"
loaderVersion="[3,)"
The entry point (as touched on in the introduction) we can use 1 of 3 methods.
The class method is the "normal" way as this method reflects the Java entry point. If you plan on only using this method of entry you don't even need Boxlin. The instance of your mod retrieved from BoxlinContext.get().instance
or casted to your class like BoxlinContext.get().instance<ExampleMod>()
.
import net.minecraftforge.fml.common.Mod
@Mod("examplemod")
class ExampleMod {
init {
// Your init code like registering event listeners
}
}
The object method has the advantage to the class method that the object is the instance of your mod. You can read up on objects here.
import net.minecraftforge.fml.common.Mod
@Mod("examplemod")
object ExampleMod {
init {
// Your init code like registering event listeners
}
}
With the function you do not even declare a class or object as a entry point it is just a function. This can give you a fully functional based start to your mod. The instance of your mod retrieved from BoxlinContext.get().instance
.
import io.opencubes.boxlin.adapter.FunctionalMod
@FunctionalMod("examplemod")
fun exampleMod() {
// Your init code like registering event listeners
}
Boxlin provides some utility functions and objects to existing code to make it more "Kotlin friendly". What it means is that Boxlin might for instance have a function for making using configuration values more friendly with the use of delegates. The aim is not to have Boxlin be a "core" mod that might introduce functions for rendering, a new configuration system, and so on.
The API for the configurations can be a hassle if you do not want to expose the raw instance of the config value. This can be fixed with Kotlin delegates giving you minimal effort to just hide the instance of the config value and having a configuration with the direct values to strings, booleans, etc.
This is a example in how you could create a configuration object.
import io.opencubes.boxlin.getValue
import io.opencubes.boxlin.setValue // If a delegate is declared with var
import net.minecraftforge.common.ForgeConfigSpec
object MyConfig {
private val builder = ForgeConfigSpec.Builder()
// Lazily build the config when it is needed
val spec: ForgeConfigSpec by lazy {
Machine // Init MyMachine config
builder.build()
}
val sayHello: Boolean by builder.define("sayHello", true)
object MyMachine {
init {
builder.push("myMachine")
}
val maxFE: Float by builder.define("maxFE", 1000.0f)
init {
builder.pop()
}
}
}
Later when registering the config just reference MyConfig.spec
.
If you are creating a proxy with the normal DistExecutor.runForDist
you can run into a really verbose way of specifying it.
val proxy: IProxy = DistExecutor.runForDist(
{ Supplier { ClientProxy() } },
{ Supplier { ServerProxy() } }
)
With a utility function with the same name runForDist
you can shorten it to.
import io.opencubes.boxlin.runForDist
val proxy = runForDist(::ClientProxy, ::ServerProxy)
And it will have the same effect as the one above.