Minecraft Mod with RubyBukkit

The other day I went to CoderDojoDC and it seemed like every kid there was obsessed with Minecraft. I recently interviewed Charles Nutter, who mentioned you could mod Minecraft with JRuby. I wanted to get some experience with it so I could help the kids out, so I figured I’d give it a shot. It wasn’t hard!

I did this with Ubuntu linux. YMMV with other OS’s, but it shouldn’t be that much different.

Gather your forces

You have to have a few things installed to begin writing your mod:

  • First off, you need Minecraft itself.
  • Download RubyBukkit from their downloads page (I used version 0.8)
  • Go to JRuby’s downloads page and download the “Complete .jar” (I used 1.7.2)
  • Follow the “Get CraftBukkit” instructions. At the time I’m writing this, I needed to get the beta build to be compatible with the version of Minecraft the client was running, so if you see an error connecting to the server, try that, just replace craftbukkit.jar in the instructions with the beta one.

At this point you should be able to start the craftbukkit server and you’ll see some output like:

23:04:48 [INFO] Starting minecraft server version 1.4.7
23:04:48 [INFO] Loading properties
23:04:48 [INFO] Default game type: SURVIVAL
23:04:48 [INFO] Generating keypair
(etc)

RubyBukkit

Now you need to set up RubyBukkit. Move the .jar file you downloaded from RubyBukkit’s website (RubyBukkit.jar) into the plugins folder in the minecraft server’s main folder. In there, create a folder called RubyBukkit, and in that folder put your JRuby .jar file and name it jruby.jar. In the end you should have something like this:

$ find plugins/
plugins/
plugins/RubyBukkit.jar
plugins/RubyBukkit
plugins/RubyBukkit/jruby.jar

This time when you start the craftbukkit server you should see (among lots of other text):

23:12:52 [INFO] [RubyBukkit] Loading RubyBukkit v0.8
23:12:52 [INFO] [RubyBukkit] Enabling RubyBukkit v0.8
23:12:52 [INFO] [RubyBukkit] Ruby version set to 1.8
23:12:52 [INFO] [RubyBukkit] Using JRuby runtime plugins/RubyBukkit/jruby.jar
23:12:52 [INFO] [RubyBukkit] Loading Ruby plugins...

If you see that, you’re ready to program! If not, RubyBukkit is pretty helpful, so look at its error messages. If there’s nothing from RubyBukkit at all, maybe you didn’t install it correctly.

Plugins

As my minecraft server was playing with a brand new world, I didn’t have a home or even very good tools. I did have some dirt, though, so I wrote this plugin:

Plugin.is {
  name "AwesomeDirt"
  version "0.2"
  author "Josh"
}

import 'org.bukkit.Material'
import 'org.bukkit.inventory.ItemStack'
import 'org.bukkit.inventory.ShapelessRecipe'

class AwesomeDirt < RubyPlugin
  def onEnable
    result = ItemStack.new(Material::DIAMOND_PICKAXE, 1)
    recipe = ShapelessRecipe.new(result)
    recipe.addIngredient(1, Material::DIRT)
    getServer.addRecipe( recipe )
  end
end

You can see the plugin system is event driven, so onEnable in my AwesomeDirt class is called when the plugin is enabled (or loaded). All I do here is construct a new shapeless recipe (this recipe only cares that the ingredients are in the craft window, not where in the window) that requires one clod of dirt and spits out one diamond pickaxe. After adding that recipe to the server, I now have some pretty awesome dirt!

To get it to work, I saved that code into a file called awesome_dirt.rb and placed it in the RubyBukkit folder in the plugins folder, so plugins/RubyBukkit/awesome_dirt.rb

it's like magic!

Digging holes

Of course, even diamond pickaxes aren’t fast enough sometimes, so I modified my code to add the /dig command:

Plugin.is {
  name "AwesomeDirt"
  version "0.2"
  author "Josh"
  commands :dig => {
    :description => "digs a tunnel",
    :usage => "/dig"
  }
}

import 'org.bukkit.Material'
import 'org.bukkit.inventory.ItemStack'
import 'org.bukkit.inventory.ShapelessRecipe'

class AwesomeDirt < RubyPlugin
  def onEnable
    result = ItemStack.new(Material::DIAMOND_PICKAXE, 1)
    recipe = ShapelessRecipe.new(result)
    recipe.addIngredient(1, Material::DIRT)
    getServer.addRecipe( recipe )
  end

  def onCommand(sender, command, label, args)
    case command.name
    when 'dig'
      # Sender is the player who ran the command
      loc = sender.location
      dig(loc)
    end
    true
  end

  def dig(loc)

    # Yaw is the easiest way to get a 2d-coordinate player facing
    dir = (loc.yaw / 90.0).round.abs
    # map it into this array for easier comprehension
    direction_facing = [:south, :west, :north, :east, :south][dir]
    world = loc.world

    size = 3
    size.times do |delta_y| # 0, 1, 2
      size.times do |delta_left_right| 
        delta_a = delta_left_right - 1 # -1, 0, 1
        size.times do |delta_front| 
          delta_b = delta_front + 1 # 1, 2, 3
          floored_x = loc.x.floor
          floored_z = loc.z.floor

          block_y = loc.y.floor + delta_y
          # which blocks we pick are dependent on which way we're looking
          block_x, block_z = case direction_facing
                             when :north
                               [ delta_a + floored_x, (floored_z - delta_b) ]
                             when :south
                               [ delta_a + floored_x, (floored_z + delta_b) ]
                             when :east
                               [ (floored_x - delta_b), delta_a + floored_z ]
                             when :west
                               [ (floored_x + delta_b), delta_a + floored_z ]
                             end
          block = world.getBlockAt(block_x, block_y, block_z)
          # 0 is Air. See http://www.minecraftwiki.net/wiki/Data_values
          block.setTypeId( 0 )
        end
      end
    end
  end
end

It’s not super pretty, but now when I issue the ‘/dig’ command in minecraft (just type /dig into the client when connected to your server), a 3x3 grid of blocks directly in front of me will turn into air (disappear). Makes building tunnels a snap!

You can see the onCommand callback is called for the /dig command.

My lazy house

Useful stuff

In the craftbukkit server console, you can run the reload command to reload your plugin. This way I was able to get a nice quick dev/test loop (write some code, save the file, reload in the server, test it in the client, repeat).

In the client itself, you can hit F3 to get some debug info including things like where you are, the direction you’re facing, and all kinds of other stuff. Invaluable for a plugin developer!

Getting some debug output from your own plugin isn’t too bad, you can either use sender.sendMessage("some string") to print stuff on the client or just puts to see stuff written out to the craftbukkit server console. Not magic but effective.

Last thoughts

Doing math in a coordinate system is probably beyond most of these kids right now, but adding recipes will probably be pretty simple, and I imagine there are a number of other relatively easy things we could try, too.

I’m mostly very impressed by how simple it was to get going with this. Kudos to the Bukkit team for their great infrastructure, and to RubyBukkit for making it possible with JRuby!

Update: Evan Light mentioned that using Purugin makes this all even easier. Give it a shot!

comments powered by Disqus