Inactive [DEV/MISC] RubyBukkit v0.8 - Write plugins in JRuby [1.1-R6, ..., 1.2.4-R1.0]

Discussion in 'Inactive/Unsupported Plugins' started by Zeerix, Jul 6, 2011.

  1. Offline

    Zeerix

    RubyBukkit - Write plugins in JRuby
    Version: v0.8

    This is a custom plugin loader that enables you to write Bukkit plugins as short Ruby/JRuby scripts.

    Below are some example plugins in Ruby.
    And there is an explanation on How to Write Plugins in Ruby in the next post.

    View RubyBukkit on BukkitDev: http://dev.bukkit.org/server-mods/rubybukkit/

    Features:
    • Loads Bukkit plugins in Ruby/JRuby

    Download, Installation:

    Just drop the jar into the plugins folder. Additionally, you need the JRuby runtime version 1.6.x. You can download it from http://jruby.org/download, for example the tar.gz or the zip.
    Extract jruby.jar from the folder lib/ in the archive and drop the file into plugins/RubyBukkit (create that folder).

    Configuration:
    The plugin will create a default configuration file on startup as plugins/RubyBukkit/config.yml.
    Code:
    settings:
        plugins-path: plugins
        debug: true
    runtime:
        ruby-version: '1.8'
        jruby-path: plugins/RubyBukkit/jruby.jar
    
    • plugins-path - the folder which this plugins looks for Ruby plugins in. All files with extension .rb are loaded. You can change this path to separate the normal Java plugins from Ruby plugins.
    • debug - enables/disabled debug output about plugin loading
    • ruby-version - JRuby version compatibility mode: '1.8' or '1.9'
    • jruby-path - location of the jruby runtime

    Example plugins:
    Here is a list of some example plugins. There's also an explanation on How to Write Plugins in Ruby in the next post.
    • [GEN/INFO] OnlinePlayers - Displays list of online players on login and by a command.
    • [WGEN] RubyMoon - Ruby version of the custom map generator BukkitFullOfMoon by Dinnerbone.
    • [ADMN] GarbageCollect - Registers a command that forces a garbage collection cycle.
    • [INFO] Uptime - Displays time since server start on /uptime.
    • [MISC/MECH] CustomRecipes - Adds some custom recipes for the crafting grid. Can easily be changed to add new recipes.

    Changelog:
    Version 0.8
    • Updated for CraftBukkit 1.1-R6.
    • registerEvent method changed.
      Use: registerEvent(org.bukkit.event.ThePackage.TheNameOfTheEvent, :Normal) { |event| /* code */ }
    Version 0.7.1
    • Updated for CraftBukkit 1.1-R1.
    Version 0.7
    • Changes for the new event system in CraftBukkit 1.1 dev builds:
      • RubyPlugin#registerEvent accepts Event.Type.EVENT_NAME, EventName.class and :EventName
    • Implemented new methods of org.bukkit.plugin.Plugin in RubyPlugin: getLogger and createRegisteredListeners
    Version 0.6
    • Added new methods for CraftBukkit 1.1 dev builds:
      • Methods for event timing measurement
      • saveResource, saveDefaultconfig
    Version 0.5
    • Implemented new Bukkit Configuration API
    • Activated JIT
    • Added classpath for Rubygems
    Show Spoiler

    Version 0.4.4
    • Fixed a bug in plugin.registerEvent forwarder method
    Version 0.4.3
    • Implemented plugin.getConfiguration()
    • Changed plugin.registerEvent to additionally take symbols and strings where enums are expected
    • Added overridable library files for Plugin.is{} block and a RubyPlugin class mixin
    Version 0.4.2
    • Moved Ruby library path from bukkit/ to lib/ (i.e. require 'lib/permissions')
    Version 0.4.1
    • Changed loading of JRuby runtime; less memory problems on /reload
    Version 0.4
    • First public release
    • Implemented virtual 'plugin.yml' inside the Ruby script
    • Implemented support for RubyBukkit-boundled and custom Ruby libraries
    • Added library for simple Permissions, PermissionsEx and GroupManager support


    Donation:
    If you like RubyBukkit, you can donate some Bitcoins to my address.
    One-time addresses and other forms of donation on request.
    RubyBukkit
    How to write a Ruby plugin

    A Ruby plugin lives in one single file called 'PluginName.rb'. The loader executes these files to get the class definitions and the plugin descriptions. Since there is no plugin.yml, that information is also read from the script file itself.

    The plugin OnlinePlayers will be used as an example. You can read the full source code behind the link.

    Plugin description
    The loader generates the plugin description from information that you provide in the Ruby script.
    The script calls "Plugin.is" with a Ruby block. In this block, method calls named like the plugin.yml nodes are used to provide the neccessary information.
    You must at least provide values for name and version.
    You can use Symbols and Strings interchangeably.
    Code:
    Plugin.is {
        name "OnlinePlayers"
        version "0.3"
        author "Zeerix"
        description "Displays list of online players on login and by a command"
        commands :list => {
            :description => "Show online players.",
            :usage => "/list",
            :aliases => [ :players, :online ]
        }
    }
    
    Main class
    The main plugin class muss inherit from RubyPlugin and define the methods onEnable and onDisable.
    The name of this class must be what you wrote in the plugin description.
    Actually, the class' name must be the same as the 'main' node in the plugin description, which defaults to the
    'name' node if you don't provide one.

    Code:
    class OnlinePlayers < RubyPlugin
       def onEnable
          print getDescription.getFullName + " enabled."
       end
       def onDisable; end
    end
    
    Java/Bukkit classes
    You can use Java classes with the full package name or import the classes just like in Java.
    Unlike Java, the import statement takes a string as argument.
    Code:
    import 'org.bukkit.event.Event'
    Nested classes and the values in a Java enum are separated with a :: instead of a period.
    Code:
    def onEnable
       pm = getServer.getPluginManager
       pm.registerEvent(Event::Type::PLAYER_LOGIN, MyListener.new, Event::Priority::Normal, self)
    end
    Events
    Additionally to the normal Bukkit interface for event/listener registration (see above), the class RubyPlugin defines the method registerEvent which lets you register a Ruby block as listener.
    This is useful for a small plugin where you don't want to create extra classes for the listeners.
    Code:
    registerEvent(Event::Type::PLAYER_LOGIN, Event::Priority::Normal) {
       |loginEvent|
       player = loginEvent.getPlayer    
       # do something with player / loginEvent
    }
    
    Scheduler
    There are also convenience methods in RubyPlugin to register tasks in the scheduler.
    The simplest one just takes a block and executes it synchronously (in the main thread):
    Code:
    scheduleSyncTask { print "Hello World!" }
    You can also register delayed and repeating tasks:
    Code:
    scheduleSyncDelayedTask(20) { print "after 20 ticks." }
    scheduleSyncRepeatingTask(10, 20) { print "after 10 ticks, every 20 ticks." }
    
    There is also an Async variant for all three.

    Commands
    Commands are registered in the plugin description (the Plugin.is block, see above).
    If a command is used by a player or in the console, the onCommand method is called.
    Use sender.isPlayer or sender.is_a?(Player) to check if it was used by a player or in the console.
    Don't forget to return true.
    Code:
    def onCommand(sender, command, label, args)
       listPlayersTo sender
       true
    end
    
    Ruby libraries
    <todo>
    • Permissions - lib/permissions.rb
      Code:
      Setup: require 'lib/permissions'
      Usage: player.has("permission.node")
      

    <reserved>

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 17, 2016
    Coelho, TZer0 and nacs like this.
  2. Offline

    gameguy27

    This is awesome! Ruby was my first programming language and basically know it in and out. This is an epic program dude. :D
     
  3. Offline

    maetthew

    WOOOW! I've just started learning Ruby a couple of months back (actually before I started to play Minecraft and this became my hobby :p). Now I can go back to study some more Ruby with this in mind as a great motivation. I will make my first actual project and RubyBukkit plugin :)
     
  4. Offline

    MalcolmLC

    I just spent about a week trying to learn python. All I want to ask is if ruby is easy to learn to .
     
  5. Offline

    Zeerix

    Yes. Ruby is an easy scripting language just like Python.
    While the syntax is quite different, the general concepts are very similar.

    Ruby is a pure OOP language, which means that anything is an object. You can call a method on the literal 1, for example:
    Code:
    1.class         # get class of 1
    => Fixnum
    "Foo".class     # get class of a string literal
    => String
    42.to_s         # convert 42 to string
    => "42"
    
    There's an interactive Ruby tutorial here: http://tryruby.org/
    ...and an introductory (comic) book here: http://mislav.uniqpath.com/poignant-guide/
     
  6. Offline

    nacs

    Looks very interesting!

    How much overhead would using JRuby cause in terms of performance? Would this only be suitable for small plugins?

    And does this allow for changes to plugin code at any time? Or would the server have to be restarted each time a change to a .rb is made?
     
  7. Offline

    Zeerix

    I don't know much about the performance of JRuby, but it is JITed to Java byte code. I converted the example map generator BukkitFullOfMoon to Ruby and found that it quite slowly generated the chunks on entering that world the first time.

    Personally, I wouldn't write something like WorldGuard in Ruby, since it would have to handle many events (Block break, place etc.) in short time.
    Any plugins which only do little work or run not often (for example: economy, maybe fire protection) and command driven plugins (Home, Warp, Help) would be no problem performance-wise.
    It's also great for prototyping a plugin which you will convert to a Java plugin later.

    You can use 'reload' from the console or in-game to load a .rb you just modified. I use that to write and debug the plugins myself. There is a problem however with a memory leak in Bukkit. If I do about 6 reloads on my local test server, I have to restart it because of an "Out Of Memory: PermGen" exception.
     
  8. Offline

    nacs

    Thanks for the info Zeerix. I'll definitely be playing around with this.

    Is there a way to see why a .rb didn't load? Or a way to see debug info / errors / warnings?

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 17, 2016
  9. Offline

    Zeerix

    Console log.
     
  10. Offline

    nacs

    Console log shows this and nothing more:
     
  11. Offline

    Zeerix

    Oh yeah, I should change that.
    It happens if there's no "Plugin.is" block in the file.
     
  12. Offline

    nacs

    Got my first little test plugin working thanks. Working well so far but I have noticed, as you mentioned, that after doing a few /reload's the server runs out of memory and the process needs to be killed.

    Would be great to have a development mode or something in RubyBukkit that would allow an individual plugin to be reloaded (not sure if this is possible) without a full /reload or a restart.
     
  13. Offline

    dragos240

    Has this been fixed? Because I want to develop in this.
     
  14. Offline

    philboy11

    How would I go about making an item edible with this. Is it possible?
     
  15. Offline

    Zeerix

    No, the Bukkit team isn't that fast with fixing bugs. Every /reload leaks some MB of memory.
    Also, you can't disable a single plugin; the events and command handlers will still be called.
    I can't fix these problems without changing Bukkit.

    Just like in Java. Register for PlayerInteractEvent and do the necessary steps in the listener (heal player and take one item away).
     
  16. Offline

    MalcolmLC

    I like were this is going!
     
  17. Offline

    Zeerix

    I changed the way how the JRuby runtime is loaded. It stays in memory on a /reload now, only the individual Ruby plugins are reloaded. I could do a /reload many times without getting an Out of Memory error.

    I'll look into commands for reloading a single plugin.
     
  18. Offline

    harrisonduell

    Looks good ill try it out later...
     
  19. Offline

    Nijikokun

  20. Offline

    TZer0

    As a Ruby-lover - I *must* try this!

    Thank you!
     
  21. Offline

    Nijikokun

  22. Offline

    TZer0

    Quick question: what about getConfiguration() and related methods? I can't seem to get that working.
     
  23. Offline

    Zeerix

    Oh, you are right. I didn't set the Configuration class up (and database stuff as well).

    You have to manually create the config instance until I update:
    Code:
    # outside class:
    import 'org.bukkit.util.config.Configuration'
    
    # in onEnable:
            cfg = Configuration.new java.io.File.new(dataFolder, "config.yml")
            cfg.load
            someSetting = cfg.getString("someSetting", "defaultValue")
            cfg.save 
    
    Much code, Niji!
    Just wanted to say that these are Perms 2.x permissions, not the new Bukkit internal permissions.

    Fixed the getConfiguration() method. However, the database initialization code still isn't included.

    You can use symbols and strings to register events now:
    Code:
    registerEvent(:PlayerLogin, :Normal) {
       |loginEvent|
       # do stuff
    }
    
    That way you don't need the Event::Type:: prefix and it is more Ruby-ish

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 17, 2016
  24. Offline

    TZer0

    Excellent! Thank you.
     
  25. Offline

    defendedclone

    uhhh does this work with ruby on rails?
     
  26. Offline

    Zeerix

    Why would it do that?
     
  27. Offline

    nacs

    Thanks for making/submitting this, its very useful as a learning tool (and yes the comments are quite hilarious :p ).
     
  28. Offline

    Zeerix

    Fixed a bug in plugin.registerEvent. If you are on 0.4.3, please update to 0.4.4!
     
  29. Offline

    nacs

    Probably a newbie question but I'm trying to use the ConsoleCommandSender to send a command as though the command was typed through console.

    I've done
    Code:
    import 'org.bukkit.command.Command'
    import 'org.bukkit.command.CommandSender'
    import 'org.bukkit.command.ConsoleCommandSender'
    However, when I then do..
    Code:
    cs = new ConsoleCommandSender(getServer());
    getServer().dispatchCommand(cs, "give playername coal 1")
    .. I get a "Caused by: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `ConsoleCommandSender' for #<Java::OrgBukkitCraftbukkit::CraftServer:0x4e1ba9a0>"

    Any ideas?

    (The 'give playername..' command is just a test command, I know there are bukkit functions to give items :) ).
     
  30. Offline

    Zeerix

    Quote: "This is madness!" - "Madness? THIS IS JAVA!"

    PS 1: Ruby way: ConsoleCommandSender.new
    PS 2: Yes, I've done this a couple of times too ;-)
     

Share This Page