mcblocky 0.1.0.pre.alpha.pre.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,146 @@
1
+ load File.expand_path('dsl/selector.rb', File.dirname(__FILE__))
2
+ load File.expand_path('dsl/commands.rb', File.dirname(__FILE__))
3
+ load File.expand_path('dsl/repeat_chain.rb', File.dirname(__FILE__))
4
+ load File.expand_path('dsl/command_block.rb', File.dirname(__FILE__))
5
+ load File.expand_path('dsl/block.rb', File.dirname(__FILE__))
6
+ load File.expand_path('dsl/container.rb', File.dirname(__FILE__))
7
+ require 'json'
8
+
9
+ module McBlocky
10
+ module DSL
11
+ def helper(*command, &block)
12
+ context.helpers << [command, block]
13
+ end
14
+
15
+ def initial(&block)
16
+ chain = Commands.new(:initial)
17
+ chain.instance_exec(&block)
18
+ chains << chain
19
+ end
20
+
21
+ def cleanup(&block)
22
+ chain = Commands.new(:cleanup)
23
+ chain.instance_exec(&block)
24
+ chains << chain
25
+ end
26
+
27
+ def after(&block)
28
+ chain = Commands.new(:after)
29
+ chain.instance_exec(&block)
30
+ chains << chain
31
+ end
32
+
33
+ def repeat(x1, y1, z1, x2, y2, z2, &block)
34
+ chain = RepeatChain.new(x1, y1, z1, x2, y2, z2)
35
+ chain.instance_exec(&block)
36
+ chains << chain
37
+ end
38
+
39
+ def at(x, y, z, data=0, kind=:normal, nbt={}, &block)
40
+ if Symbol === data
41
+ kind = data
42
+ data = 0
43
+ end
44
+ block_kind = case kind
45
+ when :normal
46
+ 'minecraft:command_block'
47
+ when :chain
48
+ 'minecraft:chain_command_block'
49
+ when :repeating
50
+ 'minecraft:repeating_command_block'
51
+ else
52
+ raise ArgumentError, 'Unknown command block type'
53
+ end
54
+ cblock = CommandBlock.new(x, y, z, data, block_kind, nbt)
55
+ cblock.instance_exec(&block)
56
+ blocks[Location.new(x, y, z)] = cblock
57
+ end
58
+
59
+ def setblock(x, y, z, kind, data=0, replacemode='replace', nbt={})
60
+ block = Block.new(x, y, z, kind, data, nbt)
61
+ blocks[Location.new(x, y, z)] = block
62
+ end
63
+
64
+ def fill(x1, y1, z1, x2, y2, z2, kind, data=0)
65
+ block = Block.new(nil, nil, nil, kind, data)
66
+ rects[Rect.new(x1, y1, z1, x2, y2, z2)] = block
67
+ end
68
+
69
+ def chest(x, y, z, data=0, &block)
70
+ container = Container.new(x, y, z, 'minecraft:chest', data)
71
+ container.instance_exec(&block)
72
+ blocks[Location.new(x, y, z)] = container
73
+ end
74
+
75
+ def trapped_chest(x, y, z, data=0, &block)
76
+ container = Container.new(x, y, z, 'minecraft:trapped_chest', data)
77
+ container.instance_exec(&block)
78
+ blocks[Location.new(x, y, z)] = container
79
+ end
80
+
81
+ def dispenser(x, y, z, data=0, &block)
82
+ container = Container.new(x, y, z, 'minecraft:dispenser', data)
83
+ container.instance_exec(&block)
84
+ blocks[Location.new(x, y, z)] = container
85
+ end
86
+
87
+ def dropper(x, y, z, data=0, &block)
88
+ container = Container.new(x, y, z, 'minecraft:dropper', data)
89
+ container.instance_exec(&block)
90
+ blocks[Location.new(x, y, z)] = container
91
+ end
92
+
93
+ def furnace(x, y, z, data=0, &block)
94
+ container = Container.new(x, y, z, 'minecraft:furnace', data)
95
+ container.instance_exec(&block)
96
+ blocks[Location.new(x, y, z)] = container
97
+ end
98
+
99
+ def to_nbt(obj)
100
+ case obj
101
+ when String
102
+ JSON.dump(obj)
103
+ when Fixnum, Float
104
+ obj.to_s
105
+ when Array
106
+ "[#{obj.map{|x| to_nbt x}.join(',')}]"
107
+ when Hash
108
+ pairs = obj.map do |k,v|
109
+ "#{k}:#{to_nbt v}"
110
+ end
111
+ "{#{pairs.join(',')}}"
112
+ else
113
+ raise ArgumentError, "No NBT form for #{obj}"
114
+ end
115
+ end
116
+ module_function :to_nbt
117
+
118
+ module Facing
119
+ DOWN = 0
120
+ UP = 1
121
+ NORTH = 2
122
+ SOUTH = 3
123
+ WEST = 4
124
+ EAST = 5
125
+ end
126
+
127
+ module Color
128
+ WHITE = 0
129
+ ORANGE = 1
130
+ MAGENTA = 2
131
+ LIGHT_BLUE = 3
132
+ YELLOW = 4
133
+ LIME = 5
134
+ PINK = 6
135
+ GRAY = 7
136
+ LIGHT_GRAY = 8
137
+ CYAN = 9
138
+ PURPLE = 10
139
+ BLUE = 11
140
+ BROWN = 12
141
+ GREEN = 13
142
+ RED = 14
143
+ BLACK = 15
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,149 @@
1
+ module McBlocky
2
+ class Executor < DSL::Commands
3
+ def self.to_commands(context, old_context=nil)
4
+ executor = Executor.new(:final)
5
+ executor.do_context(context, old_context)
6
+ executor.commands
7
+ end
8
+
9
+ def do_context(context, old_context)
10
+ # do cleanup first
11
+ if old_context
12
+ old_context.chains.select{|x|x.kind == :cleanup}.each do |c|
13
+ self.commands += c.commands
14
+ end
15
+ end
16
+
17
+ # do initial blocks
18
+ old_initials = old_context ? old_context.chains.select{|x|x.kind == :initial} : []
19
+ initials = context.chains.select{|x|x.kind == :initial}
20
+ initials.each_with_index do |chain, i|
21
+ old_chain = old_initials[i]
22
+ if old_chain
23
+ matches = true
24
+ chain.commands.each_with_index do |cmd, j|
25
+ if matches
26
+ old_cmd = old_chain.commands[j]
27
+ next if old_cmd == cmd
28
+ matches = false
29
+ command cmd
30
+ else
31
+ command cmd
32
+ end
33
+ end
34
+ else
35
+ chain.commands.each {|cmd| command cmd}
36
+ end
37
+ end
38
+
39
+ if old_context
40
+ old_context.areas.each do |x1, y1, z1, x2, y2, z2|
41
+ fill x1, y1, z1, x2, y2, z2, 'minecraft:air'
42
+ end
43
+ end
44
+ context.areas.each do |x1, y1, z1, x2, y2, z2|
45
+ fill x1, y1, z1, x2, y1, z2, 'minecraft:stained_glass', '7'
46
+ fill x1, y2, z1, x2, y2, z2, 'minecraft:stained_glass', '7'
47
+ fill x1, y1, z1, x1, y2, z2, 'minecraft:stained_glass', '7'
48
+ fill x2, y1, z1, x2, y2, z2, 'minecraft:stained_glass', '7'
49
+ fill x1, y1, z1, x2, y2, z1, 'minecraft:stained_glass', '7'
50
+ fill x1, y1, z2, x2, y2, z2, 'minecraft:stained_glass', '7'
51
+ end
52
+
53
+ rects = (old_context ? old_context.rects.keys : []) + context.rects.keys
54
+ rects.uniq.each do |rect|
55
+ old_block = old_context ? old_context.rects[rect] : nil
56
+ block = context.rects[rect]
57
+ if old_block and !block
58
+ fill rect.x1, rect.y1, rect.z1, rect.x2, rect.y2, rect.z2, 'minecraft:air'
59
+ elsif old_block and old_block != block
60
+ fill rect.x1, rect.y1, rect.z1, rect.x2, rect.y2, rect.z2, block.block_kind, block.block_data, 'replace', old_block.block_kind, old_block.block_data
61
+ else
62
+ fill rect.x1, rect.y1, rect.z1, rect.x2, rect.y2, rect.z2, block.block_kind, block.block_data
63
+ end
64
+ end
65
+
66
+ context.chains.select{|x|x.kind == :repeat}.each do |c|
67
+ do_repeat context, c
68
+ end
69
+
70
+ locations = (old_context ? old_context.blocks.keys : []) + context.blocks.keys
71
+ locations.uniq.each do |loc|
72
+ old = old_context ? old_context.blocks[loc] : nil
73
+ new = context.blocks[loc]
74
+ do_block(new, old)
75
+ end
76
+
77
+ # after blocks are set
78
+ context.chains.select{|x|x.kind == :after}.each do |c|
79
+ self.commands += c.commands
80
+ end
81
+ end
82
+
83
+ def do_block(block, old_block=nil)
84
+ if old_block and !block
85
+ setblock old_block.x, old_block.y, old_block.z, 'minecraft:air'
86
+ return
87
+ end
88
+
89
+ if old_block and old_block.block_kind == block.block_kind and old_block.block_data == block.block_data
90
+ return if old_block.nbt == block.nbt
91
+ blockdata block.x, block.y, block.z, block.nbt unless block.nbt == {}
92
+ else
93
+ setblock block.x, block.y, block.z, block.block_kind, block.block_data, 'replace'
94
+ blockdata block.x, block.y, block.z, block.nbt unless block.nbt == {}
95
+ end
96
+ end
97
+
98
+ def do_repeat(context, chain)
99
+ sequence = fill_space(chain.rect)
100
+ if chain.commands.length > sequence.length
101
+ raise ArgumentError, "Chain is too long for the provided space"
102
+ end
103
+ kind = 'minecraft:repeating_command_block'
104
+ chain.commands.each_with_index do |c,i|
105
+ cursor = sequence[i]
106
+ next_cursor = if i+1 < sequence.length
107
+ sequence[i+1]
108
+ else
109
+ Location.new(cursor.x, cursor.y+1, cursor.z)
110
+ end
111
+ facing = if next_cursor.x - cursor.x == 1
112
+ DSL::Facing::EAST
113
+ elsif next_cursor.x - cursor.x == -1
114
+ DSL::Facing::WEST
115
+ elsif next_cursor.y - cursor.y == 1
116
+ DSL::Facing::UP
117
+ elsif next_cursor.y - cursor.y == -1
118
+ DSL::Facing::DOWN
119
+ elsif next_cursor.z - cursor.z == 1
120
+ DSL::Facing::SOUTH
121
+ elsif next_cursor.z - cursor.z == -1
122
+ DSL::Facing::NORTH
123
+ end
124
+ context.blocks[cursor] = DSL::CommandBlock.new(cursor.x, cursor.y, cursor.z, facing, kind, {'auto'=>1})
125
+ context.blocks[cursor].command c
126
+ kind = 'minecraft:chain_command_block'
127
+ end
128
+ end
129
+
130
+ def fill_space(rect)
131
+ path = []
132
+ zrange = rect.z1..rect.z2
133
+ zrange.each do |z|
134
+ rz = z - rect.z1
135
+ yrange = rect.y1..rect.y2
136
+ yrange = yrange.to_a.reverse if rz % 2 != 0
137
+ yrange.each do |y|
138
+ ry = y - rect.y1
139
+ xrange = rect.x1..rect.x2
140
+ xrange = xrange.to_a.reverse if (ry+rz) % 2 != 0
141
+ xrange.each do |x|
142
+ path << Location.new(x, y, z)
143
+ end
144
+ end
145
+ end
146
+ path
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,69 @@
1
+ require 'listen'
2
+ require 'mcblocky/logging'
3
+
4
+ module McBlocky
5
+ class Listener
6
+ include Logging
7
+
8
+ def self.from_config(&block)
9
+ Config.validate
10
+ return Listener.new(File.dirname(Config.config_path), Config.config['code']['main'], &block)
11
+ end
12
+
13
+ def initialize(dir, main, &block)
14
+ @dir = dir
15
+ @main = main
16
+ @initial_files = [
17
+ @main,
18
+ File.expand_path('../dsl.rb', __FILE__),
19
+ File.expand_path('../dsl/commands.rb', __FILE__),
20
+ File.expand_path('../dsl/repeat_chain.rb', __FILE__),
21
+ File.expand_path('../dsl/selector.rb', __FILE__),
22
+ File.expand_path('../dsl/command_block.rb', __FILE__),
23
+ File.expand_path('../dsl/block.rb', __FILE__),
24
+ File.expand_path('../dsl/container.rb', __FILE__),
25
+ File.expand_path('../context.rb', __FILE__),
26
+ File.expand_path('../executor.rb', __FILE__),
27
+ ]
28
+ @files = @initial_files
29
+ @listener = Listen.to(dir, File.dirname(__FILE__), only: /\.rb$/, &method(:handle))
30
+ @handler = block
31
+ end
32
+
33
+ def start
34
+ @listener.start
35
+ begin
36
+ log_status "Loading"
37
+ result = Context.run_file(@main, @dir)
38
+ rescue Exception
39
+ log_error "Error in loaded file:"
40
+ puts $!
41
+ return
42
+ end
43
+ @handler.call(result)
44
+ @files = @initial_files + result.required_files.to_a if result.required_files
45
+ end
46
+
47
+ def handle(modified, added, removed)
48
+ @files.each do |f|
49
+ f = File.expand_path(f, @dir).gsub('\\','/')
50
+ if modified.include? f or added.include? f or removed.include? f
51
+ begin
52
+ log_status "Reloading..."
53
+ McBlocky.reload!
54
+ result = Context.run_file(@main, @dir)
55
+ rescue Exception => e
56
+ log_error "Error in loaded file:"
57
+ puts e.backtrace.join("\n\t")
58
+ .sub("\n\t", ": #{e}#{e.class ? " (#{e.class})" : ""}\n\t")
59
+ break
60
+ end
61
+ @handler.call(result)
62
+ @files = @initial_files + result.required_files.to_a if result.required_files
63
+ log_status "Reloaded."
64
+ break
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,95 @@
1
+ module McBlocky
2
+ class Location < Struct.new(:x, :y, :z)
3
+ def +(other)
4
+ if other.is_relative?
5
+ Location.new(x + other.x, y + other.y, z + other.z)
6
+ else
7
+ RelativeLocation.new(x + other.x, y + other.y, z + other.z)
8
+ end
9
+ end
10
+
11
+ def -(other)
12
+ if other.is_relative?
13
+ Location.new(x - other.x, y - other.y, z - other.z)
14
+ else
15
+ RelativeLocation.new(x - other.x, y - other.y, z - other.z)
16
+ end
17
+ end
18
+
19
+ def is_relative?
20
+ false
21
+ end
22
+ end
23
+
24
+ class RelativeLocation < Location
25
+ def +(other)
26
+ if other.is_relative?
27
+ RelativeLocation.new(x + other.x, y + other.y, z + other.z)
28
+ else
29
+ Location.new(x + other.x, y + other.y, z + other.z)
30
+ end
31
+ end
32
+
33
+ def -(other)
34
+ if other.is_relative?
35
+ RelativeLocation.new(x - other.x, y - other.y, z - other.z)
36
+ else
37
+ Location.new(x - other.x, y - other.y, z - other.z)
38
+ end
39
+ end
40
+
41
+ def is_relative?
42
+ true
43
+ end
44
+ end
45
+
46
+ class Rect < Struct.new(:x1, :y1, :z1, :x2, :y2, :z2)
47
+ def initialize(*args)
48
+ if args.length == 6
49
+ super
50
+ elsif args.length == 2
51
+ super(args[0].x, args[0].y, args[0].z, args[1].x, args[1].y, args[1].z)
52
+ elsif args.length == 4
53
+ if args[0].respond_to? :x
54
+ super(args[0].x, args[0].y, args[0].z, args[1], args[2], args[3])
55
+ elsif args[3].respond_to? :x
56
+ super(args[0], args[1], args[2], args[3].x, args[3].y, args[3].z)
57
+ else
58
+ raise ArgumentError
59
+ end
60
+ else
61
+ raise ArgumentError
62
+ end
63
+
64
+ if x1 > x2
65
+ self.x1, self.x2 = self.x2, self.x1
66
+ end
67
+ if y1 > y2
68
+ self.y1, self.y2 = self.y2, self.y1
69
+ end
70
+ if z1 > z2
71
+ self.z1, self.z2 = self.z2, self.z1
72
+ end
73
+ end
74
+
75
+ def p1
76
+ @p1 ||= Location.new(x1, y1, z1)
77
+ end
78
+
79
+ def p2
80
+ @p2 ||= Location.new(x2, y2, z2)
81
+ end
82
+
83
+ def w
84
+ x2 - x1
85
+ end
86
+
87
+ def h
88
+ y2 - y1
89
+ end
90
+
91
+ def d
92
+ z2 - z1
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,45 @@
1
+ module McBlocky
2
+ module Logging
3
+ RESET = "\e[0m"
4
+ BOLD = "\e[1m"
5
+ RED = "\e[31m"
6
+ GREEN = "\e[32m"
7
+ YELLOW = "\e[33m"
8
+ BLUE = "\e[34m"
9
+ MAGENTA = "\e[35m"
10
+ CYAN = "\e[36m"
11
+ WHITE = "\e[37m"
12
+
13
+ @@mutex = Mutex.new
14
+
15
+ def log_server(message)
16
+ @@mutex.synchronize do
17
+ puts "#{YELLOW}#{message.chomp}#{RESET}"
18
+ end
19
+ end
20
+
21
+ def log_message(message)
22
+ @@mutex.synchronize do
23
+ puts "#{MAGENTA}#{message.chomp}#{RESET}"
24
+ end
25
+ end
26
+
27
+ def log_command(message)
28
+ @@mutex.synchronize do
29
+ puts "#{CYAN}#{BOLD}/#{message.chomp}#{RESET}"
30
+ end
31
+ end
32
+
33
+ def log_status(message)
34
+ @@mutex.synchronize do
35
+ puts "#{GREEN}#{BOLD}---> #{message.chomp}#{RESET}"
36
+ end
37
+ end
38
+
39
+ def log_error(message)
40
+ @@mutex.synchronize do
41
+ puts "#{RED}#{BOLD}---> #{message.chomp}#{RESET}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,143 @@
1
+ require "open3"
2
+ require "mcblocky/logging"
3
+
4
+ module McBlocky
5
+ class ServerShutdown < StandardError; end
6
+ class Server
7
+ include Logging
8
+ def self.from_config
9
+ Config.validate
10
+ server = Config.config['server']
11
+ workdir = File.expand_path(server['workdir'], File.dirname(Config.config_path))
12
+ Dir.mkdir workdir unless Dir.exist? workdir
13
+ Dir.chdir workdir do
14
+ if server['eula']
15
+ open('eula.txt', 'w') do |f|
16
+ f.write("eula=true#{$/}")
17
+ end
18
+ end
19
+
20
+ set_server_properties(server['properties'])
21
+ end
22
+ return Server.new(server['jar'], workdir, server['java'], server['ops'])
23
+ end
24
+
25
+ def self.set_server_properties(properties, filename='server.properties')
26
+ lines = if File.exist? filename
27
+ open(filename).readlines
28
+ else
29
+ []
30
+ end
31
+ properties.each do |k,v|
32
+ if !lines.select{|l| l.start_with? "#{k}="}.empty?
33
+ lines.map! do |l|
34
+ if l.start_with? "#{k}="
35
+ "#{k}=#{v}#{$/}"
36
+ else
37
+ l
38
+ end
39
+ end
40
+ else
41
+ lines << "#{k}=#{v}#{$/}"
42
+ end
43
+ end
44
+ IO.write(filename, lines.join(''))
45
+ end
46
+
47
+ def initialize(jar, workdir, java=nil, ops=nil)
48
+ @java = java || 'java'
49
+ @jar = jar
50
+ @workdir = workdir
51
+ @queue = Queue.new
52
+ @matchers = []
53
+ @message_matchers = []
54
+ @ops = ops
55
+ end
56
+
57
+ def start
58
+ Dir.chdir @workdir do
59
+ @stdin, @stdout, @wait_thr = Open3.popen2e "#{@java} -jar #{@jar} nogui"
60
+ end
61
+ @reader = Thread.new(@stdout) do |stream|
62
+ until stream.closed?
63
+ begin
64
+ line = stream.readline
65
+ @queue << line
66
+ log_server line
67
+ if line =~ /\<([^>]+)\> (.*)$/
68
+ log_message "<#{$1}> #{$2}"
69
+ end
70
+ rescue EOFError
71
+ break
72
+ end
73
+ end
74
+ Thread.main.raise ServerShutdown
75
+ end
76
+ wait_for_line /Done \(.*?\)!/
77
+ if @ops
78
+ @ops.each {|op| command "op #{op}"}
79
+ end
80
+ end
81
+
82
+ def command(cmd)
83
+ log_command cmd
84
+ @stdin.write("#{cmd}#{$/}")
85
+ end
86
+
87
+ def say(message)
88
+ log_message "[Server] #{message}"
89
+ @stdin.write("say #{message}#{$/}")
90
+ end
91
+
92
+ def on_line(match, &block)
93
+ @matchers << [match, block]
94
+ end
95
+
96
+ def on_message(match, user=nil, &block)
97
+ @message_matchers << [match, user, block]
98
+ end
99
+
100
+ def stop
101
+ unless @stopping
102
+ @matchers = []
103
+ command "stop"
104
+ @stdin.close
105
+ @stopping = true
106
+ end
107
+ join
108
+ end
109
+
110
+ def loop!
111
+ wait_for_line nil
112
+ rescue ServerShutdown
113
+ log_status "Server stopped."
114
+ join
115
+ end
116
+
117
+ private
118
+ def wait_for_line(match)
119
+ begin
120
+ line = @queue.pop
121
+ @matchers.each do |m, block|
122
+ block.call(line) if m === line
123
+ end
124
+ if line =~ /\<([^>]+)\> (.*)$/
125
+ user, message = $1, $2
126
+ @message_matchers.each do |m, u, block|
127
+ if !u or u == user or u === user
128
+ if m === message
129
+ block.call(message, user)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end until match and match === line
135
+ line
136
+ end
137
+
138
+ def join
139
+ @wait_thr.join
140
+ @reader.join
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,3 @@
1
+ module McBlocky
2
+ VERSION = "0.1.0"
3
+ end
data/lib/mcblocky.rb ADDED
@@ -0,0 +1,23 @@
1
+ module McBlocky
2
+ def self.reload!
3
+ McBlocky.send(:remove_const, :DSL) if defined? McBlocky::DSL
4
+ McBlocky.send(:remove_const, :Context) if defined? McBlocky::Context
5
+ McBlocky.send(:remove_const, :Executor) if defined? McBlocky::Executor
6
+ load File.expand_path('mcblocky/dsl.rb', File.dirname(__FILE__))
7
+ load File.expand_path('mcblocky/context.rb', File.dirname(__FILE__))
8
+ load File.expand_path('mcblocky/executor.rb', File.dirname(__FILE__))
9
+ end
10
+ end
11
+
12
+ # non-reloadable
13
+ require "mcblocky/location"
14
+ require "mcblocky/config"
15
+ require "mcblocky/listener"
16
+ require "mcblocky/server"
17
+ require "mcblocky/version"
18
+ require "mcblocky/cli"
19
+
20
+ # reloadable
21
+ require "mcblocky/context"
22
+ require "mcblocky/dsl"
23
+ require "mcblocky/executor"
data/mcblocky.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mcblocky/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mcblocky"
8
+ spec.version = McBlocky::VERSION
9
+ spec.version = "#{spec.version}-alpha-#{ENV['TRAVIS_BUILD_NUMBER']}" if ENV['TRAVIS']
10
+ spec.authors = ["Michael Limiero"]
11
+ spec.email = ["mike5713@gmail.com"]
12
+
13
+ spec.summary = %q{Minecraft command blocks as Ruby code}
14
+ spec.description = %q{McBlocky is a Ruby DSL for creating Minecraft command block contraptions and maps.}
15
+ spec.homepage = "https://github.com/DeltaWhy/mcblocky"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.11"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "minitest", "~> 5.0"
25
+
26
+ spec.add_runtime_dependency "thor", "~> 0.19"
27
+ spec.add_runtime_dependency "listen", "~> 3.0"
28
+ end