rubycraft 0.1.0 → 0.1.1
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.
- data/README.md +3 -1
- data/VERSION +1 -1
- data/lib/rubycraft.rb +5 -0
- data/lib/rubycraft/block.rb +78 -0
- data/lib/rubycraft/block_type.rb +193 -0
- data/lib/rubycraft/byte_converter.rb +48 -0
- data/lib/rubycraft/chunk.rb +120 -0
- data/lib/rubycraft/matrix3d.rb +87 -0
- data/lib/rubycraft/nbt_helper.rb +49 -0
- data/lib/rubycraft/region.rb +259 -0
- metadata +11 -10
- data/lib/block.rb +0 -77
- data/lib/block_type.rb +0 -191
- data/lib/byte_converter.rb +0 -46
- data/lib/chunk.rb +0 -118
- data/lib/matrix3d.rb +0 -86
- data/lib/nbt_helper.rb +0 -48
- data/lib/region.rb +0 -257
@@ -0,0 +1,87 @@
|
|
1
|
+
module RubyCraft
|
2
|
+
class IndexOutOfBoundsError < StandardError
|
3
|
+
|
4
|
+
end
|
5
|
+
|
6
|
+
class Matrix3d
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def bounds
|
10
|
+
[@xlimit, @ylimit, @zlimit]
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(d1,d2,d3)
|
14
|
+
@xlimit = d1
|
15
|
+
@ylimit = d2
|
16
|
+
@zlimit = d3
|
17
|
+
@data = Array.new(d1) { Array.new(d2) { Array.new(d3) } }
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](x, y, z)
|
21
|
+
@data[x][y][z]
|
22
|
+
end
|
23
|
+
|
24
|
+
def []=(x, y, z, value)
|
25
|
+
@data[x][y][z] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def put(index, value)
|
29
|
+
ar = indexToArray(index)
|
30
|
+
self[*ar] = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(index)
|
34
|
+
ar = indexToArray(index)
|
35
|
+
self[*ar]
|
36
|
+
end
|
37
|
+
|
38
|
+
def each(&block)
|
39
|
+
for z in @data
|
40
|
+
for y in z
|
41
|
+
for x in y
|
42
|
+
yield x
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def each_triple_index(&block)
|
49
|
+
return enum_for:each_triple_index unless block_given?
|
50
|
+
@data.each_with_index do |plane, x|
|
51
|
+
plane.each_with_index do |column, y|
|
52
|
+
column.each_with_index do |value, z|
|
53
|
+
yield value, x ,y ,z
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#Actually from any iterable
|
60
|
+
def fromArray(ar)
|
61
|
+
ar.each_with_index { |obj,i| put i, obj }
|
62
|
+
return self
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def to_a(default = nil)
|
67
|
+
map do |x|
|
68
|
+
if x.nil?
|
69
|
+
default
|
70
|
+
else
|
71
|
+
x
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
def indexToArray(index)
|
78
|
+
x = index / (@zlimit * @ylimit)
|
79
|
+
index -= x * (@zlimit * @ylimit)
|
80
|
+
y = index / @zlimit
|
81
|
+
z = index % @zlimit
|
82
|
+
return x, y, z
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'nbtfile'
|
2
|
+
require 'zlib'
|
3
|
+
require 'rubycraft/byte_converter'
|
4
|
+
# Patching nbtfile clases so that they don't gzip/ungzip incorrectly the zlib bytes from
|
5
|
+
#mcr files. Use the methods from ZlibHelper
|
6
|
+
class NBTFile::Private::Tokenizer
|
7
|
+
def initialize(io)
|
8
|
+
@gz = io
|
9
|
+
@state = NBTFile::Private::TopTokenizerState.new
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class NBTFile::Emitter
|
14
|
+
def initialize(stream)
|
15
|
+
@gz = stream
|
16
|
+
@state = NBTFile::Private::TopEmitterState.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module RubyCraft
|
21
|
+
module ZlibHelper
|
22
|
+
def compress(str)
|
23
|
+
Zlib::Deflate.deflate(str)
|
24
|
+
end
|
25
|
+
|
26
|
+
def decompress(str)
|
27
|
+
Zlib::Inflate.inflate(str)
|
28
|
+
end
|
29
|
+
extend self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Handles converting bytes to/from nbt regions, which are compressesed/decompress
|
33
|
+
module NbtHelper
|
34
|
+
extend ByteConverter
|
35
|
+
extend ZlibHelper
|
36
|
+
|
37
|
+
module_function
|
38
|
+
def fromNbt(bytes)
|
39
|
+
NBTFile.read stringToIo decompress toByteString bytes
|
40
|
+
end
|
41
|
+
|
42
|
+
def toBytes(nbt)
|
43
|
+
output = StringIO.new
|
44
|
+
name, body = nbt
|
45
|
+
NBTFile.write(output, name, body)
|
46
|
+
stringToByteArray compress output.string
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'rubycraft/chunk'
|
2
|
+
|
3
|
+
module RubyCraft
|
4
|
+
class LazyChunkDelegate
|
5
|
+
include ByteConverter
|
6
|
+
include ZlibHelper
|
7
|
+
|
8
|
+
def initialize(bytes)
|
9
|
+
@bytes = bytes
|
10
|
+
@chunk = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(&block)
|
14
|
+
_getchunk.each &block
|
15
|
+
end
|
16
|
+
|
17
|
+
def block_map(&block)
|
18
|
+
_getchunk.block_map &block
|
19
|
+
end
|
20
|
+
def block_type_map(&block)
|
21
|
+
_getchunk.block_type_map &block
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](z, x, y)
|
25
|
+
_getchunk[z, x, y]
|
26
|
+
end
|
27
|
+
|
28
|
+
def []=(z, x, y, value)
|
29
|
+
_getchunk[z, x, y] = value
|
30
|
+
end
|
31
|
+
|
32
|
+
def export
|
33
|
+
_getchunk.export
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def toNbt
|
38
|
+
return @bytes if @chunk.nil?
|
39
|
+
@chunk.toNbt
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# unloacs the loaded chunk. Needed for memory optmization
|
44
|
+
def _unload
|
45
|
+
return if @chunk.nil?
|
46
|
+
@bytes = @chunk.toNbt
|
47
|
+
@chunk = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
def _getchunk
|
52
|
+
if @chunk.nil?
|
53
|
+
@chunk = Chunk.fromNbt @bytes
|
54
|
+
end
|
55
|
+
@chunk
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
# Enumerable over chunks
|
61
|
+
class Region
|
62
|
+
include Enumerable
|
63
|
+
include ByteConverter
|
64
|
+
include ZlibHelper
|
65
|
+
|
66
|
+
class RegionWritter
|
67
|
+
def initialize(io)
|
68
|
+
@io = io
|
69
|
+
end
|
70
|
+
|
71
|
+
def pad(count, value = 0)
|
72
|
+
self << Array.new(count) { value }
|
73
|
+
end
|
74
|
+
|
75
|
+
def <<(o)
|
76
|
+
input = o.kind_of?(Array) ? o : [o]
|
77
|
+
@io << ByteConverter.toByteString(input)
|
78
|
+
end
|
79
|
+
|
80
|
+
def close
|
81
|
+
@io.close
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.fromFile(filename)
|
86
|
+
new ByteConverter.stringToByteArray IO.read filename
|
87
|
+
end
|
88
|
+
|
89
|
+
def initialize(bytes)
|
90
|
+
raise "Must be an io" if bytes.kind_of?(String)
|
91
|
+
@bytes = bytes
|
92
|
+
@chunks = Array.new(32) { Array.new(32) }
|
93
|
+
readChunks bytes
|
94
|
+
end
|
95
|
+
|
96
|
+
def chunk(z, x)
|
97
|
+
@chunks[z][x]
|
98
|
+
end
|
99
|
+
|
100
|
+
def unloadChunk(z, x)
|
101
|
+
@chunks[z][x]._unload
|
102
|
+
end
|
103
|
+
|
104
|
+
def each(&block)
|
105
|
+
@chunks.each do |line|
|
106
|
+
line.each do |chunk|
|
107
|
+
yield chunk
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def cube(z, y, x, opts = {}, &block)
|
113
|
+
c = ChunkCube.new(self, [z, y, x], opts[:width], opts[:length], opts[:height])
|
114
|
+
return c unless block_given?
|
115
|
+
c.each &block
|
116
|
+
end
|
117
|
+
|
118
|
+
def exportTo(io)
|
119
|
+
output = RegionWritter.new io
|
120
|
+
chunks = getChunks
|
121
|
+
writeChunkOffsets output, chunks
|
122
|
+
output.pad blockSize, dummytimestamp
|
123
|
+
writeChunks output, chunks
|
124
|
+
output.close
|
125
|
+
end
|
126
|
+
|
127
|
+
def exportToFile(filename)
|
128
|
+
File.open(filename, "wb") { |f| exportTo f }
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
protected
|
133
|
+
def readChunks(bytes)
|
134
|
+
bytes[0..(blockSize - 1)].each_slice(4).each_with_index do |ar, i|
|
135
|
+
offset = bytesToInt [0] + ar[0..-2]
|
136
|
+
count = ar.last
|
137
|
+
if count > 0
|
138
|
+
@chunks[i / 32][i % 32 ] = readChunk(offset, bytes)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def readChunk(offset, bytes)
|
144
|
+
o = offset * blockSize
|
145
|
+
bytecount = bytesToInt bytes[o..(o + 4)]
|
146
|
+
o += 5
|
147
|
+
nbtBytes = bytes[o..(o + bytecount - 2)]
|
148
|
+
LazyChunkDelegate.new nbtBytes
|
149
|
+
end
|
150
|
+
|
151
|
+
def chunkSize(chunk)
|
152
|
+
chunk.size + chunkMetaDataSize
|
153
|
+
end
|
154
|
+
|
155
|
+
def chunkBlocks(chunk)
|
156
|
+
((chunkSize chunk).to_f / blockSize).ceil
|
157
|
+
end
|
158
|
+
|
159
|
+
def writeChunks(output, chunks)
|
160
|
+
for chunk in chunks
|
161
|
+
next if chunk.nil?
|
162
|
+
output << intBytes(chunk.size + 1)
|
163
|
+
output << defaultCompressionType
|
164
|
+
output << chunk
|
165
|
+
remaining = blockSize - chunkSize(chunk)
|
166
|
+
output.pad remaining % blockSize
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def writeChunkOffsets(output, chunks)
|
171
|
+
lastVacantPosition = 2
|
172
|
+
for chunk in chunks
|
173
|
+
if chunk
|
174
|
+
sizeCount = chunkBlocks chunk
|
175
|
+
output << intBytes(lastVacantPosition)[1..3]
|
176
|
+
output << sizeCount
|
177
|
+
lastVacantPosition += sizeCount
|
178
|
+
else
|
179
|
+
output.pad 4
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def getChunks
|
185
|
+
map do |chunk|
|
186
|
+
if chunk.nil?
|
187
|
+
nil
|
188
|
+
else
|
189
|
+
chunk.toNbt
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def chunkMetaDataSize
|
195
|
+
5
|
196
|
+
end
|
197
|
+
|
198
|
+
def defaultCompressionType
|
199
|
+
2
|
200
|
+
end
|
201
|
+
|
202
|
+
def dummytimestamp
|
203
|
+
0
|
204
|
+
end
|
205
|
+
|
206
|
+
def blockSize
|
207
|
+
4096
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
class ChunkCube
|
214
|
+
include Enumerable
|
215
|
+
|
216
|
+
# width corresponds do z, length to x, and height to y.
|
217
|
+
def initialize(region, initialPos, width, length, height)
|
218
|
+
@region = region
|
219
|
+
@initialPos = initialPos
|
220
|
+
@width = width || 1
|
221
|
+
@length = length || 1
|
222
|
+
@height = height || 1
|
223
|
+
end
|
224
|
+
|
225
|
+
def each(&block)
|
226
|
+
z, x, y = @initialPos
|
227
|
+
firstChunkX = x / chunkSide
|
228
|
+
firstChunkZ = z / chunkSide
|
229
|
+
lastChunkX = (x + @length - 1) / chunkSide
|
230
|
+
lastChunkZ = (z + @width - 1) / chunkSide
|
231
|
+
for j in firstChunkZ..lastChunkZ
|
232
|
+
for i in firstChunkX..lastChunkX
|
233
|
+
iterateOverChunk j, i, &block
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
protected
|
239
|
+
def iterateOverChunk(j, i, &block)
|
240
|
+
chunk = @region.chunk(j, i)
|
241
|
+
return if chunk.nil?
|
242
|
+
z, x, y = @initialPos
|
243
|
+
chunk.each do |b|
|
244
|
+
globalZ = b.z + (j * chunkSide)
|
245
|
+
globalX = b.x + (i * chunkSide)
|
246
|
+
if globalZ.between?(z, z + @width - 1) and
|
247
|
+
globalX.between?(x, x + @length - 1) and
|
248
|
+
b.y.between?(y, y + @height - 1)
|
249
|
+
yield b, globalZ - z, globalX - x , b.y - y
|
250
|
+
end
|
251
|
+
end
|
252
|
+
@region.unloadChunk(j, i)
|
253
|
+
end
|
254
|
+
|
255
|
+
def chunkSide
|
256
|
+
16
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubycraft
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Daniel Ribeiro
|
@@ -48,13 +48,14 @@ files:
|
|
48
48
|
- README.md
|
49
49
|
- Rakefile
|
50
50
|
- VERSION
|
51
|
-
- lib/
|
52
|
-
- lib/
|
53
|
-
- lib/
|
54
|
-
- lib/
|
55
|
-
- lib/
|
56
|
-
- lib/
|
57
|
-
- lib/
|
51
|
+
- lib/rubycraft.rb
|
52
|
+
- lib/rubycraft/block.rb
|
53
|
+
- lib/rubycraft/block_type.rb
|
54
|
+
- lib/rubycraft/byte_converter.rb
|
55
|
+
- lib/rubycraft/chunk.rb
|
56
|
+
- lib/rubycraft/matrix3d.rb
|
57
|
+
- lib/rubycraft/nbt_helper.rb
|
58
|
+
- lib/rubycraft/region.rb
|
58
59
|
has_rdoc: true
|
59
60
|
homepage: http://github.com/danielribeiro/RubyCraft
|
60
61
|
licenses: []
|
data/lib/block.rb
DELETED
@@ -1,77 +0,0 @@
|
|
1
|
-
require 'block_type'
|
2
|
-
|
3
|
-
# A minecraft block. Its position is given by a coord[x, z, y]
|
4
|
-
class Block
|
5
|
-
|
6
|
-
attr_accessor :block_type, :pos, :data
|
7
|
-
def initialize(blockType, data = 0)
|
8
|
-
@blockType = blockType
|
9
|
-
@data = 0
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.get(key)
|
13
|
-
new BlockType.get key
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.of(key)
|
17
|
-
self[key]
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.[](key)
|
21
|
-
new BlockType[key]
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
|
-
def color=(color)
|
26
|
-
@data = BlockColor::InvertedColor[color]
|
27
|
-
end
|
28
|
-
|
29
|
-
def color
|
30
|
-
BlockColor.typeColor[@data].name
|
31
|
-
end
|
32
|
-
|
33
|
-
def blockColor
|
34
|
-
BlockColor.typeColor[@data]
|
35
|
-
end
|
36
|
-
|
37
|
-
def is(name)
|
38
|
-
self.name == name.to_s
|
39
|
-
end
|
40
|
-
|
41
|
-
def name
|
42
|
-
@blockType.name
|
43
|
-
end
|
44
|
-
|
45
|
-
def id
|
46
|
-
@blockType.id
|
47
|
-
end
|
48
|
-
|
49
|
-
def transparent
|
50
|
-
@blockType.transparent
|
51
|
-
end
|
52
|
-
|
53
|
-
#sets block type by name
|
54
|
-
def name=(newName)
|
55
|
-
return if name == newName.to_s
|
56
|
-
@blockType = BlockType[newName]
|
57
|
-
end
|
58
|
-
|
59
|
-
#sets block type by id
|
60
|
-
def id=(id)
|
61
|
-
return if id == id
|
62
|
-
@blockType = BlockType.get id
|
63
|
-
end
|
64
|
-
|
65
|
-
def y
|
66
|
-
pos[2]
|
67
|
-
end
|
68
|
-
|
69
|
-
def z
|
70
|
-
pos[1]
|
71
|
-
end
|
72
|
-
|
73
|
-
def x
|
74
|
-
pos[0]
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|