mcblocky 0.1.0.pre.alpha.pre.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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