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.
- checksums.yaml +15 -0
- data/.gitignore +11 -0
- data/.travis.yml +16 -0
- data/Gemfile +4 -0
- data/README.md +34 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/workon.cmd +1 -0
- data/doc/Introduction.md +144 -0
- data/examples/ctf/arena.rb +14 -0
- data/examples/ctf/config.example.yml +15 -0
- data/examples/ctf/ctf.rb +316 -0
- data/examples/ctf/lobby.rb +20 -0
- data/examples/hello/config.example.yml +14 -0
- data/examples/hello/hello.rb +81 -0
- data/examples/hello/helpers/bar.rb +3 -0
- data/examples/hello/helpers/foo.rb +3 -0
- data/exe/mcblocky +6 -0
- data/lib/mcblocky/cli.rb +108 -0
- data/lib/mcblocky/config.rb +63 -0
- data/lib/mcblocky/context.rb +87 -0
- data/lib/mcblocky/dsl/block.rb +13 -0
- data/lib/mcblocky/dsl/command_block.rb +23 -0
- data/lib/mcblocky/dsl/commands.rb +152 -0
- data/lib/mcblocky/dsl/container.rb +24 -0
- data/lib/mcblocky/dsl/repeat_chain.rb +9 -0
- data/lib/mcblocky/dsl/selector.rb +29 -0
- data/lib/mcblocky/dsl.rb +146 -0
- data/lib/mcblocky/executor.rb +149 -0
- data/lib/mcblocky/listener.rb +69 -0
- data/lib/mcblocky/location.rb +95 -0
- data/lib/mcblocky/logging.rb +45 -0
- data/lib/mcblocky/server.rb +143 -0
- data/lib/mcblocky/version.rb +3 -0
- data/lib/mcblocky.rb +23 -0
- data/mcblocky.gemspec +28 -0
- metadata +151 -0
data/lib/mcblocky/dsl.rb
ADDED
@@ -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
|
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
|