back to list

Custom handlers my style, bro

If you want to add a custom handler (there is not much of documentation yet but feel free to ask if you can't figure it out) just place a ruby file inside vendor/handlers. As long as it doesn't start with two underscores it get's loaded. You can nest directories as much as you want as MCL traverses this directory recursively.

If you want to use more than one file for your handler feel free to organize them into folders.

Migrations

If you want to use AR models you should use a migration file to create the database. All folders inside vendor/handlers named migrations will be eligible for containing migration files. Note that the naming of the migration file is important! The name must follow this syntax (assume "create_potatoes" to be the migration name): YYYYMMDDHHMMSS_create_potatoes while the class name inside this file is also derived from the filename:

class CreatePotatoes < ActiveRecord::Migration
end

For more information about migrations refer to the official guide (note that the rails generators won't work with MCL)

Also, take a look at how the buildin lagtrack handler did it.

Additional Gems

If your handler requires additional ruby gems you might create a Gemfile (mind the capital G) anywhere inside your handler's folder. They will be eval'd in the main gemfile.

Callbacks

Take a look at the handler API file which defines the handler methods being called by MCL.

Gotchas

  • All files inside vendor/handlers might get loaded multiple times per process due to MCL reloading it's handlers.
  • Players fetched via player manager get cached. The cache get saved and cleared according to the player_cache_save_rate.
  • If you fetch players via Player model class be sure to call #clear_cache on the player manager beforehand.
  • When working with async tasks always make sure to synchronize to the main loop if necessary (it is in a lot of cases).
  • Promises are already synchronized to the main loop as they get called by it.
  • Don't block the main loop to long (this includes everything except async code which isn't synchronized to the main loop).

Boilerplate

This is a handler boilerplate to get you started: (raw download)

module JohnDoe
  # Allows the handler to be reloaded without a restart of MCL (and therefore the minecraft server).
  # It must define the name of the handler class which needs to be a direct descendant of parent.
  # The handler class MUST be passed as symbol, the parent MUST be a constant (class/module).
  Mcl.reloadable(:MyHandler, parent = JohnDoe)

  ## MyHandler (it does things!)
  # You may describe your handler here or list the available commands.
  class MyHandler < Handler
    # This method is being called by MCL (see Callbacks).
    def setup
      # It's a good habit to have a method for each command (unless they're oneliner) which accepts
      # it's ACL level as argument. This way it's more easy to see and modify the permissions.
      register_gamemode_commands
      register_some_command :admin
    end

    def register_gamemode_commands
      register_command(:s, :survival,     desc: "be mortal and die!", acl: :guest)   {|player, args| gm(0, args.first || player) }
      register_command(:c, :creative,     desc: "be creative"       , acl: :builder) {|player, args| gm(1, args.first || player) }
      register_command(:adventure,        desc: "be creative"       , acl: :builder) {|player, args| gm(2, args.first || player) }
      register_command(:spec, :spectator, desc: "become spectator"  , acl: :builder) {|player, args| gm(3, args.first || player) }
    end

    def register_some_command acl_level
      register_command :some_command, desc: "does something", acl: acl_level do |player, args, handler, option_parser|
        # ...
      end
    end

    # Organize stuff with modules if you don't want to use multiple files.
    module Helper
      def gm mode, target
        $mcl.server.invoke "/gamemode #{mode} #{target}"
      end
    end
    include Helper
  end
end