Writing a Java Plugin System

Java has a very useful modules system that allows you to write “plug-ins” for an application using the Reflection API. It can be used for a variety of things and in terms of gaming, it can be used for these:

  • Quests
  • New Gamemodes
  • New Entities

And a lot more stuff, just to give you an idea. Note that this isn’t restricted to gaming, and has even more uses in software engineering itself. There are a few reasons why we would want to use this plugin system. First of all, you don’t need to write your own scripting language or whatever for something huge. Then, its really quick and fast to set up, and it makes a whole lot of sense.

However, there are some disadvantages as well. Since Java uses the JAR format, it can easily be modified without security like obfuscation and hash checking with the internet. Also, if you are going to open your API to developers, you have to make sure public fields are public, private fields are private, and protected fields are protected. Otherwise they get access to a whole bunch of things that can ruin the game. That’s about it for explanations. Now let’s get to the real code.

So, first, we have to make an API. Most of it will probably already have been done. I will be presenting a small quests system in this. In my project, I’ve defined a Quest class as follows:


public class Quest {

	private int id;
	private int position;

        private QuestRunner qr;

        public void init(QuestRunner qr) {
               this.qr = qr;
        }

        public void setID(int id) {
                this.id = id;
        }
	
	public void nextPosition() {
		position++;
	}
	
	public int getID() {
		return id;
	}
	
	public int getPosition() {
		return position;
	}

        public List<Command> nextEvent() {
                ...
                return commands;
        }
	
}

I included a QuestRunner class(shown later) in the Quest file so the class can add events to the event buffer in the QuestRunner. Events in this term means like things to do like display text, move a character here, start another event, start a battle, etc.

For simplicity’s sake, I’ve also defined a QuestController class, that “controls” the quest, or just organizes them:


import java.util.ArrayList;
import java.util.List;

public class QuestController {

	private List<Quest> quests;
	
	public QuestController() {
		quests = new ArrayList<Quest>();
	}
	
	public Quest getQuestWithID(int id) {
		for(Quest q : quests)
			if(q.getID() == id)
				return q;
		return null;
	}
	
}

Now, there’s also a class called QuestRunner, that runs the events produced by the quest:


import java.util.ArrayList;
import java.util.List;

public class QuestRunner {

	private List<Command> eventbuffer;
	
	public QuestRunner() {
		eventbuffer = new ArrayList<Command>();
	}
	
	public void runEvents(List<Command> event) {
		for(Command comm: event) {
			eventbuffer.add(comm);
		}
		event.clear();
	}
	
	public class Command {
		
		private int command;
		private String params;
		
		public Command(int c, String p) {
			this.command = c;
			this.params = p;
		}
		
	}
	
}

I’ve only given enough information in the class so an API can be written with it, not its actual functionality, just to note. Now, I export it as a JAR(not a runnable jar). Unselect all the files, and then I go select only Quest, QuestController, and QuestRunner. This will only make these classes available in the API. So now I export it, and I now have a quest API that can be used in another project.

Note that I the only reason I export as a JAR is for simplicity here. You can export your entire project and the API will have everything available, but usually you’d need to make sure all the visibilities of fields are correct.

So, now, you can make a new project, maybe name it “Quests”. In here, make a new class that extends Quest. Do stuff in it, add to the storyline etc. Now you can export it as a JAR including only what classes you need.

Now, back in your main game, you can make use of this JAR file you exported with a URLClassLoader object. Basically it allows you to open a JAR file and get a class:


URLClassLoader classLoader = URLClassLoader .newInstance(new URL[] { new URL("file:./quests/TheStolenJewel.jar") };

The jar is loaded here, so now we can load it into an actual Quest object using the reflection API!:


Class<?> clazz = classLoader.loadClass("com.jgo.games.quests.MainQuest");
Quest quest = (Quest) clazz.newInstance();
quest.init(questrunner);

Notice that the name of the class must be the same for each quest, but you can do some nifty stuff with config files to change that.

So that’s it! You have a quest object, now you can do whatever you want with it, just like a real class!

Now you can play with this new thing and do it for anything you want! :smiley:

Thanks for reading! =) Tell me if I’ve done any errors, I’ll fix it right away.

The java class loader has the method defineClass(String name,byte[] class, int offset, int len). You could download the class to a byte array and do hash checking or decryption on that.

Yeah, that works as well.

What is the advantage, compared to use the classes as normal library and declare it like usual (not using class loader, just import)?

You don’t have to mess with the original .jar file to make mods.

If you’ve made/used Minecraft mods, you’ll know what an advantage this is.

I don’t play minecraft but I get it. Thanks.

take a look at the ServiceLoader from the JRE.
I think it is a way better plugin System then what you presented.

Just add a plugin Jar to the classpath and without any further code the plugin will be found. In your example, you have to load every Jar by your self.

I also think that an API should only consist out of interfaces^^

The real advantage of systems based on dynamic loading is that you can change behavior at runtime without restart. To do that you need to use one or more classloaders. As an example you can do something like this (Fake mixins by inheritance part) to allow for total conversions. Using a virtual filesystem probably makes a lot of sense as well.

Note: quests are pure data and really shouldn’t need behavior extensions.

You might need some sort of behavior to tell if the quest has been completed.

Well, this was just an example on how to use it. The only reason I showed it was because i use to do Bukkit plugins and I made a working runescape bot before.

Maybe me using quests was a bit broad, but there are many other things that this is capable of. For multiplayer games, it would be a good idea if things were external.

@deepthought: I’d say that for quests to not be boring, you have to be able to add behavior…but that wasn’t my point…my point is where and how.

Take say killing a specific monster/NPC, which might progress a given quest. How is that different from launching a cut-scene or performing any additional uncommon thing? It isn’t. You need to be able to script (potentially in a data-driven way) events that occur.