MMO: How did I add asynchronous kotlin scripting to my MMO written directly in tiled

Jul 20 2018

I wanted to define NPCs with simple scripts directly in tiled.

Since I was already defining NPCs in Kotlin, this was purely for convenience of being able to view it visually (a IntelliJ plugin could do the job too). I wanted something like this:

I explored several options: from doing a really simple script engine and asynchronously evaluate each statement. Also evaluated to use http://www.luaj.org/, since LUA is pretty simple, standard and support coroutines too.

But then I realized that Kotlin for JVM was supporting scripting out of the box (kinda) and NPCs run on the server that is written in Ktor and runs on the JVM.Was it possible at all? I decided to try.

I had to do a few adjustments: First, I had to include the kotlin-script-runtime and kotlin-script-util artifacts, and then declaring the javax.scripting ServiceLoader directly in my project (why is kotlin-script-util not declaring it directly?)

So I added the file resources/META-INF/services/javax.script.ScriptEngineFactory with the contents org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory. And with that I was able to use kotlin scripting already.

After that, I wanted to check that I was able to run coroutines since NPC scripts are asynchronous, and to set a this context with the Npc itself, to be able to call its functions to move herself or to say things and do stuff. Since I’m not sure how to do these things properly, I decided to generate a base kts file and inject the script at the place I wanted. So using kotlinx.coroutines launch function, the bindings global, and doing an apply, I was able to inject the NPC object to the script. And guess what? Works like a charm.

It loos like this:

override suspend fun script() {
    ktsEngine.put("npc", this)
    @Language("kotlin-script") // kotlin-script not available yet
    val result = ktsEngine.eval(
        """
            import mmo.server.*
            import com.soywiz.klock.*
            import kotlinx.coroutines.experimental.*
            launch {
                (bindings["npc"] as ${KScriptNpc::class.qualifiedName}).apply {
                    $script
                }
            }
        """
    )
    (result as Job).join()
}

So in 61 lines I was able to create a generic NPC that was defined from inside tiled as custom attributes:

My MMO is translated. Right now texts are put in a txt file in the resources folder and I have a pretty rudimentary script (using Regex instead of parsing with the kotlin-embedded-compiler) that locates all the .kt files with NPCs declarations and searches for functions that contain texts. It works fine so far. With this update, I ended with texts inside maps. So what I did was to use the tiled reader I have with in korge, to read the map, locate the NPCs with scripts, and try to locate strings as if it was a .kt file. And worked!

And that’s it for now. The whole idea is pretty cool and worked without too much problems.

Graphics from Tamara Gadea https://tamy.es/