Snippet: #fv07

Written in Ruby and posted on Nov 29, 2011 at 17:17 by apoc
   1  # rbot minecraft plugin
   2  begin
   3    require 'rubygems'
   4  rescue LoadError
   5  end
   6  require 'json'
   7  
   8  class Minecraft < Plugin
   9  
  10    class Recipes
  11      def initialize(recipes)
  12        @recipes = recipes
  13        log "Loaded #{length} recipes."
  14      end
  15  
  16      def length
  17        @recipes.keys.length
  18      end
  19  
  20      def search(query)
  21        log "Search for #{query}"
  22        def unify(str)
  23          str.downcase.gsub(/(\(|\))/,'')
  24        end
  25        results = []
  26        @recipes.keys.each do |key|
  27          results << key if unify(key).include? unify(query)
  28          # exact match found:
  29          if unify(key) == unify(query)
  30            return [ key ]
  31          end
  32        end
  33        return results
  34      end
  35  
  36      def get(product)
  37        @recipes[product]
  38      end
  39  
  40      def craft(product, amount, checklist=[])
  41        return if not @recipes.has_key? product
  42        recipes = @recipes[product]
  43        if recipes.length > 1
  44          msg = 'You can craft this in different ways: '
  45          recipes.each do |recipe|
  46            msg << craft_recipe(product, recipe, amount, checklist)
  47          end
  48          return msg
  49        else
  50          return craft_recipe(product, recipes.first, amount, checklist)
  51        end
  52      end
  53  
  54      def craft_recipe(product, recipe, amount, checklist=[])
  55        checklist << product
  56  
  57        amount += 1 while amount % recipe['output'].to_i != 0
  58        factor = (amount / recipe['output'].to_i).to_i
  59  
  60        # ingredients how many of each..
  61        recipe_list = []
  62        recipe_crafting_list = []
  63        recipe['recipe'].each_pair do |recipe_item, recipe_amount|
  64          recipe_amount *= factor
  65  
  66          recipe_list << "#{recipe_amount} #{recipe_item}"
  67          if not checklist.include? recipe_item
  68            recipe_crafting = craft(recipe_item, recipe_amount, checklist) 
  69            recipe_crafting_list << recipe_crafting if recipe_crafting
  70          end
  71        end
  72  
  73        msg = ''
  74        msg << "You craft #{amount} #{product}"
  75        msg << " with #{recipe_list.join ', '}."
  76  
  77        if not recipe_crafting_list.empty?
  78          msg << " (For the ingredients: #{recipe_crafting_list.join ' '})"
  79        end
  80  
  81        return msg
  82      end
  83  
  84    end
  85  
  86    def help(plugin, topic='')
  87      "Minecraft utilities: craft [amount] [item] | overworld [x] [y] [z] | nether [x] [y] [z] | mcpoll [host] [port]"
  88    end
  89  
  90    def initialize
  91      super
  92  
  93      recipe_path = File.dirname(__FILE__) + '/minecraft/recipes.json'
  94      @recipes = Recipes.new JSON.parse(IO.read(recipe_path))
  95    end
  96  
  97    Config.register Config::StringValue.new('minecraft.numbered_stacks',
  98      :default => true,
  99      :desc => "Display amounts with stacks.")
 100  
 101    def craft(m, params)
 102      if params.has_key? :search
 103        results = @recipes.search params[:search].join ' '
 104        if results.length == 0
 105          m.reply "Sorry, recipe not found :("
 106        elsif results.length > 1
 107          m.reply "What did you mean? #{results.join ', '}"
 108        else
 109          product = results.first  
 110          if params.has_key? :amount
 111            amount = params[:amount].to_i
 112          else
 113            amount = 1
 114          end
 115          m.reply @recipes.craft(product, amount)
 116        end
 117      else
 118        m.reply "Found #{@recipes.length} crafting recipes."
 119      end
 120    end
 121  
 122    def overworld_nether(m, params)
 123      coords = coords(m, params)
 124      if not coords
 125        return
 126      end
 127      x, y, z = coords
 128  
 129      m.reply "Overworld(#{x}, #{y}, #{z}) -> Nether(#{(x/8).floor}, #{y}, #{(z/8).floor})"
 130    end
 131  
 132    def nether_overworld(m, params)
 133      coords = coords(m, params)
 134      if not coords
 135        return
 136      end
 137      x, y, z = coords
 138  
 139      m.reply "Nether(#{x}, #{y}, #{z}) -> Overworld(#{x*8}, #{y}, #{z*8})"
 140    end
 141  
 142    def poll(m, params)
 143      host = params[:host]
 144      port = params[:port]
 145      @socket = TCPSocket.open(host, port)
 146      # --> byte 254 (0xFE)
 147      @socket.write([0xFE].pack('c'))
 148      s = StringIO.new @socket.read
 149      # should return 0xFF
 150      if s.read(1) != "\xFF"
 151        m.reply 'invalid server reply'
 152        return
 153      end
 154  
 155      # read short (length of string)
 156      len = s.read(2).unpack('n').first.to_i
 157      welcome = s.read(len*2)
 158      welcome = Iconv.conv('utf-8', 'utf-16be', welcome)
 159      welcome, current, max = welcome.split("\xC2\xA7")
 160      m.reply "The server responded: #{welcome} (#{current}/#{max})"
 161      @socket.close
 162    end
 163  
 164    private
 165  
 166    def coords(m, params)
 167      return nil if not params.has_key? :coords or params[:coords].empty?
 168      begin
 169        # split the list of values by space or comma
 170        coords = params[:coords]
 171        if coords.length < 2
 172          coords = params[:coords].join.split(',')
 173        end
 174        coords.map { |n| n.strip } # remove whitespaces & convert to integers
 175  
 176        # either x, y, z OR x, z
 177        if coords.length == 3
 178          x, y, z = coords
 179        elsif coords.length == 2
 180          x, z = coords
 181          y = 0
 182        else
 183          raise 'error parsing coordinates'
 184        end
 185        coords = [x.to_i, y.to_i, z.to_i] 
 186      rescue
 187        m.reply 'error: ' + $!
 188        debug $!
 189        debug $@
 190        return nil
 191      end
 192  
 193      return coords
 194    end
 195  end
 196  
 197  plugin = Minecraft.new
 198  
 199  plugin.map('craft', :action => 'craft')
 200  plugin.map('craft [:amount] *search', :action => 'craft', :requirements => {:amount => /\d+/})
 201  
 202  plugin.map('overworld *coords', :action => 'overworld_nether')
 203  plugin.map('nether *coords', :action => 'nether_overworld')
 204  
 205  plugin.map('mcpoll :host :port', :action => 'poll', :defaults => {:host => 'daddelbox.mount.at', :port => 25565})
 206