For my Minecraft server, I often find myself wanting to add a little feature here and there, additions that don’t yet exist or are too minor to bother with searching for a pre-existing plugin. For covering the “last of the mile” and sometimes a little more, I use a plugin specific for my server, but because it’s a little of “everything,” I can’t write it like one big plugin — that makes it difficult for me to maintain it. On the other hand, I don’t want to create a new Bukkit plugin for every little feature because that requires a lot of work to maintain all of them. To fix this problem, my plugin, Rebar (previously gone by with other names), is a little framework within itself that it helps me add new things very quickly, with little overhead and little effort. This is similar to some Python plugin frameworks that I wrote a long time ago, except Python provides you with much more freedom ;)
Components
Pretty much everything is a component. Components know how to initialize themselves as well as shutdown their tasks. They are very light — most components are confined to only one class. When components are loaded, “loader helpers” help initialize the component by automatically linking resources within the class (loader helpers themselves are extensible!). For example, say I want to store sessions in this one component — I want to remember where the last person performed a command, but I only want to keep the data while the user is still logged in. There’s a component for that, and selecting it for this new component is easy with the @InjectComponent helper:
@InjectComponent private Sessions sessionsManager;
// ...
}
What if I need to access another plugin?
@InjectPlugin(WorldGuardPlugin.class)
private LazyPluginReference<WorldGuardPlugin> worldguard;
// ...
}
Because plugins can possibly be unloaded or be made unavailable any time (as opposed to components, which are not outside the world of Rebar), they are accessed through a proxy LazyPluginReference class. We have to specify the class of the plugin in the annotation, sadly, because generics are only known at compile time in Java.
Enabling components is a matter of placing a line in a configuration file. I run two separate servers, a survival and a creative, so often I do not use the same components on both servers.
- com.sk89q.skcraft.users.UserManager
- com.sk89q.skcraft.FancyName
- com.sk89q.skcraft.GameMechanics
- com.sk89q.skcraft.BetterVehicles
# ...
Events
Handling events is the bread and butter of most of the features. Those familiar with the event API in Bukkit know that it’s not a particularly spectacular piece of code though. There’s a lot of code duplicated everywhere, it takes a lot of code to even initiate an event, registering an event handler is a pain, and custom events are a complete joke. I get around some of the issues by using annotations:
@BukkitEvent(type = Event.Type.BLOCK_BREAK)
public void postInitialize() {
Rebar.getInstance().registerEvents(new BlockListener());
}
// ...
private class BlockListener extends org.bukkit.event.block.BlockListener {
@BukkitEvent(type = Event.Type.BLOCK_BREAK)
public void onBlockBreak(BlockBreakEvent event) {
// Process event
}
}
}
Priorities are optionally specified, but it defaults to “normal.”
Custom Events
Rebar has its own improved and fast event system for custom events, and registering event handlers is even the same as the code above. (Java reflection is not at all used except during initialization.) Below is an example of a custom event to check whether a block can be modified. Because the build protection event dispatcher is only active if the build protection component is running, the requirement has to be stated (that’s noted by the @RequiresComponent annotation).
@RegisteredEvent(type = BlockProtectedEvent.class)
public class ExampleProtectionTest extends AbstractComponent {
public void postInitialize() {
Rebar.getInstance().registerEvents(new ProtectionListener());
}
// ...
public class ProtectionListener extends com.sk89q.rebar.components.ProtectionListener {
@RegisteredEvent(type = BlockProtectedEvent.class)
public void onEvent(BlockProtectedEvent event) {
// Process code here
}
}
}
Creating an event is easy:
// Code for the event
public void call(ProtectionListener listener) {
listener.onEvent(this);
}
}
By design, a listener can contain multiple events.
public void onEvent(BlockProtectedEvent event) {
}
public void onEvent(GuestProtectedEvent event) {
}
}
Event priorities are integers, with certain default ones set at certain numbers. This allows for some leeway when setting priority.
Making Events Work
Often times multiple events need to be consolidated into one, or made more specific. For example, say you wanted to protect all signs with the text “[Lock]” on it from being destroyed. If you had to copy the same protection code to each component, you’d find it to be a nightmare! Instead in my plugin, the build protection is handled by a event that is raised by a build protection component. It allows other components to not at all worry about how to protect blocks, just which blocks to protect. It handles dependent blocks as well (e.g. wall signs on a back wall) all in one place.
I pass off a BlockProtectedEvent and whichever component feels like protecting another block can easily handle the event. The code for the actual component that fires the event is shown partially below:
public void postInitialize() {
Rebar rebar = Rebar.getInstance();
rebar.registerEvents(new BlockListener());
rebar.registerEvents(new EntityListener());
}
// ...
private boolean canModifyAttached(Block from, BlockFace face, Entity entity) {
// This checks to see if you can modify attached blocks (signs on wall, etc.)
}
public boolean canModify(Block block, Entity entity) {
if (Rebar.getInstance().getEventManager().dispatch(
new BlockProtectedEvent(block, entity)).isCancelled()) return false;
if (block.getType() == Material.AIR) return true;
if (!canModifyAttached(block, BlockFace.NORTH, entity)) return false;
if (!canModifyAttached(block, BlockFace.WEST, entity)) return false;
if (!canModifyAttached(block, BlockFace.EAST, entity)) return false;
if (!canModifyAttached(block, BlockFace.SOUTH, entity)) return false;
if (!canModifyAttached(block, BlockFace.UP, entity)) return false;
return true;
}
public boolean canModify(Block block) {
return canModify(block, null);
}
private class EntityListener extends org.bukkit.event.entity.EntityListener {
@Override
@BukkitEvent(type = Event.Type.ENTITY_EXPLODE, priority = Priority.Lowest)
public void onEntityExplode(EntityExplodeEvent event) {
if (event.isCancelled()) return;
for (Block block : event.blockList()) {
if (!canModify(block, event.getEntity())) {
event.setCancelled(true);
return;
}
}
}
@Override
@BukkitEvent(type = Event.Type.ENDERMAN_PICKUP, priority = Priority.Lowest)
public void onEndermanPickup(EndermanPickupEvent event) {
if (event.isCancelled()) return;
if (!canModify(event.getBlock(), event.getEntity())) {
event.setCancelled(true);
return;
}
}
// ...
}
// ...
}
Services
Services actually exist in Bukkit (I wrote it), but not many people are familiar with it, and the code is a bit generics-happy, so it tends to scare off people. However, in Rebar, I use them in order to provide a way to plug and play different parts of code and provide a little interop.
An example of a wallet service is:
void beginTransaction();
void commitTransaction();
Wallet getWallet(OfflinePlayer player);
void saveWallet(OfflinePlayer player);
}
The transaction methods are to ensure the possibility of atomic transactions when dealing with a player’s currency, which allows threading within the plugin as well as access from outside the server entirely.
Registering and working with services is pretty much the code I wrote in Bukkit, so I won’t go deeply into that. However, naturally, I have a loader helper for that:
@InjectService
private WalletService wallets;
// ...
}
Now I can support currency in any component and I don’t have to care as to what is providing the implementation!
Commands
Commands are handled by the command handling code inside of WorldEdit. It features boolean flags as well as flags of other data types (many thanks to TomyLobo), allowing you to do some pretty cool things. Annotations are also used heavily here, with an emphasis on as a little code needed as possible.
// ...
@Command(aliases = { "update-users" }, desc = "Reload the list of users and their permissions")
@CommandPermissions({ "skcraft.reload-users" })
public void reloadUsers(CommandContext context, CommandSender sender) {
userManager.reload();
ChatUtil.msg(sender, ChatColor.YELLOW, "Users and groups have been reloaded.");
}
}
It was this instead of creating a class for each component. I purposely chose not to do that instead back then because WorldEdit had over 100 commands, which would have meant over 100 classes (something I didn’t want to deal with).
Permissions
Permissions are actually handled by WEPIF, which was improved with thanks from zml2008. WEPIF abstracts a lot of the frustrating parts of handling permissions, especially for users of Bukkit’s convoluted permissions system.
Example
Here’s an example of a component that changes your name color to a random one. I don’t designate group colors on my server — everyone gets a random name color so it’s easy to read chat and it’s not cluttered.
private final static Random random = new Random();
public final static ChatColor[] NAME_COLORS = new ChatColor[] {
ChatColor.GOLD,
ChatColor.GRAY,
ChatColor.BLUE,
ChatColor.GREEN,
ChatColor.AQUA,
ChatColor.RED,
ChatColor.LIGHT_PURPLE,
ChatColor.YELLOW,
};
public void postInitialize() {
Rebar.getInstance().registerEvents(new PlayerListener());
for (Player player : Rebar.getInstance().getServer().getOnlinePlayers()) {
fancifyPlayer(player);
}
}
public void shutdown() {
}
public void fancifyPlayer(Player player) {
player.setDisplayName(
NAME_COLORS[random.nextInt(NAME_COLORS.length)]
+ player.getName() + ChatColor.WHITE);
}
public class PlayerListener extends org.bukkit.event.player.PlayerListener {
@Override
@BukkitEvent(type = Event.Type.PLAYER_JOIN)
public void onPlayerJoin(PlayerJoinEvent event) {
fancifyPlayer(event.getPlayer());
}
}
}
Source
Anyway, I actually hope to release some of this framework in the matter of a few weeks, so be on the look out! I’ve been slowly cleaning up things, and figuring out how to make some things less hardcoded (there are some limitations on Bukkit I’ll have to either fix or work around). You can watch me on GitHub if you want. If this article was mostly foreign to you, then you should read up on Java annotations and generics.
I'm on Twitter!
Really interestiing! Can’t wait for the source of the framework! ^^
Did you modify this post? I’m almost certain I saw something yesterday about throwing exceptions and I can’t find it today.
Throwing exceptions is pretty slow, so I eventually decided to get rid of that.
A very interesting read. I did have a go at creating my own, very much worse version of this (load from config, @Command And @BukkitEvent).
It’s a brilliant and much more elegant solution to the mini-plugins problem then 20 heavyweight bukkit plugins or one lumpy catch-all plugin. I hope we see a public release soon.
Hoping you’re still planning to release this. This is the kind of framework I always had in mind but never got round to implementing that would allow me to work on small improvements to our server when I have particular inspiration.
Nice valve time here! If this isn’t released in the next few days CommandBook will have a component system too.
Do you still plan to release it? This framework sounds really awesome!
Would love to see this
Especially how you call your initialise functions in your components