Skip to content
adelheidsoftwarePublic

About

Build RS3 Lua Plugins with Java and Kotlin.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

70 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Oneiro

Maven Central

Write RuneScape 3 plugins in Java or Kotlin, similar to the way you would write a RuneLite plugin, and get readable Lua for the RS3 Client Plugin API.

Named after the Oneiromancer.

Status: early development. Plugins written in Java or Kotlin compile to Lua and build into the folder and zip that go to Mod.io; what each language supports is in java-subset.md and kotlin-subset.md. Versions will remain unstable until the RS3 API reaches stability at 1.0.0. We will not update this to 1.0.0 until that happens at a minimum.

What it does

RS3 client plugins are Lua 5.4 scripts, which Jagex's moderators review before they are published on Mod.io. Oneiro lets you write them in Java or Kotlin, with the tools you know: Gradle, your IDE, type checking and completion for the whole RS3 API. It compiles your code into Lua that reads as if written by hand, one module per file, with comments carried over, Lua Language Server annotations, and direct calls to the RS3 API. There is no runtime library. Only the Lua is submitted and moderated.

The source keeps the shape of a RuneLite plugin, a Plugin class with @Subscribe functions and a settings interface, and calls the RS3 API by the names it documents. In Java:

package com.example.coins;

import static org.adelheid.oneiro.rs3.Globals.log;

import org.adelheid.oneiro.api.Plugin;
import org.adelheid.oneiro.api.Subscribe;
import org.adelheid.oneiro.rs3.Inventory;
import org.adelheid.oneiro.rs3.InventoryUpdatedEventPayload;
import org.adelheid.oneiro.rs3.config;
import org.adelheid.oneiro.rs3.id;

/** Logs the coins in your backpack whenever the amount changes. */
public final class CoinWatch extends Plugin {

    private CoinSettings settings;
    private int shown = -1;

    @Subscribe
    public void onInventoryUpdated(InventoryUpdatedEventPayload event) {
        // The bank and other inventories send updates too
        if (event.inv.id != id.Inv.RS3_INV || !settings.enabled()) {
            return;
        }
        int coins = Inventory.Find(config.Inv.RS3_INV).GetTotal(config.Obj.RS3_COINS);
        if (coins != shown) {
            shown = coins;
            log("Coins in the backpack: " + coins);
        }
    }
}

In Kotlin:

package com.example.coins

import org.adelheid.oneiro.api.Plugin
import org.adelheid.oneiro.api.Subscribe
import org.adelheid.oneiro.rs3.Globals.log
import org.adelheid.oneiro.rs3.Inventory
import org.adelheid.oneiro.rs3.InventoryUpdatedEventPayload
import org.adelheid.oneiro.rs3.config
import org.adelheid.oneiro.rs3.id

/** Logs the coins in your backpack whenever the amount changes. */
class CoinWatch : Plugin() {

    private lateinit var settings: CoinSettings
    private var shown = -1

    @Subscribe
    fun onInventoryUpdated(event: InventoryUpdatedEventPayload) {
        // The bank and other inventories send updates too
        if (event.inv.id != id.Inv.RS3_INV || !settings.enabled()) {
            return
        }
        val coins = Inventory.Find(config.Inv.RS3_INV).GetTotal(config.Obj.RS3_COINS)
        if (coins != shown) {
            shown = coins
            log("Coins in the backpack: $coins")
        }
    }
}

Either becomes this main.lua:

-- main.lua
-- Logs the coins in your backpack whenever the amount changes.

local CoinSettings = require("src/coin_settings")

local shown = -1

local function onInventoryUpdated(event)
    -- The bank and other inventories send updates too
    if event.inv.id ~= id.Inv.INV or not CoinSettings.enabled() then
        return
    end
    local coins = Inventory.Find(config.Inv.INV):GetTotal(config.Obj.COINS)
    if coins ~= shown then
        shown = coins
        log(formatStr("Coins in the backpack: {}", coins))
    end
end

Event.InventoryUpdated.Subscribe("coin-watch.onInventoryUpdated", onInventoryUpdated)

-- The client calls PluginShutdown when the plugin is disabled, and requires it to be global.
function PluginShutdown()
    Event.InventoryUpdated.Unsubscribe("coin-watch.onInventoryUpdated")
end

The Gradle plugin also writes plugin.json from the build, checks the folder that ships, runs the plugin's Lua tests, zips it for upload, and links it into the beta client's dev_plugins folder. A construct Oneiro cannot translate, or a call that needs a permission the build does not declare, is a compile error that says what to do instead. A call that Jagex's moderators review closely, such as a change to the right-click menu, compiles with a warning that names what they look at.

Getting started

You need:

  • Gradle 9; Oneiro is tested with 9.8.2.
  • JDK 25, which Oneiro compiles with. Gradle finds it when it is installed, whichever Java Gradle itself runs on.
  • For a plugin written in Kotlin: the Kotlin Gradle plugin 2.3.0 or later.
  • To try the plugin in game: the RS3 beta client.

In a new folder named coin-watch, create the Gradle wrapper with gradle wrapper --gradle-version 9.8.2, then build.gradle.kts:

plugins {
    // For a plugin written in Kotlin, add kotlin("jvm") version "2.4.21"
    id("org.adelheid.oneiro") version "0.4.0"
}

version = "1.0.0"

repositories {
    mavenCentral()
}

oneiro {
    stubs {
        // The RS3 API's type information, from which Oneiro generates the RS3 bindings on your machine
        remote("https://github.com/adelheidsoftware/rs3-api-types", "9f151d069fec71348ec19d7f87722d8ff7178e9d")
    }
    plugin {
        name = "Coin Watch"
        contactEmail = "you@example.org"
    }
}

The folder's name is the project's name, and so the plugin's identifier: the name of its zip and of its link in the client. The build needs no settings.gradle.kts, except on a machine without the JDK that Oneiro compiles with, where this one lets Gradle download it:

plugins {
    // Lets Gradle download the JDK that Oneiro compiles with
    id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}

Put CoinWatch.java from above in src/main/java/com/example/coins/, or CoinWatch.kt in src/main/kotlin/com/example/coins/, and its settings interface next to it. As CoinSettings.java:

package com.example.coins;

import org.adelheid.oneiro.api.ConfigGroup;
import org.adelheid.oneiro.api.ConfigItem;

/** Coin Watch's settings. */
@ConfigGroup("coinwatch")
public interface CoinSettings {

    @ConfigItem(keyName = "enabled", name = "Log the coins")
    default boolean enabled() {
        return true;
    }
}

As CoinSettings.kt:

package com.example.coins

import org.adelheid.oneiro.api.ConfigGroup
import org.adelheid.oneiro.api.ConfigItem

/** Coin Watch's settings. */
@ConfigGroup("coinwatch")
interface CoinSettings {

    @ConfigItem(keyName = "enabled", name = "Log the coins")
    fun enabled(): Boolean = true
}

A plugin is written in one language; the build fails when it has both Java and Kotlin sources. A plugin written in Kotlin also needs the Kotlin Analysis API, which JetBrains publishes only to its own repositories. In a build like this one, which declares its repositories in build.gradle.kts, Oneiro's Gradle plugin adds those repositories itself. Only a build that declares its repositories in settings.gradle.kts instead, under dependencyResolutionManagement, adds the JetBrains ones there, as the plugin model shows.

Then build the plugin and link it into the client:

./gradlew build           # compiles, checks, tests and zips the plugin
./gradlew linkDevPlugin   # links it into the client's dev_plugins folder, once

The zip for Mod.io is build/distributions/coin-watch-1.0.0.zip. On Windows, linkDevPlugin finds the beta client's dev_plugins folder on its own; elsewhere, name it in the build: oneiro { devPlugins = file("/path/to/dev_plugins") }.

In the client's console, prf finds the newly linked plugin and pon coin-watch enables it. After each build, prl coin-watch reloads it. When the console shows an error from the plugin, copy it into a file and run ./gradlew explainError --input=console.txt: it shows the lines of your source that the error came from, and what the client's own messages mean.

Documentation

Jagex reviews every plugin before it is published and expects its developer to understand all of its code. Oneiro's Lua is written to be read: read it before you submit.

Building from source

./gradlew build

To build a plugin against a checkout of Oneiro instead of a release, for example to try a change, include the checkout in the plugin's settings.gradle.kts and apply the plugin without a version, as CONTRIBUTING.md describes.

Contributing

Contributions are welcome; CONTRIBUTING.md explains how to build, test and add to the supported languages. Changes are listed in CHANGELOG.md.

Licence

MIT, see LICENSE.

Oneiro is not affiliated with or endorsed by Jagex or RuneLite. RuneScape is a trademark of Jagex Ltd.

About

Build RS3 Lua Plugins with Java and Kotlin.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages