1
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
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
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
147 @socket.write([0xFE].pack('c'))
148 s = StringIO.new @socket.read
149
150 if s.read(1) != "\xFF"
151 m.reply 'invalid server reply'
152 return
153 end
154
155
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
170 coords = params[:coords]
171 if coords.length < 2
172 coords = params[:coords].join.split(',')
173 end
174 coords.map { |n| n.strip }
175
176
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