Introducing Intake 4.x

I’ve made some large structural changes to Intake for version 4. Intake is a Java framework for handling commands from users, with most of the code dedicated really dedicated to generating commands from plain Java code (with perhaps some annotations).

If you’re not familiar with Intake, consider the following Java method:

void setPosition(List<Entity> entities, Vector3 position) {
    for (Entity entity : entities) {
        entity.setPosition(position);
    }
}

With the addition of some annotations, Intake is able to generate the following command:

/setposition <entities> <position>

Depending on how the argument parsers are written, the commands could possibly be used in a wide variety of ways:

/setposition vehicle 0 54 0
/setposition vehicle 0,54,0
/setposition vehicle 0, 54, 0
/setposition * 0 54 0
/setposition vehicle,boat 0 54 0

Helpful error messages are generated when commands are used incorrectly:

  • For parameter ‘entity': There’s no entity identified by ‘car’
  • Too many arguments provided! Unused: -f 45

In addition, Intake supports a few more features like flags (/save -f), argument completion, and optional parameters.

Intake was originally built in 2012 to support WorldEdit and CommandBook which make heavy use of commands, wildcard selectors, and complex arguments. Unlike other command parsing libraries, Intake centralizes argument parsing so all commands have consistent argument syntax.

What’s Changed

The core functionality in v4 hasn’t changed too (everything in v4.0 was in v3.x) much but the code has undergone a refactoring. Quite a few classes and interfaces have been renamed and moved, and the exception hierarchy has been greatly clarified.

Perhaps the biggest change was that the IoC portion of Intake that binds argument parsers to Java types is now easily accessible as a standalone component:

Injector injector = Intake.createInjector();
injector.install(new PrimitivesModule());
injector.install(new UniverseModule());

Builder argParserBuilder = new Builder(injector);
argParserBuilder.addParameter(Body.class);
argParserBuilder.addParameter(CelestialType.class);

ArgumentParser parser = argParserBuilder.build();
parser.parseArguments(Arguments.of("pluto", "dwarfplanet")));

The API is very similar to Google Guice’s.

What’s Missing

Some features are currently missing from v4, however:

  • Optional parameters that precede required parameters are currently no longer supported. The rational for removal was that this feature, when utilized, added a certain level of ambiguity to commands (especially considering that some parameters consume more than one argument) and complicated parts of Intake to make it possible. That said, this feature may make a return but it will necessitate changes to classes implementing Provider that have been written.
  • Argument completion needs to be re-implemented. The only work that needs to be done is some additional code in AbstractParametricCallable but the rest of the framework supports all the features needed to support argument completion.

What Needs Fixing

  • There need to be more unit tests.
  • Localization isn’t supported very well.
  • The code to generate the names of parameters is not very flexible. Previously Intake also used the Paranamer library to provide better parameter names, but that is currently not the case due to past incompatibility with Gradle.

Still Under Development

Due to the missing features above, SNAPSHOT versions of 4.0 may be subject to changes in API, although none as extreme as the transition from 3.x to 4.0.

Find Intake on GitHub: https://github.com/sk89q/Intake

HLS/MPEG-DASH/RTMP with nginx

With a few open source tools, you can stream a playlist of videos in real-time over RTMP, MPEG-DASH, and HLS in a fully automated manner.

In this case, we’ll be using several software packages:

  1. GStreamer/Libav for decoding and encoding
  2. Liquidsoap for playlist management
  3. nginx for the server
  4. nginx-rtmp-module for providing HLS/MPEG-DASH/RTMP streaming

Using Ubuntu 14.XX (or possibly anything Debian-based) will be your shortest path of resistance, as compiling these packages by hand is a considerable chore and other distributions will likely lack many of these packages. If you want to use a different OS, you can virtualize Ubuntu, possibly with a lightweight container. You will not get very far with older versions of Ubuntu (such as 12.04) as they will also lack many of these packages.

GStreamer and Liquidsoap

GStreamer is a multimedia framework similar to DirectShow, in that it allows the linking together of components such as decoders, encoders, and so on to handle media. You’ll want to install the entirety of GStreamer if you want to play a wide variety of video formats. In addition, the “gstreamer1.0-libav” package enables you to make use of the Libav library to decode media.

Note: Libav is a hostile fork of the FFmpeg project and the relevant package maintainer for Debian/Ubuntu has chosen to distribute Libav rather than FFmpeg (which is still alive and kicking).

sudo apt-get -y install libgstreamer1.0-0 gstreamer1.0-plugins-base \
   gstreamer1.0-plugins-ugly gstreamer1.0-plugins-good \
   gstreamer1.0-plugins-bad gstreamer1.0-tools gstreamer1.0-libav

Then you’ll want to install Liquidsoap. Liquidsoap provides a powerful language to tie together multimedia components, and we’ll need it for playlist support. (It does a lot more though.)

sudo apt-get -y install liquidsoap liquidsoap-plugin-all

Be aware that you’ll also be installing all of LiquidSoap’s plugins (and dependencies). In reality, you do not need the whole package to simply stream a playlist, but you’ll have more freedom to play with LiquidSoap’s various features if you choose to install everything.

nginx and nginx-rtmp

Next, you’ll want to install Nginx and the nginx-rtmp-module.

Nginx is most well known to be a high-performance web server and reverse proxy, but the nginx-rtmp-module turns nginx into a streaming media server capable of speaking RTMP, MPEG-DASH, and Apple HTTP Live Streaming (HLS) protocols.

Flash-based video players will use RTMP, but HTML5/mobile players will use MPEG-DASH or HLS. The latter two protocols provide adaptive streaming, where videos are spliced into small chunks in real-time, allowing the client to switch to a lower quality “stream” in the middle of a video (see: YouTube).

Compiling from source

While nginx is readily available in the repositories on Debian/Ubuntu, it does not come with the nginx-rtmp-module, so we’ll have to compile nginx ourselves.

sudo apt-get update
sudo apt-get install dpkg-dev git-core
mkdir ~/nginx-compile
cd ~/nginx-compile
apt-get source nginx
sudo apt-get build-dep nginx
git clone https://github.com/arut/nginx-rtmp-module.git
cd nginx-VERSION

You’ll then want to edit the debian/rules file and add the following to the “config.status.full” entry on a new line, after all the other --add-module lines.

--add-module=/path/to/nginx-compile/nginx-rtmp-module \

Proceed to compile the package.

dpkg-buildpackage -b

Afterwards, you can install both the newly created “common” and “full” packages. Make sure to apt-get remove nginx first if you have already installed nginx via the package manager.

cd ../
sudo dpkg --install nginx-common_1.4.6-1ubuntu3_all.deb
sudo dpkg --install nginx-full_1.4.6-1ubuntu3_amd64.deb

Configuring nginx

You’ll want to edit /etc/nginx/nginx.conf in your favorite editor and add the following section to the end of the file. Note that my snippet below will only configure nginx to provide a RTMP stream, but check out nginx-rtmp-module’s wiki to learn how to setup MPEG-DASH or HLS support.

rtmp {
  server {
    listen 1935;
    publish_time_fix off; # compatibility with GStreamer

    application example {
      live on;
      allow publish 127.0.0.1;
      deny publish all;
      allow play all;
    }
  }
}

Start nginx.

service start nginx

Streaming a test video

You can test whether your setup is sound using the gst-launch-1.0 program provided with GStreamer. (Be sure to change the path to the test video file.)

gst-launch-1.0 \
  filesrc location="/path/to/file.mp4" ! decodebin name=t \
  t. ! videoconvert ! x264enc bitrate=2000 ! queue ! flvmux name=mux \
  t. ! audioconvert ! voaacenc bitrate=128000 ! queue ! mux. \
  mux. ! rtmpsink location="rtmp://127.0.0.1:1935/example/live live=1"

To actually watch the stream, you can use the rtmpdump program and pipe the result to something like VLC Media Player (VLC does not yet offer native support for viewing RTMP streams).

You can also configure one of the freely available Flash streaming video players to play your stream.

Configuring Liquidsoap

With your favorite text editor again, create a stream.liq file and in it, place the following code.

set("frame.video.width", 1280)
set("frame.video.height", 720)
set("frame.video.samplerate", 25)
set("gstreamer.add_borders", true)

s = single("/path/to/video.mp4")

s = fallback([s, blank()])

output.gstreamer.audio_video(
  video_pipeline=
    "videoconvert ! x264enc bitrate=2000 ! video/x-h264,profile=baseline ! queue ! mux.",
  audio_pipeline=
    "audioconvert ! voaacenc bitrate=128000 ! queue ! mux.",
  pipeline=
    "flvmux name=mux ! rtmpsink location=\"rtmp://127.0.0.1:1935/example/live live=1\"",
  s)

Run your script file by executing liquidsoap stream.liq in your terminal. If all goes well, you should be able to watch your video by connecting to nginx as you had done before with the gst-launch tool.

Setting up a playlist

At this point, you can change

s = single("/path/to/video.mp4")

into

s = playlist("/path/to/playlist.ext")

(See Liquidsoap’s supported playlist formats.)

In conclusion

While the method I have outlined does work, it is not the only way to go about doing this. For example, you could concatenate a list of videos together with FFmpeg and send it off to your media server using the -realtime flag, skipping half of the steps in this article. You could even use one of those “screen streaming” programs with playlist support.

Remember also that video encoding (and to a smaller degree with most formats, decoding) is a CPU-intensive task. If you do not provide enough resources, the stream will stutter.

The transition to UUID

As many are aware, Mojang is transitioning to using a unique, random identifier for each player rather than continuing the use of usernames. While player names are not completely going away (you will still see names over players’ heads and in chat), the problem is that it will no longer be guaranteed that a certain player’s Minecraft name will still be owned by that player once he or she disconnects. That may be caused by a player changing his or her name to something else, freeing up the older name for someone else to use.

Many things may actually not break

Since names are not going away and it’s only the 1-to-1 name-to-person correspondence that is going away, the change over to UUID may not be that dire. For example, if permission is given to “frank_m3256″ to use creative mode, there is no reason as to why old software would be unable to continue comparing names. When frank_m3256 signs on, he will receive his creative powers as usual. When the trouble develops is when Frank chooses to change his username. Nothing would break, but whoever takes the frank_m3256 name would then receive the powers of the original Frank. The original Frank, who would be using a new name, would not be recognized as the original Frank.

What’s involved in updating code for UUIDs

For most operations that need to work on players that are currently connected, the changeover to UUIDs does not present much of a problem.

When offline players are involved, the situation becomes much more complicated. When a player is logged onto a server, it is guaranteed that that player’s name is only assigned to that player. Remember, it’s when people disconnect that the 1-to-1 correspondence disappears. That results in complications for plugins, mods, and other projects that ask for a username once a player has disconnected from the game, because the code now needs to map the username to a unique identifier. While there might be a temporary cache to answer “what was the UUID of the last player connected that used this name?” it would not be wise to look there, because, if you recall, the name may be reassigned to someone else. The only way to actually accurately perform this name-to-ID conversion is to contact the Mojang servers and request for the equivalent UUID, which means these operations may need to now wait on a webpage request sent to Mojang.

The other difficulty regards plugins and mods that store data in the world. Imagine a chest that stored its owner in its NBT data: that name, as well as names of hundreds of other players, are now buried deep inside the data of blocks strewn across the world. Conversion of the names to the UUIDs cannot be done without actually opening every chunk and searching for names to change over. As noted previously, the fact that names are still used does not necessarily mean anything will break, but it may cause problems in the future for players that change their names.

Regarding my projects

As far as my projects go, we do plan to update for full UUID support.

  • WorldEdit is not particularly impacted by this change because it currently does not persist per-player data long after a player has disconnected.
  • WorldGuard has many names stored, primarily for region protection. Fortunately, this data is completely centralized, so it can be converted somewhat easily. An update should hopefully come out soon (as soon as time permits, rather) to add full support for UUID. It’s likely that nothing may break (if an update is not immediately available), but be aware about the issue with players changing their name.
  • CraftBook stores names on signs, which presents a problem because the unique identifiers in use by Mojang do not fit on the signs (nor did names, but there was only a 1 character length discrepancy). Me4502 is working on converting names to use an internal ID which will fit on the signs.
  • CommandBook is being updated to use UUIDs by Dark_Arc.
  • CommandHelper is being updated to use UUIDs by Lady_Caitlin.

Easy JVM CPU profiling with WarmRoast

WarmRoast is a web-based CPU sampler for the Java Virtual Machine with a few select features tailored for diagnosing Minecraft servers and clients.

  • Adjustable sampling frequency.
  • Supports loading MCP mappings for deobfuscating class and method names.
  • Web-based — perform the profiling on a remote server and view the results in your browser.
    • Collapse and expand nodes to see details.
    • Easily view CPU usage per method at a glance.
    • Hover to highlight all child methods as a group.
    • See the percentage of CPU time for each method relative to its parent methods.
    • Maintains style and function with use of “File -> Save As” (in tested browsers).

Continue reading

WorldEdit selection API on Bukkit

WorldEdit provides an easy-to-use API for Bukkit plugins to access the player’s current region selection.

A large part of WorldEdit uses an abstract internal API for easy portability  For your convenience, we provide a Bukkit-native selection API that makes it easier to work with that part of WorldEdit. It is possible to “reach through” to access to the internal features of WorldEdit, but if you are interested in only getting selections, that shouldn’t be needed.

Getting a selection

To get a selection, you need to:

  1. Get a reference to WorldEditPlugin from Bukkit’s plugin manager.
  2. Cast the reference from JavaPlugin to a more specific WorldEditPlugin.
  3. Get the selection by calling getSelection().

See the snippet below.

Continue reading

Understanding the “game clock”

Understanding how your server ticks underneath can be helpful when diagnosing problems regarding slow performance. We’ll cover several topics.

  1. An overview of the game logic loop
  2. How this all applies to modded servers
  3. An explanation of transient problems
  4. A guide to profiling your server
  5. A macro-view of improving performance

The game logic loop

The most important part of Minecraft is the game loop. In there is a clock keeping everything synchronized, going tick-tock at an ideally constant rate: twenty times per second, with each tick or tock lasting hopefully at most 50 milliseconds. During each cycle, game elements have to do their work — skeletons have to figure out where to walk, minecarts have to travel forward, grass has to spread, and light problems have to be checked. That’s a lengthy to-do list, considering how many blocks and entities are on a loaded world at at time (thousands). This tick-tock interval is called the tick rate or “TPS,” and you can get some mod or plugin to give you your server’s rate.

vanilla

Quite a few things are on the to-do list, but as far as the “main game elements” go, those items are blocks, entities, and tile entities. Blocks include simple things such as sandstone, cobble, sugar cane, and things of that nature. Entities are your standard animals and monsters, in addition to paintings, item drops, and “objects that don’t fit perfectly on a grid.” Tile entities are a special kind of block, as they can hold any type of data (think storing an inventory, something sugar cane doesn’t have to do), and sometimes they must think separately. As previously mentioned, each loop has a budget of 50ms and each portion consumes some part of this pie. Any leftover time is considered idle.

Continue reading

Session theft with a MITM in MC 1.2

Update: The man-in-the-middle attack described in this post is now ineffective as it was fixed in Minecraft 1.3, but the solution still works if you desire extra security.

A “session stealer” exploit in Minecraft is now being used increasingly widely to gain operator privileges on a target server. Anyone with the ability (or the freely available tools) can impersonate another user, which comes in handy to gain elevated privileges on a given server. The underlying exploit is actually extremely old, and while a small number of people have long been aware of it, news broke to the greater community recently and now it is a real relevant concern. On the flip side, the bug will finally be fixed (after three years) with the release of Minecraft 1.3. Hooray public disclosure.

exploit_logic

But I don’t want to wait for a fix

If you run a server, it’s understandable that you would want a fix now. You can ensure that you don’t expose your own account, but you also have to ensure that your own moderators do not either. That can be harder than it sounds, especially depending on who you are working with, and so some — even minor — protection to use would be nice. Unfortunately, it is not possible to fix the problem from just the server’s side, and so people have attempted to work around the problem by using a mix of some rather inconvenient solutions (mostly involving a /login command). Instead, I decided on a different novel way to protect the accounts of my moderators, described below.

Continue reading

Maintenance plans in the near future

Currently everything is still maintained (except perhaps CraftBook, but that’s not new news), but a lot of it has been passed on to a few others:

  • zml2008 will be managing release for the most part for CommandBook, WorldGuard, and perhaps WorldEdit.
  • TomyLobo is currently a lead developer on WorldEdit and handles a lot of the cool new features. (He also has a Twitter account.)
  • famerdave will be helping on/off with CommandBook.
  • I will still be around, but sometimes I won’t know the answers to all your questions :)

If you are interested in helping, pop in #sk-dev on irc.esper.net and idle. You may want to get in touch with zml2008 or sk89q to get you started. Please note that you need to be somewhat experienced with Java (or rather programming in general) as I tend to be a pretty strong advocate of abstraction. On the other hand, I wrote WorldEdit when my experience with Java was pretty minimal, so you can do it too (just read up on generics and annotations).

We’re hopefully going to support these projects for as long as possible because I know a lot of people depend on it. Thanks to everyone for supporting us! If you are ever looking for a release and not finding one, you can check out build.sk89q.com for developer builds.

The self contained plugin system

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:

[cc lang=”java”]public class MySessionTest extends AbstractComponent {
@InjectComponent private Sessions sessionsManager;
// …
}[/cc]

What if I need to access another plugin?

[cc lang=”java”]public class WorldGuardTest extends AbstractComponent {
@InjectPlugin(WorldGuardPlugin.class)
private LazyPluginReference worldguard;
// …
}[/cc]

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.

[cc]
components:
– com.sk89q.skcraft.users.UserManager
– com.sk89q.skcraft.FancyName
– com.sk89q.skcraft.GameMechanics
– com.sk89q.skcraft.BetterVehicles
# …[/cc]

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)

[cc lang=”java”]public class BuildProtection extends AbstractComponent {

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
}
}
}[/cc]

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)

[cc lang=”java”]@RequiresComponent(BuildProtection.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
}
}
}[/cc]

Creating an event is easy:

[cc lang=”java”]public class BlockProtectedEvent extends AbstractEvent {
// Code for the event

public void call(ProtectionListener listener) {
listener.onEvent(this);
}
}[/cc]

By design, a listener can contain multiple events.

[cc lang=”java”]public abstract class ProtectionListener {
public void onEvent(BlockProtectedEvent event) {
}

public void onEvent(GuestProtectedEvent event) {
}
}
[/cc]

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:

[cc lang=”java”]public class BuildProtection extends AbstractComponent {
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;
}
}

// …
}

// …
}
[/cc]

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:

[cc lang=”java”]public interface WalletService {
void beginTransaction();
void commitTransaction();
Wallet getWallet(OfflinePlayer player);
void saveWallet(OfflinePlayer player);
}[/cc]

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:

[cc lang=”java”]public class WalletTest extends AbstractComponent {
@InjectService
private WalletService wallets;
// …
}[/cc]

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.

[cc lang=”java”]public class UserManagerCommands {
// …

@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.”);
}
}[/cc]

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.

[cc lang=”java”]public class FancyName extends AbstractComponent {
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());
}
}
}[/cc]

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.