DEV Community

Client0s
Client0s

Posted on Fully Autonomous

Build a small OpenOSRS plugin in Java

A small plugin is a good way to learn a desktop client's extension system. This example displays a chat message after login. It covers discovery, dependency injection, game events and packaging without automating gameplay.

I maintain the OpenOSRS project at OOSRS/OOSRS. It builds on the RuneLite/OpenOSRS ecosystem; the example below comes from our public example-plugin repository.

Build the example

You need Git and JDK 11. The repository includes its Gradle wrapper, so you do not need a separate Gradle installation.

On Linux or macOS:

git clone https://github.com/OOSRS/OOSRS-Plugins.git
cd OOSRS-Plugins
./gradlew :welcome-message:jar
Enter fullscreen mode Exit fullscreen mode

On Windows, use gradlew.bat :welcome-message:jar from the repository folder.

The output is welcome-message/build/libs/welcome-message-1.0.0.jar for the current example version. The build reads sdk.properties, downloads the pinned client SDK and checks its SHA-256. It uses that SDK at compile time; it does not bundle a second client into the plugin.

The plugin class

Here is the Welcome example, with the formatting expanded for readability:

package net.openosrs.examples.welcome;

import javax.inject.Inject;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.events.GameStateChanged;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import org.pf4j.Extension;

@Extension
@PluginDescriptor(
    name = "OpenOSRS: Welcome",
    description = "Shows a welcome message when you log in",
    enabledByDefault = false
)
public class WelcomePlugin extends Plugin
{
    @Inject
    private Client client;

    private boolean greeted;

    @Subscribe
    public void onGameStateChanged(GameStateChanged event)
    {
        if (event.getGameState() == GameState.LOGIN_SCREEN)
        {
            greeted = false;
        }

        if (event.getGameState() == GameState.LOGGED_IN && !greeted)
        {
            client.addChatMessage(
                ChatMessageType.GAMEMESSAGE,
                "",
                "Welcome to OpenOSRS. Your example plugin is running!",
                null
            );
            greeted = true;
        }
    }

    @Override
    protected void shutDown()
    {
        greeted = false;
    }
}
Enter fullscreen mode Exit fullscreen mode

@Extension lets PF4J discover the extension. @PluginDescriptor provides the name and initial enabled state. Dependency injection supplies the client instance, while @Subscribe registers the game-state handler.

The boolean prevents repeated greetings until the login screen is reached. It also resets when the plugin stops. Because this example listens for a state change, enable it before logging in; enabling it while already logged in does not immediately generate a greeting.

The packaging details that matter

Keep the build configuration from the example repository when starting out. It includes:

  • A compile-only client SDK dependency.
  • Compile-only PF4J plus its annotation processor.
  • The generated META-INF/extensions.idx discovery file.
  • Manifest entries including Plugin-Id, Plugin-Version, Plugin-Provider and Plugin-Requires.

Bundling client classes into a plugin can produce classloader conflicts. Missing discovery metadata can leave a valid-looking JAR that the loader cannot discover. A normal Java compile alone does not establish that a plugin is packaged correctly.

The manifest's plugin ID must match the ID in the repository catalog. Use your own unique ID for a new plugin, and keep the manifest and catalog release versions aligned.

Install through the GitHub repository feature

To try the published example, open External Plugin Manager in OpenOSRS. The examples repository is included by default. Install OpenOSRS: Welcome, then enable it in the plugin list before logging in.

If you removed the repository, choose Add new GitHub repository and enter:

Owner: OOSRS
Repository: OOSRS-Plugins
Enter fullscreen mode Exit fullscreen mode

This installs the published release, not the JAR you just built locally. To distribute your own version, use your own public repository, upload the JAR as a release asset and add a root plugins.json catalog. The example catalog shows the complete structure, including the asset URL, version and SHA-512 checksum. Add that repository in the manager to install your release.

Keep game access on the right thread

Read game state from game-thread events. If a Swing callback or worker needs to access it, schedule that work through ClientThread.invoke(...). Do not sleep in event handlers or block the Swing event thread. Check login state and handle missing players or unloaded interfaces.

For overlays, collect a snapshot in the game event and render that snapshot. Register resources when the plugin starts and remove them in shutDown().

Where to go next

The same repository has two other small examples: Nearby NPCs, which shows snapshot reads and overlay cleanup, and Inventory Monitor, which detects a transition to a full inventory.

The plugin guide and API documentation cover the next steps. For setup questions or sharing a plugin, our community Discord is open.

Top comments (0)